1
0
mirror of https://github.com/flarum/core.git synced 2025-08-16 13:24:11 +02:00

Compare commits

..

35 Commits

Author SHA1 Message Date
Sami Mazouz
1caff3e6fc fix(sticky): filter mutator union takes order by out of its clause caung trouble
Signed-off-by: Sami Mazouz <ilyasmazouz@gmail.com>
2022-07-07 14:20:25 +01:00
Sami Mazouz
e21a1e09fd test(sticky): list discussions works as expected with stickies
Signed-off-by: Sami Mazouz <ilyasmazouz@gmail.com>
2022-07-07 14:16:01 +01:00
Sami Mazouz
03178bb460 fix: sort field in search queries conflicts between multiple tables
Signed-off-by: Sami Mazouz <ilyasmazouz@gmail.com>
2022-07-07 13:14:47 +01:00
Sami Mazouz
5753edac79 test: list posts with mentions filter and createdAt sort
Signed-off-by: Sami Mazouz <ilyasmazouz@gmail.com>
2022-07-07 13:11:23 +01:00
Daniël Klabbers
2e3d6dfa2c chore: upstream changes to dependencies 2022-07-05 21:19:01 +02:00
David Wheatley
7cd28710bc fix: composer title positioning incorrect with custom header height (#3502) 2022-07-04 14:28:44 +01:00
Clark Winkelmann
bc1d6f9e91 fix: assertAdmin sending wrong ability name to gate (#3501) 2022-07-04 14:18:18 +01:00
Sami Mazouz
bf4c543692 fix: restricted sibling tags appearing for unauthorized members on the sidebar (#3419)
* test: user should only be able to see related tags when allowed

* fix: restricted sibling tags appearing for unauthorized members on the sidebar

* fix: apply logic on tags with parents
2022-07-04 12:19:03 +01:00
David Sevilla Martin
709c5566bb fix: overflow notifications group header text with ellipses (#3500)
Fixes #3408
2022-07-04 12:16:59 +01:00
David Wheatley
9a62c32c28 fix: remove return type 2022-07-04 10:52:10 +01:00
flarum-bot
bbf90e42ff Bundled output for commit 0859bb13a5
Includes transpiled JS/TS, and Typescript declaration files (typings).

[skip ci]
2022-06-21 15:36:15 +00:00
Daniël Klabbers
0859bb13a5 feature: adds advanced link handling in core (#3455)
* feature: adds advanced link handling in core

This PR adds rel and target to textformatter so that these can be easily extended and rendered into the source.

Without using the Extender the default values `ngc nofollow` are provided as a backward compatible way.

The new extender allows conditional overrides, a proof of concept extension is available at https://github.com/luceos/flarum-ext-dofollow; I will probably migrate this into the Blomstra namespace soon.

* Apply fixes from StyleCI

* fix typehints

* fix: mixed typehint is php 8+

Co-authored-by: StyleCI Bot <bot@styleci.io>
2022-06-21 17:32:23 +02:00
flarum-bot
62be3e01be Bundled output for commit 613523c9b4
Includes transpiled JS/TS, and Typescript declaration files (typings).

[skip ci]
2022-06-20 15:57:13 +00:00
Sami Mazouz
613523c9b4 fix(tags): tag selection modal shown when no tags can be selected (#3448) 2022-06-20 16:53:11 +01:00
flarum-bot
3e56bd3dc6 Bundled output for commit 293e8ab8b6
Includes transpiled JS/TS, and Typescript declaration files (typings).

[skip ci]
2022-06-20 12:06:42 +00:00
David Wheatley
293e8ab8b6 feat: support custom reusable setting components for buildSettingComponent (#3494) 2022-06-20 13:01:29 +01:00
David Wheatley
824fb2feff chore: enable and set up prettier for flarum/tags (#3496) 2022-06-20 13:00:01 +01:00
flarum-bot
4923253fbf Bundled output for commit 36c296d787
Includes transpiled JS/TS, and Typescript declaration files (typings).

[skip ci]
2022-06-20 03:17:23 +00:00
David Wheatley
36c296d787 fix(a11y: DiscussionListItem): controls dropdown not visible when tabbed into (#3450) 2022-06-20 03:52:54 +01:00
David Wheatley
9fc2e5e2c0 fix(extension typings): remove some accessibility modifiers from Component (#3437) 2022-06-20 03:52:25 +01:00
David Wheatley
cb47a9c92e feat(translator): add locale getter (#3451) 2022-06-20 03:51:26 +01:00
Martin Hasoň
5dedec12f9 feat: return error code for failed CLI-based installs (#3452) 2022-06-20 03:50:38 +01:00
flarum-bot
5e81592e18 Bundled output for commit 74bcab866c
Includes transpiled JS/TS.

[skip ci]
2022-06-19 22:54:28 +00:00
Sami Mazouz
74bcab866c fix(suspend): suspension modal shows after suspension is over (#3449) 2022-06-19 23:51:29 +01:00
David Wheatley
1d949a3170 feat: split frontend asset generation into separate steps for more extensibility (#3446) 2022-06-19 22:58:05 +01:00
flarum-bot
79e0f44324 Bundled output for commit 976a03c9e5
Includes transpiled JS/TS.

[skip ci]
2022-06-19 21:30:47 +00:00
Sami Mazouz
976a03c9e5 fix: post mention notification errors with _no content_ subjects (#3493)
Co-authored-by: David Wheatley <hi@davwheat.dev>
2022-06-19 22:27:39 +01:00
SychO9
49ab6630d4 chore: run yarn-audit-fix 2022-06-19 20:16:55 +01:00
Sami Mazouz
27fb011bf5 chore: with yarn workspaces we only need a single yarn.lock (#3464)
* chore: yarn workspaces means we only need a single `yarn.lock`
* fix: add a `cache_dependency_path` workflow input
* fix: re-introduce `env.cache_dependency_path`
2022-06-19 19:01:21 +01:00
Sami Mazouz
6466427061 chore: move reusable workflows to monorepo (#3490) 2022-06-19 11:14:21 +01:00
flawedworld
818035f385 feat: split preloaded JSON payload and frontend boot into separate <script> tags (#3461) 2022-06-13 16:11:00 +01:00
flarum-bot
4748f31d93 Bundled output for commit e049e9d7ae
Includes transpiled JS/TS, and Typescript declaration files (typings).

[skip ci]
2022-06-09 09:14:05 +00:00
Ian Morland
e049e9d7ae chore: add priorities to AdminNav (#3453) 2022-06-09 10:07:50 +01:00
Rafael Horvat
776f9bf132 feat: add automatic created/updated timestamps for various tables (#3435)
Adds timestamps for various tables:

- groups
- group_user
- group_permission
- tags
- discussion_tag
- post_mentions_post
- post_mentions_user
2022-06-07 20:35:27 +01:00
Daniël Klabbers
f882ff9bb5 update constant for 1.3.1 2022-06-07 14:34:51 +02:00
148 changed files with 1756 additions and 61083 deletions

112
.github/workflows/REUSABLE_backend.yml vendored Normal file
View File

@@ -0,0 +1,112 @@
name: Flarum Backend Jobs
on:
workflow_call:
inputs:
enable_backend_testing:
description: "Enable Backend Testing?"
type: boolean
default: true
required: false
backend_directory:
description: The directory of the project where backend code is located. This should contain a `composer.json` file, and is generally the root directory of the repo.
type: string
required: false
default: '.'
php_versions:
description: Versions of PHP to test with. Should be array of strings encoded as JSON array
type: string
required: false
default: '["7.4", "8.0", "8.1"]'
db_versions:
description: Versions of databases to test with. Should be array of strings encoded as JSON array
type: string
required: false
default: '["mysql:5.7", "mariadb"]'
php_ini_values:
description: PHP ini values
type: string
required: false
default: error_reporting=E_ALL
env:
COMPOSER_ROOT_VERSION: dev-main
FLARUM_TEST_TMP_DIR_LOCAL: tests/integration/tmp
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
php: ${{ fromJSON(inputs.php_versions) }}
service: ${{ fromJSON(inputs.db_versions) }}
prefix: ['', flarum_]
include:
- service: 'mysql:5.7'
db: MySQL
- service: mariadb
db: MariaDB
- prefix: flarum_
prefixStr: (prefix)
exclude:
- php: 8.0
service: 'mysql:5.7'
prefix: flarum_
- php: 8.0
service: mariadb
prefix: flarum_
services:
mysql:
image: ${{ matrix.service }}
ports:
- 13306:3306
name: 'PHP ${{ matrix.php }} / ${{ matrix.db }} ${{ matrix.prefixStr }}'
if: inputs.enable_backend_testing
steps:
- uses: actions/checkout@master
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
coverage: xdebug
extensions: curl, dom, gd, json, mbstring, openssl, pdo_mysql, tokenizer, zip
tools: phpunit, composer:v2
ini-values: ${{ inputs.php_ini_values }}
# The authentication alter is necessary because newer mysql versions use the `caching_sha2_password` driver,
# which isn't supported prior to PHP7.4
# When we drop support for PHP7.3, we should remove this from the setup.
- name: Create MySQL Database
run: |
sudo systemctl start mysql
mysql -uroot -proot -e 'CREATE DATABASE flarum_test;' --port 13306
mysql -uroot -proot -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'root';" --port 13306
- name: Install Composer dependencies
run: composer install
working-directory: ${{ inputs.backend_directory }}
- name: Setup Composer tests
run: composer test:setup
working-directory: ${{ inputs.backend_directory }}
env:
DB_PORT: 13306
DB_PASSWORD: root
DB_PREFIX: ${{ matrix.prefix }}
- name: Run Composer tests
run: composer test
working-directory: ${{ inputs.backend_directory }}
env:
COMPOSER_PROCESS_TIMEOUT: 600

220
.github/workflows/REUSABLE_frontend.yml vendored Normal file
View File

@@ -0,0 +1,220 @@
name: Flarum Frontend Jobs
on:
workflow_call:
inputs:
enable_bundlewatch:
description: "Enable Bundlewatch?"
type: boolean
default: false
required: false
enable_prettier:
description: "Enable Prettier?"
type: boolean
default: true
required: false
enable_typescript:
description: "Enable TypeScript?"
type: boolean
default: true
required: false
backend_directory:
description: The directory of the project where backend code is located. This should contain a `composer.json` file, and is generally the root directory of the repo.
type: string
required: false
default: '.'
frontend_directory:
description: The directory of the project where frontend code is located. This should contain a `package.json` file.
type: string
required: false
default: './js'
main_git_branch:
description: The main git branch to use for the workflow.
type: string
required: false
default: main
node_version:
description: The node version to use for the workflow.
type: number
required: false
default: 16
js_package_manager:
description: "Enable TypeScript?"
type: string
default: yarn
required: false
cache_dependency_path:
description: "The path to the cache dependency file."
type: string
required: false
secrets:
bundlewatch_github_token:
description: The GitHub token to use for Bundlewatch.
required: false
env:
COMPOSER_ROOT_VERSION: dev-main
ci_script: ${{ inputs.js_package_manager == 'yarn' && 'yarn install --immutable' || 'npm ci' }}
cache_dependency_path: ${{ inputs.cache_dependency_path || format(inputs.js_package_manager == 'yarn' && '{0}/yarn.lock' || '{0}/package-lock.json', inputs.frontend_directory) }}
jobs:
bundlewatch:
name: Bundlewatch
runs-on: ubuntu-latest
if: inputs.enable_bundlewatch
steps:
- name: Check out code
uses: actions/checkout@v2
- name: Set up Node
uses: actions/setup-node@v2
with:
node-version: ${{ inputs.node_version }}
cache: ${{ inputs.js_package_manager }}
cache-dependency-path: ${{ env.cache_dependency_path }}
- name: Build production assets
uses: flarum/action-build@2
with:
github_token: ${{ secrets.github_token }}
build_script: build
package_manager: ${{ inputs.js_package_manager }}
js_path: ${{ inputs.frontend_directory }}
do_not_commit: true
- name: Check bundle size change
run: node_modules/.bin/bundlewatch --config .bundlewatch.config.json
working-directory: ${{ inputs.frontend_directory }}
env:
BUNDLEWATCH_GITHUB_TOKEN: ${{ secrets.bundlewatch_github_token }}
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
prettier:
name: Prettier
runs-on: ubuntu-latest
if: inputs.enable_prettier
steps:
- name: Check out code
uses: actions/checkout@v2
- name: Set up Node
uses: actions/setup-node@v2
with:
node-version: ${{ inputs.node_version }}
cache: ${{ inputs.js_package_manager }}
cache-dependency-path: ${{ env.cache_dependency_path }}
- name: Install JS dependencies
run: ${{ env.ci_script }}
working-directory: ${{ inputs.frontend_directory }}
- name: Check JS formatting
run: ${{ inputs.js_package_manager }} run format-check
working-directory: ${{ inputs.frontend_directory }}
typecheck:
name: Typecheck
runs-on: ubuntu-latest
if: inputs.enable_typescript
steps:
- name: Check out code
uses: actions/checkout@v2
- name: Set up Node
uses: actions/setup-node@v2
with:
node-version: ${{ inputs.node_version }}
cache: ${{ inputs.js_package_manager }}
cache-dependency-path: ${{ env.cache_dependency_path }}
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.0'
extensions: curl, dom, gd, json, mbstring, openssl, pdo_mysql, tokenizer, zip
tools: composer:v2
# Needed since tsconfig draws typings from vendor folder.
- name: Install Composer dependencies
run: composer install
working-directory: ${{ inputs.backend_directory }}
- name: Install JS dependencies
run: ${{ env.ci_script }}
working-directory: ${{ inputs.frontend_directory }}
- name: Typecheck
run: ${{ inputs.js_package_manager }} run check-typings
working-directory: ${{ inputs.frontend_directory }}
type-coverage:
name: Type Coverage
runs-on: ubuntu-latest
if: inputs.enable_typescript
steps:
- name: Check out code
uses: actions/checkout@v2
- name: Set up Node
uses: actions/setup-node@v2
with:
node-version: ${{ inputs.node_version }}
cache: ${{ inputs.js_package_manager }}
cache-dependency-path: ${{ env.cache_dependency_path }}
- name: Install JS dependencies
run: ${{ env.ci_script }}
working-directory: ${{ inputs.frontend_directory }}
- name: Check type coverage
run: ${{ inputs.js_package_manager }} run check-typings-coverage
working-directory: ${{ inputs.frontend_directory }}
build:
name: Build
runs-on: ubuntu-latest
if: "always() && !contains(needs.*.result, 'failed') && !contains(needs.*.result, 'cancelled')"
needs: [bundlewatch, prettier, typecheck, type-coverage]
steps:
- name: Check out code
uses: actions/checkout@v2
- name: Set up Node
uses: actions/setup-node@v2
with:
node-version: ${{ inputs.node_version }}
cache: ${{ inputs.js_package_manager }}
cache-dependency-path: ${{ env.cache_dependency_path }}
# Our action will install npm/yarn, cd into `${{ inputs.frontend_directory }}`, build dist JS and typings,
# then commit and upload any changes iff we are on the main branch and have just pushed.
- name: Build production JS
if: inputs.enable_typescript
uses: flarum/action-build@2
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
build_script: build
package_manager: ${{ inputs.js_package_manager }}
typings_script: build-typings
js_path: ${{ inputs.frontend_directory }}
do_not_commit: ${{ github.ref != format('refs/heads/{0}', inputs.main_git_branch) || github.event_name != 'push' }}
# Our action will install npm/yarn, cd into `${{ inputs.frontend_directory }}`, build dist JS and typings,
# then commit and upload any changes iff we are on the main branch and have just pushed.
- name: Build production JS
if: "! inputs.enable_typescript"
uses: flarum/action-build@2
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
build_script: build
package_manager: ${{ inputs.js_package_manager }}
js_path: ${{ inputs.frontend_directory }}
do_not_commit: ${{ github.ref != format('refs/heads/{0}', inputs.main_git_branch) || github.event_name != 'push' }}

View File

@@ -2,13 +2,9 @@ name: Akismet PHP
on: [workflow_dispatch, push, pull_request]
# The reusable workflow definitions will be moved to the `flarum/framework` repo soon.
# This will break your current script.
# When this happens, run `flarum-cli audit infra --fix` to update your infrastructure.
jobs:
run:
uses: flarum/.github/.github/workflows/REUSABLE_backend.yml@main
uses: ./.github/workflows/REUSABLE_backend.yml
with:
enable_backend_testing: true

View File

@@ -2,13 +2,9 @@ name: Akismet JS
on: [workflow_dispatch, push, pull_request]
# The reusable workflow definitions will be moved to the `flarum/framework` repo soon.
# This will break your current script.
# When this happens, run `flarum-cli audit infra --fix` to update your infrastructure.
jobs:
run:
uses: flarum/.github/.github/workflows/REUSABLE_frontend.yml@main
uses: ./.github/workflows/REUSABLE_frontend.yml
with:
enable_bundlewatch: false
enable_prettier: true
@@ -17,6 +13,7 @@ jobs:
frontend_directory: ./extensions/akismet/js
backend_directory: ./extensions/akismet
js_package_manager: yarn
cache_dependency_path: ./yarn.lock
main_git_branch: main
secrets:

View File

@@ -2,13 +2,9 @@ name: Approval PHP
on: [workflow_dispatch, push, pull_request]
# The reusable workflow definitions will be moved to the `flarum/framework` repo soon.
# This will break your current script.
# When this happens, run `flarum-cli audit infra --fix` to update your infrastructure.
jobs:
run:
uses: flarum/.github/.github/workflows/REUSABLE_backend.yml@main
uses: ./.github/workflows/REUSABLE_backend.yml
with:
enable_backend_testing: false

View File

@@ -2,13 +2,9 @@ name: Approval JS
on: [workflow_dispatch, push, pull_request]
# The reusable workflow definitions will be moved to the `flarum/framework` repo soon.
# This will break your current script.
# When this happens, run `flarum-cli audit infra --fix` to update your infrastructure.
jobs:
run:
uses: flarum/.github/.github/workflows/REUSABLE_frontend.yml@main
uses: ./.github/workflows/REUSABLE_frontend.yml
with:
enable_bundlewatch: false
enable_prettier: true
@@ -17,6 +13,7 @@ jobs:
frontend_directory: ./extensions/approval/js
backend_directory: ./extensions/approval
js_package_manager: yarn
cache_dependency_path: ./yarn.lock
main_git_branch: main
secrets:

View File

@@ -2,13 +2,9 @@ name: Core PHP
on: [workflow_dispatch, push, pull_request]
# The reusable workflow definitions will be moved to the `flarum/framework` repo soon.
# This will break your current script.
# When this happens, run `flarum-cli audit infra --fix` to update your infrastructure.
jobs:
run:
uses: flarum/.github/.github/workflows/REUSABLE_backend.yml@main
uses: ./.github/workflows/REUSABLE_backend.yml
with:
enable_backend_testing: true

View File

@@ -2,13 +2,9 @@ name: Core JS
on: [workflow_dispatch, push, pull_request]
# The reusable workflow definitions will be moved to the `flarum/framework` repo soon.
# This will break your current script.
# When this happens, run `flarum-cli audit infra --fix` to update your infrastructure.
jobs:
run:
uses: flarum/.github/.github/workflows/REUSABLE_frontend.yml@main
uses: ./.github/workflows/REUSABLE_frontend.yml
with:
enable_bundlewatch: true
enable_prettier: true
@@ -17,6 +13,7 @@ jobs:
frontend_directory: ./framework/core/js
backend_directory: ./framework/core
js_package_manager: yarn
cache_dependency_path: ./yarn.lock
main_git_branch: main
secrets:

View File

@@ -2,13 +2,9 @@ name: Embed PHP
on: [workflow_dispatch, push, pull_request]
# The reusable workflow definitions will be moved to the `flarum/framework` repo soon.
# This will break your current script.
# When this happens, run `flarum-cli audit infra --fix` to update your infrastructure.
jobs:
run:
uses: flarum/.github/.github/workflows/REUSABLE_backend.yml@main
uses: ./.github/workflows/REUSABLE_backend.yml
with:
enable_backend_testing: false

View File

@@ -2,13 +2,9 @@ name: Embed JS
on: [workflow_dispatch, push, pull_request]
# The reusable workflow definitions will be moved to the `flarum/framework` repo soon.
# This will break your current script.
# When this happens, run `flarum-cli audit infra --fix` to update your infrastructure.
jobs:
run:
uses: flarum/.github/.github/workflows/REUSABLE_frontend.yml@main
uses: ./.github/workflows/REUSABLE_frontend.yml
with:
enable_bundlewatch: false
enable_prettier: true
@@ -17,6 +13,7 @@ jobs:
frontend_directory: ./extensions/embed/js
backend_directory: ./extensions/embed
js_package_manager: yarn
cache_dependency_path: ./yarn.lock
main_git_branch: main
secrets:

View File

@@ -2,13 +2,9 @@ name: Emoji PHP
on: [workflow_dispatch, push, pull_request]
# The reusable workflow definitions will be moved to the `flarum/framework` repo soon.
# This will break your current script.
# When this happens, run `flarum-cli audit infra --fix` to update your infrastructure.
jobs:
run:
uses: flarum/.github/.github/workflows/REUSABLE_backend.yml@main
uses: ./.github/workflows/REUSABLE_backend.yml
with:
enable_backend_testing: false

View File

@@ -2,13 +2,9 @@ name: Emoji JS
on: [workflow_dispatch, push, pull_request]
# The reusable workflow definitions will be moved to the `flarum/framework` repo soon.
# This will break your current script.
# When this happens, run `flarum-cli audit infra --fix` to update your infrastructure.
jobs:
run:
uses: flarum/.github/.github/workflows/REUSABLE_frontend.yml@main
uses: ./.github/workflows/REUSABLE_frontend.yml
with:
enable_bundlewatch: false
enable_prettier: true
@@ -17,6 +13,7 @@ jobs:
frontend_directory: ./extensions/emoji/js
backend_directory: ./extensions/emoji
js_package_manager: yarn
cache_dependency_path: ./yarn.lock
main_git_branch: main
secrets:

View File

@@ -2,13 +2,9 @@ name: Flags PHP
on: [workflow_dispatch, push, pull_request]
# The reusable workflow definitions will be moved to the `flarum/framework` repo soon.
# This will break your current script.
# When this happens, run `flarum-cli audit infra --fix` to update your infrastructure.
jobs:
run:
uses: flarum/.github/.github/workflows/REUSABLE_backend.yml@main
uses: ./.github/workflows/REUSABLE_backend.yml
with:
enable_backend_testing: true

View File

@@ -2,13 +2,9 @@ name: Flags JS
on: [workflow_dispatch, push, pull_request]
# The reusable workflow definitions will be moved to the `flarum/framework` repo soon.
# This will break your current script.
# When this happens, run `flarum-cli audit infra --fix` to update your infrastructure.
jobs:
run:
uses: flarum/.github/.github/workflows/REUSABLE_frontend.yml@main
uses: ./.github/workflows/REUSABLE_frontend.yml
with:
enable_bundlewatch: false
enable_prettier: true
@@ -17,6 +13,7 @@ jobs:
frontend_directory: ./extensions/flags/js
backend_directory: ./extensions/flags
js_package_manager: yarn
cache_dependency_path: ./yarn.lock
main_git_branch: main
secrets:

View File

@@ -2,13 +2,9 @@ name: Likes PHP
on: [workflow_dispatch, push, pull_request]
# The reusable workflow definitions will be moved to the `flarum/framework` repo soon.
# This will break your current script.
# When this happens, run `flarum-cli audit infra --fix` to update your infrastructure.
jobs:
run:
uses: flarum/.github/.github/workflows/REUSABLE_backend.yml@main
uses: ./.github/workflows/REUSABLE_backend.yml
with:
enable_backend_testing: false

View File

@@ -2,13 +2,9 @@ name: Likes JS
on: [workflow_dispatch, push, pull_request]
# The reusable workflow definitions will be moved to the `flarum/framework` repo soon.
# This will break your current script.
# When this happens, run `flarum-cli audit infra --fix` to update your infrastructure.
jobs:
run:
uses: flarum/.github/.github/workflows/REUSABLE_frontend.yml@main
uses: ./.github/workflows/REUSABLE_frontend.yml
with:
enable_bundlewatch: false
enable_prettier: true
@@ -17,6 +13,7 @@ jobs:
frontend_directory: ./extensions/likes/js
backend_directory: ./extensions/likes
js_package_manager: yarn
cache_dependency_path: ./yarn.lock
main_git_branch: main
secrets:

View File

@@ -2,13 +2,9 @@ name: Lock PHP
on: [workflow_dispatch, push, pull_request]
# The reusable workflow definitions will be moved to the `flarum/framework` repo soon.
# This will break your current script.
# When this happens, run `flarum-cli audit infra --fix` to update your infrastructure.
jobs:
run:
uses: flarum/.github/.github/workflows/REUSABLE_backend.yml@main
uses: ./.github/workflows/REUSABLE_backend.yml
with:
enable_backend_testing: false

View File

@@ -2,13 +2,9 @@ name: Lock JS
on: [workflow_dispatch, push, pull_request]
# The reusable workflow definitions will be moved to the `flarum/framework` repo soon.
# This will break your current script.
# When this happens, run `flarum-cli audit infra --fix` to update your infrastructure.
jobs:
run:
uses: flarum/.github/.github/workflows/REUSABLE_frontend.yml@main
uses: ./.github/workflows/REUSABLE_frontend.yml
with:
enable_bundlewatch: false
enable_prettier: true
@@ -17,6 +13,7 @@ jobs:
frontend_directory: ./extensions/lock/js
backend_directory: ./extensions/lock
js_package_manager: yarn
cache_dependency_path: ./yarn.lock
main_git_branch: main
secrets:

View File

@@ -2,13 +2,9 @@ name: Markdown PHP
on: [workflow_dispatch, push, pull_request]
# The reusable workflow definitions will be moved to the `flarum/framework` repo soon.
# This will break your current script.
# When this happens, run `flarum-cli audit infra --fix` to update your infrastructure.
jobs:
run:
uses: flarum/.github/.github/workflows/REUSABLE_backend.yml@main
uses: ./.github/workflows/REUSABLE_backend.yml
with:
enable_backend_testing: false

View File

@@ -2,13 +2,9 @@ name: Markdown JS
on: [workflow_dispatch, push, pull_request]
# The reusable workflow definitions will be moved to the `flarum/framework` repo soon.
# This will break your current script.
# When this happens, run `flarum-cli audit infra --fix` to update your infrastructure.
jobs:
run:
uses: flarum/.github/.github/workflows/REUSABLE_frontend.yml@main
uses: ./.github/workflows/REUSABLE_frontend.yml
with:
enable_bundlewatch: false
enable_prettier: true
@@ -17,6 +13,7 @@ jobs:
frontend_directory: ./extensions/markdown/js
backend_directory: ./extensions/markdown
js_package_manager: yarn
cache_dependency_path: ./yarn.lock
main_git_branch: main
secrets:

View File

@@ -2,13 +2,9 @@ name: Mentions PHP
on: [workflow_dispatch, push, pull_request]
# The reusable workflow definitions will be moved to the `flarum/framework` repo soon.
# This will break your current script.
# When this happens, run `flarum-cli audit infra --fix` to update your infrastructure.
jobs:
run:
uses: flarum/.github/.github/workflows/REUSABLE_backend.yml@main
uses: ./.github/workflows/REUSABLE_backend.yml
with:
enable_backend_testing: true

View File

@@ -2,13 +2,9 @@ name: Mentions JS
on: [workflow_dispatch, push, pull_request]
# The reusable workflow definitions will be moved to the `flarum/framework` repo soon.
# This will break your current script.
# When this happens, run `flarum-cli audit infra --fix` to update your infrastructure.
jobs:
run:
uses: flarum/.github/.github/workflows/REUSABLE_frontend.yml@main
uses: ./.github/workflows/REUSABLE_frontend.yml
with:
enable_bundlewatch: false
enable_prettier: true
@@ -17,6 +13,7 @@ jobs:
frontend_directory: ./extensions/mentions/js
backend_directory: ./extensions/mentions
js_package_manager: yarn
cache_dependency_path: ./yarn.lock
main_git_branch: main
secrets:

View File

@@ -2,13 +2,9 @@ name: Nicknames PHP
on: [workflow_dispatch, push, pull_request]
# The reusable workflow definitions will be moved to the `flarum/framework` repo soon.
# This will break your current script.
# When this happens, run `flarum-cli audit infra --fix` to update your infrastructure.
jobs:
run:
uses: flarum/.github/.github/workflows/REUSABLE_backend.yml@main
uses: ./.github/workflows/REUSABLE_backend.yml
with:
enable_backend_testing: true

View File

@@ -2,13 +2,9 @@ name: Nicknames JS
on: [workflow_dispatch, push, pull_request]
# The reusable workflow definitions will be moved to the `flarum/framework` repo soon.
# This will break your current script.
# When this happens, run `flarum-cli audit infra --fix` to update your infrastructure.
jobs:
run:
uses: flarum/.github/.github/workflows/REUSABLE_frontend.yml@main
uses: ./.github/workflows/REUSABLE_frontend.yml
with:
enable_bundlewatch: false
enable_prettier: true
@@ -17,6 +13,7 @@ jobs:
frontend_directory: ./extensions/nicknames/js
backend_directory: ./extensions/nicknames
js_package_manager: yarn
cache_dependency_path: ./yarn.lock
main_git_branch: main
secrets:

View File

@@ -2,13 +2,9 @@ name: Package Manager PHP
on: [workflow_dispatch, push, pull_request]
# The reusable workflow definitions will be moved to the `flarum/framework` repo soon.
# This will break your current script.
# When this happens, run `flarum-cli audit infra --fix` to update your infrastructure.
jobs:
run:
uses: flarum/.github/.github/workflows/REUSABLE_backend.yml@main
uses: ./.github/workflows/REUSABLE_backend.yml
with:
enable_backend_testing: true

View File

@@ -2,13 +2,9 @@ name: Package Manager JS
on: [workflow_dispatch, push, pull_request]
# The reusable workflow definitions will be moved to the `flarum/framework` repo soon.
# This will break your current script.
# When this happens, run `flarum-cli audit infra --fix` to update your infrastructure.
jobs:
run:
uses: flarum/.github/.github/workflows/REUSABLE_frontend.yml@main
uses: ./.github/workflows/REUSABLE_frontend.yml
with:
enable_bundlewatch: false
enable_prettier: true
@@ -17,6 +13,7 @@ jobs:
frontend_directory: ./extensions/package-manager/js
backend_directory: ./extensions/package-manager
js_package_manager: yarn
cache_dependency_path: ./yarn.lock
main_git_branch: main
secrets:

View File

@@ -2,13 +2,9 @@ name: Pusher PHP
on: [workflow_dispatch, push, pull_request]
# The reusable workflow definitions will be moved to the `flarum/framework` repo soon.
# This will break your current script.
# When this happens, run `flarum-cli audit infra --fix` to update your infrastructure.
jobs:
run:
uses: flarum/.github/.github/workflows/REUSABLE_backend.yml@main
uses: ./.github/workflows/REUSABLE_backend.yml
with:
enable_backend_testing: false

View File

@@ -2,13 +2,9 @@ name: Pusher JS
on: [workflow_dispatch, push, pull_request]
# The reusable workflow definitions will be moved to the `flarum/framework` repo soon.
# This will break your current script.
# When this happens, run `flarum-cli audit infra --fix` to update your infrastructure.
jobs:
run:
uses: flarum/.github/.github/workflows/REUSABLE_frontend.yml@main
uses: ./.github/workflows/REUSABLE_frontend.yml
with:
enable_bundlewatch: false
enable_prettier: true
@@ -17,6 +13,7 @@ jobs:
frontend_directory: ./extensions/pusher/js
backend_directory: ./extensions/pusher
js_package_manager: yarn
cache_dependency_path: ./yarn.lock
main_git_branch: main
secrets:

View File

@@ -2,13 +2,9 @@ name: Statistics PHP
on: [workflow_dispatch, push, pull_request]
# The reusable workflow definitions will be moved to the `flarum/framework` repo soon.
# This will break your current script.
# When this happens, run `flarum-cli audit infra --fix` to update your infrastructure.
jobs:
run:
uses: flarum/.github/.github/workflows/REUSABLE_backend.yml@main
uses: ./.github/workflows/REUSABLE_backend.yml
with:
enable_backend_testing: true

View File

@@ -2,13 +2,9 @@ name: Statistics JS
on: [workflow_dispatch, push, pull_request]
# The reusable workflow definitions will be moved to the `flarum/framework` repo soon.
# This will break your current script.
# When this happens, run `flarum-cli audit infra --fix` to update your infrastructure.
jobs:
run:
uses: flarum/.github/.github/workflows/REUSABLE_frontend.yml@main
uses: ./.github/workflows/REUSABLE_frontend.yml
with:
enable_bundlewatch: false
enable_prettier: true
@@ -17,6 +13,7 @@ jobs:
frontend_directory: ./extensions/statistics/js
backend_directory: ./extensions/statistics
js_package_manager: yarn
cache_dependency_path: ./yarn.lock
main_git_branch: main
secrets:

View File

@@ -2,14 +2,10 @@ name: Sticky PHP
on: [workflow_dispatch, push, pull_request]
# The reusable workflow definitions will be moved to the `flarum/framework` repo soon.
# This will break your current script.
# When this happens, run `flarum-cli audit infra --fix` to update your infrastructure.
jobs:
run:
uses: flarum/.github/.github/workflows/REUSABLE_backend.yml@main
uses: ./.github/workflows/REUSABLE_backend.yml
with:
enable_backend_testing: false
enable_backend_testing: true
backend_directory: ./extensions/sticky

View File

@@ -2,13 +2,9 @@ name: Sticky JS
on: [workflow_dispatch, push, pull_request]
# The reusable workflow definitions will be moved to the `flarum/framework` repo soon.
# This will break your current script.
# When this happens, run `flarum-cli audit infra --fix` to update your infrastructure.
jobs:
run:
uses: flarum/.github/.github/workflows/REUSABLE_frontend.yml@main
uses: ./.github/workflows/REUSABLE_frontend.yml
with:
enable_bundlewatch: false
enable_prettier: true
@@ -17,6 +13,7 @@ jobs:
frontend_directory: ./extensions/sticky/js
backend_directory: ./extensions/sticky
js_package_manager: yarn
cache_dependency_path: ./yarn.lock
main_git_branch: main
secrets:

View File

@@ -2,13 +2,9 @@ name: Subscriptions PHP
on: [workflow_dispatch, push, pull_request]
# The reusable workflow definitions will be moved to the `flarum/framework` repo soon.
# This will break your current script.
# When this happens, run `flarum-cli audit infra --fix` to update your infrastructure.
jobs:
run:
uses: flarum/.github/.github/workflows/REUSABLE_backend.yml@main
uses: ./.github/workflows/REUSABLE_backend.yml
with:
enable_backend_testing: false

View File

@@ -2,13 +2,9 @@ name: Subscriptions JS
on: [workflow_dispatch, push, pull_request]
# The reusable workflow definitions will be moved to the `flarum/framework` repo soon.
# This will break your current script.
# When this happens, run `flarum-cli audit infra --fix` to update your infrastructure.
jobs:
run:
uses: flarum/.github/.github/workflows/REUSABLE_frontend.yml@main
uses: ./.github/workflows/REUSABLE_frontend.yml
with:
enable_bundlewatch: false
enable_prettier: true
@@ -17,6 +13,7 @@ jobs:
frontend_directory: ./extensions/subscriptions/js
backend_directory: ./extensions/subscriptions
js_package_manager: yarn
cache_dependency_path: ./yarn.lock
main_git_branch: main
secrets:

View File

@@ -2,13 +2,9 @@ name: Suspend PHP
on: [workflow_dispatch, push, pull_request]
# The reusable workflow definitions will be moved to the `flarum/framework` repo soon.
# This will break your current script.
# When this happens, run `flarum-cli audit infra --fix` to update your infrastructure.
jobs:
run:
uses: flarum/.github/.github/workflows/REUSABLE_backend.yml@main
uses: ./.github/workflows/REUSABLE_backend.yml
with:
enable_backend_testing: false

View File

@@ -2,13 +2,9 @@ name: Suspend JS
on: [workflow_dispatch, push, pull_request]
# The reusable workflow definitions will be moved to the `flarum/framework` repo soon.
# This will break your current script.
# When this happens, run `flarum-cli audit infra --fix` to update your infrastructure.
jobs:
run:
uses: flarum/.github/.github/workflows/REUSABLE_frontend.yml@main
uses: ./.github/workflows/REUSABLE_frontend.yml
with:
enable_bundlewatch: false
enable_prettier: true
@@ -17,6 +13,7 @@ jobs:
frontend_directory: ./extensions/suspend/js
backend_directory: ./extensions/suspend
js_package_manager: yarn
cache_dependency_path: ./yarn.lock
main_git_branch: main
secrets:

View File

@@ -2,13 +2,9 @@ name: Tags PHP
on: [workflow_dispatch, push, pull_request]
# The reusable workflow definitions will be moved to the `flarum/framework` repo soon.
# This will break your current script.
# When this happens, run `flarum-cli audit infra --fix` to update your infrastructure.
jobs:
run:
uses: flarum/.github/.github/workflows/REUSABLE_backend.yml@main
uses: ./.github/workflows/REUSABLE_backend.yml
with:
enable_backend_testing: true

View File

@@ -2,21 +2,18 @@ name: Tags JS
on: [workflow_dispatch, push, pull_request]
# The reusable workflow definitions will be moved to the `flarum/framework` repo soon.
# This will break your current script.
# When this happens, run `flarum-cli audit infra --fix` to update your infrastructure.
jobs:
run:
uses: flarum/.github/.github/workflows/REUSABLE_frontend.yml@main
uses: ./.github/workflows/REUSABLE_frontend.yml
with:
enable_bundlewatch: false
enable_prettier: false
enable_prettier: true
enable_typescript: true
frontend_directory: ./extensions/tags/js
backend_directory: ./extensions/tags
js_package_manager: yarn
cache_dependency_path: ./yarn.lock
main_git_branch: main
secrets:
bundlewatch_github_token: ${{ secrets.BUNDLEWATCH_GITHUB_TOKEN }}

View File

@@ -86,7 +86,6 @@
"require": {
"ext-json": "*",
"php": ">=7.3",
"axy/sourcemap": "^0.1.4",
"components/font-awesome": "^5.14.0",
"composer/composer": "^2.0",
"dflydev/fig-cookies": "^3.0.0",
@@ -127,6 +126,8 @@
"psr/http-server-middleware": "^1.0",
"pusher/pusher-php-server": "^2.2",
"s9e/text-formatter": "^2.3.6",
"sycho/json-api": "^0.5.0",
"sycho/sourcemap": "^2.0.0",
"symfony/config": "^5.2.2",
"symfony/console": "^5.2.2",
"symfony/event-dispatcher": "^5.2.2",
@@ -134,7 +135,6 @@
"symfony/polyfill-intl-messageformatter": "^1.22.0",
"symfony/translation": "^5.1.5",
"symfony/yaml": "^5.2.2",
"tobscure/json-api": "^0.3.0",
"wikimedia/less.php": "^3.0"
},
"require-dev": {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -23,6 +23,6 @@ export default class PostMentionedNotification extends Notification {
}
excerpt() {
return truncate(this.attrs.notification.subject().contentPlain(), 200);
return truncate(this.attrs.notification.subject().contentPlain() || '', 200);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,30 @@
<?php
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;
return [
'up' => function (Builder $schema) {
$schema->table('post_mentions_post', function (Blueprint $table) {
$table->timestamp('created_at')->nullable();
});
// do this manually because dbal doesn't recognize timestamp columns
$connection = $schema->getConnection();
$prefix = $connection->getTablePrefix();
$connection->statement("ALTER TABLE `${prefix}post_mentions_post` MODIFY created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP");
},
'down' => function (Builder $schema) {
$schema->table('post_mentions_post', function (Blueprint $table) {
$table->dropColumn('created_at');
});
}
];

View File

@@ -0,0 +1,30 @@
<?php
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;
return [
'up' => function (Builder $schema) {
$schema->table('post_mentions_user', function (Blueprint $table) {
$table->timestamp('created_at')->nullable();
});
// do this manually because dbal doesn't recognize timestamp columns
$connection = $schema->getConnection();
$prefix = $connection->getTablePrefix();
$connection->statement("ALTER TABLE `${prefix}post_mentions_user` MODIFY created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP");
},
'down' => function (Builder $schema) {
$schema->table('post_mentions_user', function (Blueprint $table) {
$table->dropColumn('created_at');
});
}
];

View File

@@ -14,7 +14,7 @@ use Flarum\Testing\integration\RetrievesAuthorizedUsers;
use Flarum\Testing\integration\TestCase;
use Illuminate\Support\Arr;
class ListTest extends TestCase
class ListPostsTest extends TestCase
{
use RetrievesAuthorizedUsers;
@@ -85,4 +85,26 @@ class ListTest extends TestCase
$ids = Arr::pluck($data, 'id');
$this->assertEqualsCanonicalizing(['4'], $ids, 'IDs do not match');
}
/**
* @test
*/
public function mentioned_filter_works_with_sort()
{
$response = $this->send(
$this->request('GET', '/api/posts')
->withQueryParams([
'filter' => ['mentioned' => 1],
'sort' => '-createdAt'
])
);
$data = json_decode($response->getBody()->getContents(), true)['data'];
$this->assertEquals(200, $response->getStatusCode());
// Order-independent comparison
$ids = Arr::pluck($data, 'id');
$this->assertEqualsCanonicalizing(['2', '3'], $ids, 'IDs do not match');
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -51,7 +51,7 @@
"prettier": true,
"typescript": false,
"bundlewatch": false,
"backendTesting": false,
"backendTesting": true,
"editorConfig": true,
"styleci": true
}
@@ -64,5 +64,28 @@
}
],
"minimum-stability": "dev",
"prefer-stable": true
"prefer-stable": true,
"autoload-dev": {
"psr-4": {
"Flarum\\Sticky\\Tests\\": "tests/"
}
},
"scripts": {
"test": [
"@test:unit",
"@test:integration"
],
"test:unit": "phpunit -c tests/phpunit.unit.xml",
"test:integration": "phpunit -c tests/phpunit.integration.xml",
"test:setup": "@php tests/integration/setup.php"
},
"scripts-descriptions": {
"test": "Runs all tests.",
"test:unit": "Runs all unit tests.",
"test:integration": "Runs all integration tests.",
"test:setup": "Sets up a database for use with integration tests. Execute this only once."
},
"require-dev": {
"flarum/testing": "^1.0.0"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -60,7 +60,6 @@ class PinStickiedDiscussionsToTop
$query->orderByRaw('is_sticky and not exists ('.$read->toSql().') and last_posted_at > ? desc')
->addBinding(array_merge($read->getBindings(), [$filterState->getActor()->read_time ?: 0]), 'union');
$query->unionOrders = array_merge($query->unionOrders, $query->orders);
$query->unionLimit = $query->limit;
$query->unionOffset = $query->offset;

View File

View File

@@ -0,0 +1,90 @@
<?php
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\Sticky\tests\integration\api;
use Carbon\Carbon;
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
use Flarum\Testing\integration\TestCase;
use Illuminate\Support\Arr;
class ListDiscussionsTest extends TestCase
{
use RetrievesAuthorizedUsers;
protected function setUp(): void
{
parent::setUp();
$this->extension('flarum-sticky');
$this->prepareDatabase([
'users' => [
['id' => 1, 'username' => 'Muralf', 'email' => 'muralf@machine.local', 'is_email_confirmed' => 1],
$this->normalUser()
],
'discussions' => [
['id' => 1, 'title' => __CLASS__, 'created_at' => Carbon::now(), 'last_posted_at' => Carbon::now(), 'user_id' => 1, 'first_post_id' => 1, 'comment_count' => 1, 'is_sticky' => true],
['id' => 2, 'title' => __CLASS__, 'created_at' => Carbon::now()->addMinute(), 'last_posted_at' => Carbon::now(), 'user_id' => 1, 'first_post_id' => 1, 'comment_count' => 1, 'is_sticky' => false],
['id' => 3, 'title' => __CLASS__, 'created_at' => Carbon::now()->addMinutes(2), 'last_posted_at' => Carbon::now(), 'user_id' => 1, 'first_post_id' => 1, 'comment_count' => 1, 'is_sticky' => true],
['id' => 4, 'title' => __CLASS__, 'created_at' => Carbon::now()->addMinutes(3), 'last_posted_at' => Carbon::now(), 'user_id' => 1, 'first_post_id' => 1, 'comment_count' => 1, 'is_sticky' => false],
],
'discussion_user' => [
['discussion_id' => 1, 'user_id' => 1, 'last_read_post_number' => 1],
['discussion_id' => 3, 'user_id' => 1, 'last_read_post_number' => 1],
]
]);
}
/** @test */
public function list_discussions_shows_sticky_first_as_guest()
{
$response = $this->send(
$this->request('GET', '/api/discussions')
);
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode($response->getBody()->getContents(), true);
$this->assertEqualsCanonicalizing([1, 3, 2, 4], Arr::pluck($data['data'], 'id'));
}
/** @test */
public function list_discussions_shows_sticky_unread_first_as_user()
{
$response = $this->send(
$this->request('GET', '/api/discussions', [
'authenticatedAs' => 2
])
);
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode($response->getBody()->getContents(), true);
$this->assertEqualsCanonicalizing([1, 3, 2, 4], Arr::pluck($data['data'], 'id'));
}
/** @test */
public function list_discussions_shows_normal_order_when_all_read_as_user()
{
$response = $this->send(
$this->request('GET', '/api/discussions', [
'authenticatedAs' => 1
])
);
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode($response->getBody()->getContents(), true);
$this->assertEqualsCanonicalizing([1, 2, 3, 4], Arr::pluck($data['data'], 'id'));
}
}

View File

@@ -0,0 +1,16 @@
<?php
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
use Flarum\Testing\integration\Setup\SetupScript;
require __DIR__.'/../../vendor/autoload.php';
$setup = new SetupScript();
$setup->run();

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="true"
stopOnFailure="false"
>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">../src/</directory>
</include>
</coverage>
<testsuites>
<testsuite name="Flarum Integration Tests">
<directory suffix="Test.php">./integration</directory>
<exclude>./integration/tmp</exclude>
</testsuite>
</testsuites>
</phpunit>

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">../src/</directory>
</include>
</coverage>
<testsuites>
<testsuite name="Flarum Unit Tests">
<directory suffix="Test.php">./unit</directory>
</testsuite>
</testsuites>
<listeners>
<listener class="\Mockery\Adapter\Phpunit\TestListener" />
</listeners>
</phpunit>

View File

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -7,11 +7,12 @@ export default function () {
if (app.session.user) {
const message = app.session.user.suspendMessage();
const until = app.session.user.suspendedUntil();
const isSuspended = message && until && new Date() < until;
const alreadyDisplayed = localStorage.getItem(localStorageKey()) === until?.getTime().toString();
if (message && !alreadyDisplayed) {
if (isSuspended && !alreadyDisplayed) {
app.modal.show(SuspensionInfoModal, { message, until });
} else if (!until && localStorage.getItem(localStorageKey())) {
} else if (localStorage.getItem(localStorageKey())) {
localStorage.removeItem(localStorageKey());
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
import type Tag from "../common/models/Tag";
import type TagListState from "../forum/states/TagListState";
import type Tag from '../common/models/Tag';
import type TagListState from '../forum/states/TagListState';
declare module 'flarum/forum/routes' {
export interface ForumRoutes {

View File

@@ -1,2 +1,2 @@
import Tag from "../models/Tag";
import Tag from '../models/Tag';
export default function sortTags(tags: Tag[]): Tag[];

View File

@@ -1,4 +1,4 @@
import type Tag from "../../common/models/Tag";
import type Tag from '../../common/models/Tag';
export default class TagListState {
loadedIncludes: Set<unknown>;
load(includes?: string[]): Promise<Tag[]>;

2
extensions/tags/js/dist/admin.js generated vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
extensions/tags/js/dist/forum.js generated vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -2,6 +2,7 @@
"private": true,
"name": "@flarum/tags",
"version": "0.0.0",
"prettier": "@flarum/prettier-config",
"dependencies": {
"sortablejs": "^1.14.0"
},
@@ -13,14 +14,17 @@
"build-typings": "yarn run clean-typings && ([ -e src/@types ] && cp -r src/@types dist-typings/@types || true) && tsc && yarn run post-build-typings",
"post-build-typings": "find dist-typings -type f -name '*.d.ts' -print0 | xargs -0 sed -i 's,../src/@types,@types,g'",
"check-typings": "tsc --noEmit --emitDeclarationOnly false",
"check-typings-coverage": "typescript-coverage-report"
"check-typings-coverage": "typescript-coverage-report",
"format": "prettier --write src",
"format-check": "prettier --check src"
},
"devDependencies": {
"flarum-webpack-config": "^2.0.0",
"webpack": "^5.65.0",
"webpack-cli": "^4.9.1",
"flarum-tsconfig": "^1.0.2",
"flarum-webpack-config": "^2.0.0",
"prettier": "^2.7.1",
"typescript": "^4.5.4",
"typescript-coverage-report": "^0.6.1"
"typescript-coverage-report": "^0.6.1",
"webpack": "^5.65.0",
"webpack-cli": "^4.9.1"
}
}

View File

@@ -1,5 +1,5 @@
import type Tag from "../common/models/Tag";
import type TagListState from "../forum/states/TagListState";
import type Tag from '../common/models/Tag';
import type TagListState from '../forum/states/TagListState';
declare module 'flarum/forum/routes' {
export interface ForumRoutes {

View File

@@ -2,26 +2,30 @@ import { extend } from 'flarum/common/extend';
import PermissionGrid from 'flarum/admin/components/PermissionGrid';
import SettingDropdown from 'flarum/admin/components/SettingDropdown';
export default function() {
extend(PermissionGrid.prototype, 'startItems', items => {
items.add('allowTagChange', {
icon: 'fas fa-tag',
label: app.translator.trans('flarum-tags.admin.permissions.allow_edit_tags_label'),
setting: () => {
const minutes = parseInt(app.data.settings.allow_tag_change, 10);
export default function () {
extend(PermissionGrid.prototype, 'startItems', (items) => {
items.add(
'allowTagChange',
{
icon: 'fas fa-tag',
label: app.translator.trans('flarum-tags.admin.permissions.allow_edit_tags_label'),
setting: () => {
const minutes = parseInt(app.data.settings.allow_tag_change, 10);
return SettingDropdown.component({
defaultLabel: minutes
? app.translator.trans('core.admin.permissions_controls.allow_some_minutes_button', {count: minutes})
: app.translator.trans('core.admin.permissions_controls.allow_indefinitely_button'),
key: 'allow_tag_change',
options: [
{value: '-1', label: app.translator.trans('core.admin.permissions_controls.allow_indefinitely_button')},
{value: '10', label: app.translator.trans('core.admin.permissions_controls.allow_ten_minutes_button')},
{value: 'reply', label: app.translator.trans('core.admin.permissions_controls.allow_until_reply_button')}
]
});
}
}, 90);
return SettingDropdown.component({
defaultLabel: minutes
? app.translator.trans('core.admin.permissions_controls.allow_some_minutes_button', { count: minutes })
: app.translator.trans('core.admin.permissions_controls.allow_indefinitely_button'),
key: 'allow_tag_change',
options: [
{ value: '-1', label: app.translator.trans('core.admin.permissions_controls.allow_indefinitely_button') },
{ value: '10', label: app.translator.trans('core.admin.permissions_controls.allow_ten_minutes_button') },
{ value: 'reply', label: app.translator.trans('core.admin.permissions_controls.allow_until_reply_button') },
],
});
},
},
90
);
});
}

View File

@@ -1,14 +1,22 @@
export default function () {
app.extensionData
.for('flarum-tags')
.registerPermission({
icon: 'fas fa-tag',
label: app.translator.trans('flarum-tags.admin.permissions.tag_discussions_label'),
permission: 'discussion.tag',
}, 'moderate', 95)
.registerPermission({
icon: 'fas fa-tags',
label: app.translator.trans('flarum-tags.admin.permissions.bypass_tag_counts_label'),
permission: 'bypassTagCounts',
}, 'start', 89);
.registerPermission(
{
icon: 'fas fa-tag',
label: app.translator.trans('flarum-tags.admin.permissions.tag_discussions_label'),
permission: 'discussion.tag',
},
'moderate',
95
)
.registerPermission(
{
icon: 'fas fa-tags',
label: app.translator.trans('flarum-tags.admin.permissions.bypass_tag_counts_label'),
permission: 'bypassTagCounts',
},
'start',
89
);
}

View File

@@ -1,11 +1,11 @@
import { extend } from 'flarum/common/extend';
import BasicsPage from 'flarum/admin/components/BasicsPage';
export default function() {
extend(BasicsPage.prototype, 'homePageItems', items => {
export default function () {
extend(BasicsPage.prototype, 'homePageItems', (items) => {
items.add('tags', {
path: '/tags',
label: app.translator.trans('flarum-tags.admin.basics.tags_label')
label: app.translator.trans('flarum-tags.admin.basics.tags_label'),
});
});
}

View File

@@ -11,10 +11,10 @@ import tagIcon from '../common/helpers/tagIcon';
import sortTags from '../common/utils/sortTags';
import Tag from '../common/models/Tag';
export default function() {
export default function () {
extend(PermissionGrid.prototype, 'oninit', function () {
this.loading = true;
})
});
extend(PermissionGrid.prototype, 'oncreate', function () {
app.store.find<Tag[]>('tags', {}).then(() => {
@@ -30,7 +30,7 @@ export default function() {
}
return original(vnode);
})
});
override(app, 'getRequiredPermissions', (original, permission) => {
const tagPrefix = permission.match(/^tag\d+\./);
@@ -40,45 +40,60 @@ export default function() {
const required = original(globalPermission);
return required.map(required => tagPrefix[0] + required);
return required.map((required) => tagPrefix[0] + required);
}
return original(permission);
});
extend(PermissionGrid.prototype, 'scopeItems', items => {
extend(PermissionGrid.prototype, 'scopeItems', (items) => {
sortTags(app.store.all('tags'))
.filter(tag => tag.isRestricted())
.forEach(tag => items.add('tag' + tag.id(), {
label: tagLabel(tag),
onremove: () => tag.save({isRestricted: false}),
render: item => {
if ('setting' in item) return '';
.filter((tag) => tag.isRestricted())
.forEach((tag) =>
items.add('tag' + tag.id(), {
label: tagLabel(tag),
onremove: () => tag.save({ isRestricted: false }),
render: (item) => {
if ('setting' in item) return '';
if (item.permission === 'viewForum'
|| item.permission === 'startDiscussion'
|| (item.permission && item.permission.indexOf('discussion.') === 0 && item.tagScoped !== false)
|| item.tagScoped) {
return PermissionDropdown.component({
permission: 'tag' + tag.id() + '.' + item.permission,
allowGuest: item.allowGuest
});
}
if (
item.permission === 'viewForum' ||
item.permission === 'startDiscussion' ||
(item.permission && item.permission.indexOf('discussion.') === 0 && item.tagScoped !== false) ||
item.tagScoped
) {
return PermissionDropdown.component({
permission: 'tag' + tag.id() + '.' + item.permission,
allowGuest: item.allowGuest,
});
}
return '';
}
}));
return '';
},
})
);
});
extend(PermissionGrid.prototype, 'scopeControlItems', items => {
const tags = sortTags(app.store.all<Tag>('tags').filter(tag => !tag.isRestricted()));
extend(PermissionGrid.prototype, 'scopeControlItems', (items) => {
const tags = sortTags(app.store.all<Tag>('tags').filter((tag) => !tag.isRestricted()));
if (tags.length) {
items.add('tag', <Dropdown className='Dropdown--restrictByTag' buttonClassName='Button Button--text' label={app.translator.trans('flarum-tags.admin.permissions.restrict_by_tag_heading')} icon='fas fa-plus' caretIcon={null}>
{tags.map(tag => <Button icon={true} onclick={() => tag.save({ isRestricted: true })}>
{[tagIcon(tag, { className: 'Button-icon' }), ' ', tag.name()]}
</Button>)}
</Dropdown>);
items.add(
'tag',
<Dropdown
className="Dropdown--restrictByTag"
buttonClassName="Button Button--text"
label={app.translator.trans('flarum-tags.admin.permissions.restrict_by_tag_heading')}
icon="fas fa-plus"
caretIcon={null}
>
{tags.map((tag) => (
<Button icon={true} onclick={() => tag.save({ isRestricted: true })}>
{[tagIcon(tag, { className: 'Button-icon' }), ' ', tag.name()]}
</Button>
))}
</Dropdown>
);
}
});
}

View File

@@ -59,9 +59,7 @@ export default class EditTagModal extends Modal<EditTagModalAttrs> {
content() {
return (
<div className="Modal-body">
<div className="Form">
{this.fields().toArray()}
</div>
<div className="Form">{this.fields().toArray()}</div>
</div>
);
}
@@ -69,59 +67,97 @@ export default class EditTagModal extends Modal<EditTagModalAttrs> {
fields() {
const items = new ItemList();
items.add('name', <div className="Form-group">
<label>{app.translator.trans('flarum-tags.admin.edit_tag.name_label')}</label>
<input className="FormControl" placeholder={app.translator.trans('flarum-tags.admin.edit_tag.name_placeholder')} value={this.name()} oninput={(e: InputEvent) => {
const target = e.target as HTMLInputElement;
this.name(target.value);
this.slug(slug(target.value));
}} />
</div>, 50);
items.add(
'name',
<div className="Form-group">
<label>{app.translator.trans('flarum-tags.admin.edit_tag.name_label')}</label>
<input
className="FormControl"
placeholder={app.translator.trans('flarum-tags.admin.edit_tag.name_placeholder')}
value={this.name()}
oninput={(e: InputEvent) => {
const target = e.target as HTMLInputElement;
this.name(target.value);
this.slug(slug(target.value));
}}
/>
</div>,
50
);
items.add('slug', <div className="Form-group">
<label>{app.translator.trans('flarum-tags.admin.edit_tag.slug_label')}</label>
<input className="FormControl" bidi={this.slug} />
</div>, 40);
items.add(
'slug',
<div className="Form-group">
<label>{app.translator.trans('flarum-tags.admin.edit_tag.slug_label')}</label>
<input className="FormControl" bidi={this.slug} />
</div>,
40
);
items.add('description', <div className="Form-group">
<label>{app.translator.trans('flarum-tags.admin.edit_tag.description_label')}</label>
<textarea className="FormControl" bidi={this.description} />
</div>, 30);
items.add(
'description',
<div className="Form-group">
<label>{app.translator.trans('flarum-tags.admin.edit_tag.description_label')}</label>
<textarea className="FormControl" bidi={this.description} />
</div>,
30
);
items.add('color', <div className="Form-group">
<label>{app.translator.trans('flarum-tags.admin.edit_tag.color_label')}</label>
<ColorPreviewInput className="FormControl" placeholder="#aaaaaa" bidi={this.color} />
</div>, 20);
items.add(
'color',
<div className="Form-group">
<label>{app.translator.trans('flarum-tags.admin.edit_tag.color_label')}</label>
<ColorPreviewInput className="FormControl" placeholder="#aaaaaa" bidi={this.color} />
</div>,
20
);
items.add('icon', <div className="Form-group">
<label>{app.translator.trans('flarum-tags.admin.edit_tag.icon_label')}</label>
<div className="helpText">
{app.translator.trans('flarum-tags.admin.edit_tag.icon_text', { a: <a href="https://fontawesome.com/icons?m=free" tabindex="-1" /> })}
</div>
<input className="FormControl" placeholder="fas fa-bolt" bidi={this.icon} />
</div>, 10);
items.add(
'icon',
<div className="Form-group">
<label>{app.translator.trans('flarum-tags.admin.edit_tag.icon_label')}</label>
<div className="helpText">
{app.translator.trans('flarum-tags.admin.edit_tag.icon_text', { a: <a href="https://fontawesome.com/icons?m=free" tabindex="-1" /> })}
</div>
<input className="FormControl" placeholder="fas fa-bolt" bidi={this.icon} />
</div>,
10
);
items.add('hidden', <div className="Form-group">
<div>
<label className="checkbox">
<input type="checkbox" bidi={this.isHidden} />
{app.translator.trans('flarum-tags.admin.edit_tag.hide_label')}
</label>
</div>
</div>, 10);
items.add(
'hidden',
<div className="Form-group">
<div>
<label className="checkbox">
<input type="checkbox" bidi={this.isHidden} />
{app.translator.trans('flarum-tags.admin.edit_tag.hide_label')}
</label>
</div>
</div>,
10
);
items.add('submit', <div className="Form-group">
{Button.component({
type: 'submit',
className: 'Button Button--primary EditTagModal-save',
loading: this.loading,
}, app.translator.trans('flarum-tags.admin.edit_tag.submit_button'))}
{this.tag.exists ? (
<button type="button" className="Button EditTagModal-delete" onclick={this.delete.bind(this)}>
{app.translator.trans('flarum-tags.admin.edit_tag.delete_tag_button')}
</button>
) : ''}
</div>, -10);
items.add(
'submit',
<div className="Form-group">
{Button.component(
{
type: 'submit',
className: 'Button Button--primary EditTagModal-save',
loading: this.loading,
},
app.translator.trans('flarum-tags.admin.edit_tag.submit_button')
)}
{this.tag.exists ? (
<button type="button" className="Button EditTagModal-delete" onclick={this.delete.bind(this)}>
{app.translator.trans('flarum-tags.admin.edit_tag.delete_tag_button')}
</button>
) : (
''
)}
</div>,
-10
);
return items;
}
@@ -147,20 +183,22 @@ export default class EditTagModal extends Modal<EditTagModalAttrs> {
// This is done for better error visibility on smaller screen heights.
this.tag.save(this.submitData()).then(
() => this.hide(),
() => this.loading = false
() => (this.loading = false)
);
}
delete() {
if (confirm(extractText(app.translator.trans('flarum-tags.admin.edit_tag.delete_tag_confirmation')))) {
const children = app.store.all<Tag>('tags').filter(tag => tag.parent() === this.tag);
const children = app.store.all<Tag>('tags').filter((tag) => tag.parent() === this.tag);
this.tag.delete().then(() => {
children.forEach(tag => tag.pushData({
attributes: { isChild: false },
// @deprecated. Temporary hack for type safety, remove before v1.3.
relationships: { parent: null as any as [] }
}));
children.forEach((tag) =>
tag.pushData({
attributes: { isChild: false },
// @deprecated. Temporary hack for type safety, remove before v1.3.
relationships: { parent: null as any as [] },
})
);
m.redraw();
});

View File

@@ -1,5 +1,6 @@
import sortable from 'sortablejs';
import app from 'flarum/admin/app';
import ExtensionPage from 'flarum/admin/components/ExtensionPage';
import Button from 'flarum/common/components/Button';
import LoadingIndicator from 'flarum/common/components/LoadingIndicator';
@@ -18,16 +19,18 @@ function tagItem(tag) {
{Button.component({
className: 'Button Button--link',
icon: 'fas fa-pencil-alt',
onclick: () => app.modal.show(EditTagModal, { model: tag })
onclick: () => app.modal.show(EditTagModal, { model: tag }),
})}
</div>
{!tag.isChild() && tag.position() !== null ? (
<ol className="TagListItem-children TagList">
{sortTags(app.store.all('tags'))
.filter(child => child.parent() === tag)
.filter((child) => child.parent() === tag)
.map(tagItem)}
</ol>
) : ''}
) : (
''
)}
</li>
);
}
@@ -62,81 +65,78 @@ export default class TagsPage extends ExtensionPage {
const minSecondaryTags = this.setting('flarum-tags.min_secondary_tags', 0);
const maxSecondaryTags = this.setting('flarum-tags.max_secondary_tags', 0);
const tags = sortTags(app.store.all('tags').filter(tag => !tag.parent()));
const tags = sortTags(app.store.all('tags').filter((tag) => !tag.parent()));
return (
<div className="TagsContent">
<div className="TagsContent-list">
<div className="container" key={this.forcedRefreshKey} oncreate={this.onListOnCreate.bind(this)}><div className="SettingsGroups">
<div className="TagGroup">
<label>{app.translator.trans('flarum-tags.admin.tags.primary_heading')}</label>
<ol className="TagList TagList--primary">
{tags
.filter(tag => tag.position() !== null && !tag.isChild())
.map(tagItem)}
</ol>
{Button.component(
{
className: 'Button TagList-button',
icon: 'fas fa-plus',
onclick: () => app.modal.show(EditTagModal, { primary: true }),
},
app.translator.trans('flarum-tags.admin.tags.create_primary_tag_button')
)}
</div>
<div className="container" key={this.forcedRefreshKey} oncreate={this.onListOnCreate.bind(this)}>
<div className="SettingsGroups">
<div className="TagGroup">
<label>{app.translator.trans('flarum-tags.admin.tags.primary_heading')}</label>
<ol className="TagList TagList--primary">{tags.filter((tag) => tag.position() !== null && !tag.isChild()).map(tagItem)}</ol>
{Button.component(
{
className: 'Button TagList-button',
icon: 'fas fa-plus',
onclick: () => app.modal.show(EditTagModal, { primary: true }),
},
app.translator.trans('flarum-tags.admin.tags.create_primary_tag_button')
)}
</div>
<div className="TagGroup TagGroup--secondary">
<label>{app.translator.trans('flarum-tags.admin.tags.secondary_heading')}</label>
<ul className="TagList">
{tags
.filter(tag => tag.position() === null)
.sort((a, b) => a.name().localeCompare(b.name()))
.map(tagItem)}
</ul>
{Button.component(
{
className: 'Button TagList-button',
icon: 'fas fa-plus',
onclick: () => app.modal.show(EditTagModal, { primary: false }),
},
app.translator.trans('flarum-tags.admin.tags.create_secondary_tag_button')
)}
</div>
<div className="Form">
<label>{app.translator.trans('flarum-tags.admin.tags.settings_heading')}</label>
<div className="Form-group">
<label>{app.translator.trans('flarum-tags.admin.tag_settings.required_primary_heading')}</label>
<div className="helpText">{app.translator.trans('flarum-tags.admin.tag_settings.required_primary_text')}</div>
<div className="TagSettings-rangeInput">
<input
className="FormControl"
type="number"
min="0"
value={minPrimaryTags()}
oninput={withAttr('value', this.setMinTags.bind(this, minPrimaryTags, maxPrimaryTags))}
/>
{app.translator.trans('flarum-tags.admin.tag_settings.range_separator_text')}
<input className="FormControl" type="number" min={minPrimaryTags()} bidi={maxPrimaryTags} />
</div>
<div className="TagGroup TagGroup--secondary">
<label>{app.translator.trans('flarum-tags.admin.tags.secondary_heading')}</label>
<ul className="TagList">
{tags
.filter((tag) => tag.position() === null)
.sort((a, b) => a.name().localeCompare(b.name()))
.map(tagItem)}
</ul>
{Button.component(
{
className: 'Button TagList-button',
icon: 'fas fa-plus',
onclick: () => app.modal.show(EditTagModal, { primary: false }),
},
app.translator.trans('flarum-tags.admin.tags.create_secondary_tag_button')
)}
</div>
<div className="Form-group">
<label>{app.translator.trans('flarum-tags.admin.tag_settings.required_secondary_heading')}</label>
<div className="helpText">{app.translator.trans('flarum-tags.admin.tag_settings.required_secondary_text')}</div>
<div className="TagSettings-rangeInput">
<input
className="FormControl"
type="number"
min="0"
value={minSecondaryTags()}
oninput={withAttr('value', this.setMinTags.bind(this, minSecondaryTags, maxSecondaryTags))}
/>
{app.translator.trans('flarum-tags.admin.tag_settings.range_separator_text')}
<input className="FormControl" type="number" min={minSecondaryTags()} bidi={maxSecondaryTags} />
<div className="Form">
<label>{app.translator.trans('flarum-tags.admin.tags.settings_heading')}</label>
<div className="Form-group">
<label>{app.translator.trans('flarum-tags.admin.tag_settings.required_primary_heading')}</label>
<div className="helpText">{app.translator.trans('flarum-tags.admin.tag_settings.required_primary_text')}</div>
<div className="TagSettings-rangeInput">
<input
className="FormControl"
type="number"
min="0"
value={minPrimaryTags()}
oninput={withAttr('value', this.setMinTags.bind(this, minPrimaryTags, maxPrimaryTags))}
/>
{app.translator.trans('flarum-tags.admin.tag_settings.range_separator_text')}
<input className="FormControl" type="number" min={minPrimaryTags()} bidi={maxPrimaryTags} />
</div>
</div>
<div className="Form-group">
<label>{app.translator.trans('flarum-tags.admin.tag_settings.required_secondary_heading')}</label>
<div className="helpText">{app.translator.trans('flarum-tags.admin.tag_settings.required_secondary_text')}</div>
<div className="TagSettings-rangeInput">
<input
className="FormControl"
type="number"
min="0"
value={minSecondaryTags()}
oninput={withAttr('value', this.setMinTags.bind(this, minSecondaryTags, maxSecondaryTags))}
/>
{app.translator.trans('flarum-tags.admin.tag_settings.range_separator_text')}
<input className="FormControl" type="number" min={minSecondaryTags()} bidi={maxSecondaryTags} />
</div>
</div>
<div className="Form-group">{this.submitButton()}</div>
</div>
<div className="Form-group">{this.submitButton()}</div>
</div>
</div>
<div className="TagsContent-footer">
<p>{app.translator.trans('flarum-tags.admin.tags.about_tags_text')}</p>
</div>
@@ -147,19 +147,21 @@ export default class TagsPage extends ExtensionPage {
}
onListOnCreate(vnode) {
this.$('.TagList').get().map(e => {
sortable.create(e, {
group: 'tags',
delay: 50,
delayOnTouchOnly: true,
touchStartThreshold: 5,
animation: 150,
swapThreshold: 0.65,
dragClass: 'sortable-dragging',
ghostClass: 'sortable-placeholder',
onSort: (e) => this.onSortUpdate(e)
})
});
this.$('.TagList')
.get()
.map((e) => {
sortable.create(e, {
group: 'tags',
delay: 50,
delayOnTouchOnly: true,
touchStartThreshold: 5,
animation: 150,
swapThreshold: 0.65,
dragClass: 'sortable-dragging',
ghostClass: 'sortable-placeholder',
onSort: (e) => this.onSortUpdate(e),
});
});
}
setMinTags(minTags, maxTags, value) {
@@ -175,9 +177,9 @@ export default class TagsPage extends ExtensionPage {
app.store.getById('tags', e.item.getAttribute('data-id')).pushData({
attributes: {
position: null,
isChild: false
isChild: false,
},
relationships: { parent: null }
relationships: { parent: null },
});
}
@@ -187,12 +189,15 @@ export default class TagsPage extends ExtensionPage {
.map(function () {
return {
id: $(this).data('id'),
children: $(this).find('li')
children: $(this)
.find('li')
.map(function () {
return $(this).data('id');
}).get()
})
.get(),
};
}).get();
})
.get();
// Now that we have an accurate representation of the order which the
// primary tags are in, we will update the tag attributes in our local
@@ -202,18 +207,18 @@ export default class TagsPage extends ExtensionPage {
parent.pushData({
attributes: {
position: i,
isChild: false
isChild: false,
},
relationships: { parent: null }
relationships: { parent: null },
});
tag.children.forEach((child, j) => {
app.store.getById('tags', child).pushData({
attributes: {
position: j,
isChild: true
isChild: true,
},
relationships: { parent }
relationships: { parent },
});
});
});
@@ -221,7 +226,7 @@ export default class TagsPage extends ExtensionPage {
app.request({
url: app.forum.attribute('apiUrl') + '/tags/order',
method: 'POST',
body: { order }
body: { order },
});
this.forcedRefreshKey++;

View File

@@ -6,7 +6,7 @@ import addTagsHomePageOption from './addTagsHomePageOption';
import addTagChangePermission from './addTagChangePermission';
import TagsPage from './components/TagsPage';
app.initializers.add('flarum-tags', app => {
app.initializers.add('flarum-tags', (app) => {
app.store.models.tags = Tag;
app.extensionData.for('flarum-tags').registerPage(TagsPage);
@@ -17,7 +17,6 @@ app.initializers.add('flarum-tags', app => {
addTagChangePermission();
});
// Expose compat API
import tagsCompat from './compat';
import { compat } from '@flarum/core/admin';

View File

@@ -9,5 +9,5 @@ export default {
'tags/models/Tag': Tag,
'tags/helpers/tagsLabel': tagsLabel,
'tags/helpers/tagIcon': tagIcon,
'tags/helpers/tagLabel': tagLabel
'tags/helpers/tagLabel': tagLabel,
};

View File

@@ -4,11 +4,7 @@ export default function tagIcon(tag, attrs = {}, settings = {}) {
const hasIcon = tag && tag.icon();
const { useColor = true } = settings;
attrs.className = classList([
attrs.className,
'icon',
hasIcon ? tag.icon() : 'TagIcon'
]);
attrs.className = classList([attrs.className, 'icon', hasIcon ? tag.icon() : 'TagIcon']);
if (tag && useColor) {
attrs.style = attrs.style || {};
@@ -21,5 +17,5 @@ export default function tagIcon(tag, attrs = {}, settings = {}) {
attrs.className += ' untagged';
}
return hasIcon ? <i {...attrs}/> : <span {...attrs}/>;
return hasIcon ? <i {...attrs} /> : <span {...attrs} />;
}

View File

@@ -18,7 +18,7 @@ export default function tagLabel(tag, attrs = {}) {
if (link) {
attrs.title = tag.description() || '';
attrs.href = app.route('tag', {tags: tag.slug()});
attrs.href = app.route('tag', { tags: tag.slug() });
}
if (tag.isChild()) {
@@ -28,11 +28,11 @@ export default function tagLabel(tag, attrs = {}) {
attrs.className += ' untagged';
}
return (
m((link ? Link : 'span'), attrs,
<span className="TagLabel-text">
{tag && tag.icon() && tagIcon(tag, {}, {useColor: false})} {tagText}
</span>
)
return m(
link ? Link : 'span',
attrs,
<span className="TagLabel-text">
{tag && tag.icon() && tagIcon(tag, {}, { useColor: false })} {tagText}
</span>
);
}

View File

@@ -9,9 +9,9 @@ export default function tagsLabel(tags, attrs = {}) {
attrs.className = 'TagsLabel ' + (attrs.className || '');
if (tags) {
sortTags(tags).forEach(tag => {
sortTags(tags).forEach((tag) => {
if (tag || tags.length === 1) {
children.push(tagLabel(tag, {link}));
children.push(tagLabel(tag, { link }));
}
});
} else {

View File

@@ -1,4 +1,4 @@
import Tag from "../models/Tag";
import Tag from '../models/Tag';
export default function sortTags(tags: Tag[]) {
return tags.slice(0).sort((a, b) => {
@@ -7,8 +7,7 @@ export default function sortTags(tags: Tag[]) {
// If they're both secondary tags, sort them by their discussions count,
// descending.
if (aPos === null && bPos === null)
return b.discussionCount() - a.discussionCount();
if (aPos === null && bPos === null) return b.discussionCount() - a.discussionCount();
// If just one is a secondary tag, then the primary tag should
// come first.
@@ -23,20 +22,14 @@ export default function sortTags(tags: Tag[]) {
// If they both have the same parent, then their positions are local,
// so we can compare them directly.
if (aParent === bParent) return aPos - bPos;
// If they are both child tags, then we will compare the positions of their
// parents.
else if (aParent && bParent)
return aParent.position()! - bParent.position()!;
else if (aParent && bParent) return aParent.position()! - bParent.position()!;
// If we are comparing a child tag with its parent, then we let the parent
// come first. If we are comparing an unrelated parent/child, then we
// compare both of the parents.
else if (aParent)
return aParent === b ? 1 : aParent.position()! - bPos;
else if (bParent)
return bParent === a ? -1 : aPos - bParent.position()!;
else if (aParent) return aParent === b ? 1 : aParent.position()! - bPos;
else if (bParent) return bParent === a ? -1 : aPos - bParent.position()!;
return 0;
});

View File

@@ -15,15 +15,14 @@ export default function () {
if (tag) {
const parent = tag.parent();
const tags = parent ? [parent, tag] : [tag];
promise.then(composer => composer.fields.tags = tags);
promise.then((composer) => (composer.fields.tags = tags));
} else {
app.composer.fields.tags = [];
}
});
extend(DiscussionComposer.prototype, 'oninit', function () {
app.tagList.load(['parent']).then(() => m.redraw())
app.tagList.load(['parent']).then(() => m.redraw());
});
// Add tag-selection abilities to the discussion composer.
@@ -34,10 +33,10 @@ export default function () {
app.modal.show(TagDiscussionModal, {
selectedTags: (this.composer.fields.tags || []).slice(0),
onsubmit: tags => {
onsubmit: (tags) => {
this.composer.fields.tags = tags;
this.$('textarea').focus();
}
},
});
};
@@ -47,32 +46,43 @@ export default function () {
const tags = this.composer.fields.tags || [];
const selectableTags = getSelectableTags();
items.add('tags', (
items.add(
'tags',
<a className={classList(['DiscussionComposer-changeTags', !selectableTags.length && 'disabled'])} onclick={this.chooseTags.bind(this)}>
{tags.length
? tagsLabel(tags)
: <span className="TagLabel untagged">{app.translator.trans('flarum-tags.forum.composer_discussion.choose_tags_link')}</span>}
</a>
), 10);
{tags.length ? (
tagsLabel(tags)
) : (
<span className="TagLabel untagged">{app.translator.trans('flarum-tags.forum.composer_discussion.choose_tags_link')}</span>
)}
</a>,
10
);
});
override(DiscussionComposer.prototype, 'onsubmit', function (original) {
const chosenTags = this.composer.fields.tags || [];
const chosenPrimaryTags = chosenTags.filter(tag => tag.position() !== null && !tag.isChild());
const chosenSecondaryTags = chosenTags.filter(tag => tag.position() === null);
const chosenPrimaryTags = chosenTags.filter((tag) => tag.position() !== null && !tag.isChild());
const chosenSecondaryTags = chosenTags.filter((tag) => tag.position() === null);
const selectableTags = getSelectableTags();
if ((!chosenTags.length
|| (chosenPrimaryTags.length < app.forum.attribute('minPrimaryTags'))
|| (chosenSecondaryTags.length < app.forum.attribute('minSecondaryTags'))
) && selectableTags.length) {
const minPrimaryTags = parseInt(app.forum.attribute('minPrimaryTags'));
const minSecondaryTags = parseInt(app.forum.attribute('minSecondaryTags'));
const maxPrimaryTags = parseInt(app.forum.attribute('maxPrimaryTags'));
const maxSecondaryTags = parseInt(app.forum.attribute('maxSecondaryTags'));
if (
((!chosenTags.length && maxPrimaryTags !== 0 && maxSecondaryTags !== 0) ||
chosenPrimaryTags.length < minPrimaryTags ||
chosenSecondaryTags.length < minSecondaryTags) &&
selectableTags.length
) {
app.modal.show(TagDiscussionModal, {
selectedTags: chosenTags,
onsubmit: tags => {
this.composer.fields.tags = tags;
original();
}
});
selectedTags: chosenTags,
onsubmit: (tags) => {
this.composer.fields.tags = tags;
original();
},
});
} else {
original();
}

View File

@@ -4,13 +4,16 @@ import Button from 'flarum/common/components/Button';
import TagDiscussionModal from './components/TagDiscussionModal';
export default function() {
export default function () {
// Add a control allowing the discussion to be moved to another category.
extend(DiscussionControls, 'moderationControls', function(items, discussion) {
extend(DiscussionControls, 'moderationControls', function (items, discussion) {
if (discussion.canTag()) {
items.add('tags', <Button icon="fas fa-tag" onclick={() => app.modal.show(TagDiscussionModal, { discussion })}>
{app.translator.trans('flarum-tags.forum.discussion_controls.edit_tags_button')}
</Button>);
items.add(
'tags',
<Button icon="fas fa-tag" onclick={() => app.modal.show(TagDiscussionModal, { discussion })}>
{app.translator.trans('flarum-tags.forum.discussion_controls.edit_tags_button')}
</Button>
);
}
});
}

View File

@@ -10,10 +10,10 @@ import TagHero from './components/TagHero';
import Tag from '../common/models/Tag';
import { ComponentAttrs } from 'flarum/common/Component';
const findTag = (slug: string) => app.store.all<Tag>('tags').find(tag => tag.slug().localeCompare(slug, undefined, { sensitivity: 'base' }) === 0);
const findTag = (slug: string) => app.store.all<Tag>('tags').find((tag) => tag.slug().localeCompare(slug, undefined, { sensitivity: 'base' }) === 0);
export default function() {
IndexPage.prototype.currentTag = function() {
export default function () {
IndexPage.prototype.currentTag = function () {
if (this.currentActiveTag) {
return this.currentActiveTag;
}
@@ -25,7 +25,7 @@ export default function() {
tag = findTag(slug);
}
if (slug && !tag || (tag && !tag.isChild() && !tag.children())) {
if ((slug && !tag) || (tag && !tag.isChild() && !tag.children())) {
if (this.currentTagLoading) {
return;
}
@@ -36,13 +36,16 @@ export default function() {
// a child tag page, then either:
// - We loaded in that child tag (and its siblings) in the API document
// - We first navigated to the current tag's parent, which would have loaded in the current tag's siblings.
app.store.find('tags', slug, { include: 'children,children.parent,parent,state'}).then(() => {
this.currentActiveTag = findTag(slug);
app.store
.find('tags', slug, { include: 'children,children.parent,parent,state' })
.then(() => {
this.currentActiveTag = findTag(slug);
m.redraw();
}).finally(() => {
this.currentTagLoading = false;
});
m.redraw();
})
.finally(() => {
this.currentTagLoading = false;
});
}
if (tag) {
@@ -54,7 +57,7 @@ export default function() {
};
// If currently viewing a tag, insert a tag hero at the top of the view.
override(IndexPage.prototype, 'hero', function(original) {
override(IndexPage.prototype, 'hero', function (original) {
const tag = this.currentTag();
if (tag) return <TagHero model={tag} />;
@@ -62,13 +65,13 @@ export default function() {
return original();
});
extend(IndexPage.prototype, 'view', function(vdom: Mithril.Vnode<ComponentAttrs, {}>) {
extend(IndexPage.prototype, 'view', function (vdom: Mithril.Vnode<ComponentAttrs, {}>) {
const tag = this.currentTag();
if (tag) vdom.attrs.className += ' IndexPage--tag'+tag.id();
if (tag) vdom.attrs.className += ' IndexPage--tag' + tag.id();
});
extend(IndexPage.prototype, 'setTitle', function() {
extend(IndexPage.prototype, 'setTitle', function () {
const tag = this.currentTag();
if (tag) {
@@ -78,7 +81,7 @@ export default function() {
// If currently viewing a tag, restyle the 'new discussion' button to use
// the tag's color, and disable if the user isn't allowed to edit.
extend(IndexPage.prototype, 'sidebarItems', function(items) {
extend(IndexPage.prototype, 'sidebarItems', function (items) {
const tag = this.currentTag();
if (tag) {
@@ -92,18 +95,20 @@ export default function() {
}
newDiscussion.attrs.disabled = !canStartDiscussion;
newDiscussion.children = app.translator.trans(canStartDiscussion ? 'core.forum.index.start_discussion_button' : 'core.forum.index.cannot_start_discussion_button');
newDiscussion.children = app.translator.trans(
canStartDiscussion ? 'core.forum.index.start_discussion_button' : 'core.forum.index.cannot_start_discussion_button'
);
}
});
// Add a parameter for the global search state to pass on to the
// DiscussionListState that will let us filter discussions by tag.
extend(GlobalSearchState.prototype, 'params', function(params) {
extend(GlobalSearchState.prototype, 'params', function (params) {
params.tags = m.route.param('tags');
});
// Translate that parameter into a gambit appended to the search query.
extend(DiscussionListState.prototype, 'requestParams', function(this: DiscussionListState, params) {
extend(DiscussionListState.prototype, 'requestParams', function (this: DiscussionListState, params) {
if (typeof params.include === 'string') {
params.include = [params.include];
} else {
@@ -118,7 +123,7 @@ export default function() {
if (q) {
filter.q = `${q} tag:${this.params.tags}`;
}
params.filter = filter
params.filter = filter;
}
});
}

View File

@@ -5,9 +5,9 @@ import DiscussionHero from 'flarum/forum/components/DiscussionHero';
import tagsLabel from '../common/helpers/tagsLabel';
import sortTags from '../common/utils/sortTags';
export default function() {
export default function () {
// Add tag labels to each discussion in the discussion list.
extend(DiscussionListItem.prototype, 'infoItems', function(items) {
extend(DiscussionListItem.prototype, 'infoItems', function (items) {
const tags = this.attrs.discussion.tags();
if (tags && tags.length) {
@@ -16,7 +16,7 @@ export default function() {
});
// Restyle a discussion's hero to use its first tag's color.
extend(DiscussionHero.prototype, 'view', function(view) {
extend(DiscussionHero.prototype, 'view', function (view) {
const tags = sortTags(this.attrs.discussion.tags());
if (tags && tags.length) {
@@ -30,11 +30,11 @@ export default function() {
// Add a list of a discussion's tags to the discussion hero, displayed
// before the title. Put the title on its own line.
extend(DiscussionHero.prototype, 'items', function(items) {
extend(DiscussionHero.prototype, 'items', function (items) {
const tags = this.attrs.discussion.tags();
if (tags && tags.length) {
items.add('tags', tagsLabel(tags, {link: true}), 5);
items.add('tags', tagsLabel(tags, { link: true }), 5);
}
});
}

View File

@@ -7,14 +7,17 @@ import TagLinkButton from './components/TagLinkButton';
import TagsPage from './components/TagsPage';
import sortTags from '../common/utils/sortTags';
export default function() {
export default function () {
// Add a link to the tags page, as well as a list of all the tags,
// to the index page's sidebar.
extend(IndexPage.prototype, 'navItems', function (items) {
items.add('tags', <LinkButton icon="fas fa-th-large" href={app.route('tags')}>
{app.translator.trans('flarum-tags.forum.index.tags_link')}
</LinkButton>
, -10);
items.add(
'tags',
<LinkButton icon="fas fa-th-large" href={app.route('tags')}>
{app.translator.trans('flarum-tags.forum.index.tags_link')}
</LinkButton>,
-10
);
if (app.current.matches(TagsPage)) return;
@@ -24,7 +27,7 @@ export default function() {
const tags = app.store.all('tags');
const currentTag = this.currentTag();
const addTag = tag => {
const addTag = (tag) => {
let active = currentTag === tag;
if (!active && currentTag) {
@@ -36,23 +39,21 @@ export default function() {
// use its children to populate the dropdown. The problem here is that `view`
// on TagLinkButton is only called AFTER SelectDropdown, so no children are available
// for SelectDropdown to use at the time.
items.add('tag' + tag.id(), TagLinkButton.component({model: tag, params, active}, tag?.name()), -14);
items.add('tag' + tag.id(), TagLinkButton.component({ model: tag, params, active }, tag?.name()), -14);
};
sortTags(tags)
.filter(tag => tag.position() !== null && (!tag.isChild() || (currentTag && (tag.parent() === currentTag || tag.parent() === currentTag.parent()))))
.filter(
(tag) => tag.position() !== null && (!tag.isChild() || (currentTag && (tag.parent() === currentTag || tag.parent() === currentTag.parent())))
)
.forEach(addTag);
const more = tags
.filter(tag => tag.position() === null)
.sort((a, b) => b.discussionCount() - a.discussionCount());
const more = tags.filter((tag) => tag.position() === null).sort((a, b) => b.discussionCount() - a.discussionCount());
more.splice(0, 3).forEach(addTag);
if (more.length) {
items.add('moreTags', <LinkButton href={app.route('tags')}>
{app.translator.trans('flarum-tags.forum.index.more_link')}
</LinkButton>, -16)
items.add('moreTags', <LinkButton href={app.route('tags')}>{app.translator.trans('flarum-tags.forum.index.more_link')}</LinkButton>, -16);
}
});
}

View File

@@ -9,9 +9,7 @@ export default class DiscussionTaggedPost extends EventPost {
const newTags = attrs.post.content()[1];
function diffTags(tags1, tags2) {
return tags1
.filter(tag => tags2.indexOf(tag) === -1)
.map(id => app.store.getById('tags', id));
return tags1.filter((tag) => tags2.indexOf(tag) === -1).map((id) => app.store.getById('tags', id));
}
attrs.tagsAdded = diffTags(newTags, oldTags);
@@ -39,15 +37,15 @@ export default class DiscussionTaggedPost extends EventPost {
if (this.attrs.tagsAdded.length) {
data.tagsAdded = app.translator.trans('flarum-tags.forum.post_stream.tags_text', {
tags: tagsLabel(this.attrs.tagsAdded, {link: true}),
count: this.attrs.tagsAdded.length
tags: tagsLabel(this.attrs.tagsAdded, { link: true }),
count: this.attrs.tagsAdded.length,
});
}
if (this.attrs.tagsRemoved.length) {
data.tagsRemoved = app.translator.trans('flarum-tags.forum.post_stream.tags_text', {
tags: tagsLabel(this.attrs.tagsRemoved, {link: true}),
count: this.attrs.tagsRemoved.length
tags: tagsLabel(this.attrs.tagsRemoved, { link: true }),
count: this.attrs.tagsRemoved.length,
});
}

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