mirror of
https://github.com/flarum/core.git
synced 2025-08-14 04:14:06 +02:00
Compare commits
146 Commits
sm/lower-r
...
v2.0.0-bet
Author | SHA1 | Date | |
---|---|---|---|
|
4e95d06190 | ||
|
e1a91dcd77 | ||
|
fbe7be69ef | ||
|
7f946ca0dd | ||
|
cc05a6dd3b | ||
|
649be7cb03 | ||
|
f19007f424 | ||
|
15112c2f40 | ||
|
00ef0dd9d0 | ||
|
cae706a638 | ||
|
2be1932e54 | ||
|
2339c23aae | ||
|
2b08c30a22 | ||
|
fa88731fe1 | ||
|
65d8c16580 | ||
|
1a206ff658 | ||
|
0ca99dcba5 | ||
|
12fc3aeec2 | ||
|
b07b310fdf | ||
|
e2221b5f74 | ||
|
f54a5200cf | ||
|
236a8e9e0a | ||
|
7feab89cca | ||
|
bbc4b6dd13 | ||
|
561e22784a | ||
|
469127ccf3 | ||
|
80c116b386 | ||
|
b74c7f9746 | ||
|
b7bab2811d | ||
|
3cbc7f4de1 | ||
|
1b9ff2b6fa | ||
|
d02a924bb8 | ||
|
973f4f6f6b | ||
|
9977d491cf | ||
|
9758592daa | ||
|
60feaa0184 | ||
|
5557bf82d3 | ||
|
090fd4dea5 | ||
|
ecb23a64fc | ||
|
7ba768bf68 | ||
|
f13dc05866 | ||
|
a34a5d4d62 | ||
|
79e969778e | ||
|
41d62b8c82 | ||
|
5dc94bf4e8 | ||
|
fdaf09752c | ||
|
975c2c936f | ||
|
ce5feca140 | ||
|
55cd0850c7 | ||
|
6b31a47f05 | ||
|
db1e36d545 | ||
|
333bbb11e2 | ||
|
863d6526df | ||
|
97e56af2cd | ||
|
21da7758af | ||
|
89ff984446 | ||
|
7136ad01d5 | ||
|
875fd241b7 | ||
|
962d95746d | ||
|
0b995b96ef | ||
|
76d8ea505e | ||
|
cf7ef48906 | ||
|
670aa2e236 | ||
|
ac6133a768 | ||
|
48ec73f86e | ||
|
00426c85e3 | ||
|
33121ed1cc | ||
|
b2f6b4cf88 | ||
|
1cd644d27f | ||
|
68faca4d5f | ||
|
603140a0b1 | ||
|
36d0795607 | ||
|
a7d584f8e1 | ||
|
958dec5944 | ||
|
55aa8a7723 | ||
|
e3943eebba | ||
|
101ff98822 | ||
|
6a9ce6f8e8 | ||
|
2366666091 | ||
|
8c9a772635 | ||
|
9d611d1ee2 | ||
|
e73ffee9e7 | ||
|
1b18f57353 | ||
|
9ce03c6ec8 | ||
|
1bdfb66f6c | ||
|
a3461a8020 | ||
|
da8ebe439b | ||
|
5d26485c04 | ||
|
38e1b49cc1 | ||
|
0c40e19cdf | ||
|
6d1e90c969 | ||
|
3294941226 | ||
|
87fa4a32dd | ||
|
e08a987842 | ||
|
9fe17b3c24 | ||
|
4dce4d40a3 | ||
|
a81d13e26c | ||
|
382991648c | ||
|
b44d5175b6 | ||
|
d9e79608fb | ||
|
5d281b9471 | ||
|
27087cc713 | ||
|
d5f944023c | ||
|
153fb8684e | ||
|
eca21fa1bf | ||
|
c835bdb59a | ||
|
570580dcf0 | ||
|
e43449c32d | ||
|
bc695e9865 | ||
|
6d88c99782 | ||
|
097a54289f | ||
|
d90573f8f1 | ||
|
f6e84a0dc5 | ||
|
22b74d0404 | ||
|
94b0d67a40 | ||
|
9920a9f2f0 | ||
|
08b36149fd | ||
|
7f001dfe3b | ||
|
5fbb9fe9a7 | ||
|
33a72f5c14 | ||
|
af2a32a3a4 | ||
|
bcff06a362 | ||
|
e1c82a19dc | ||
|
770feabb59 | ||
|
464b67f848 | ||
|
41e5ff2525 | ||
|
49064f6912 | ||
|
7cfa560b4e | ||
|
70081a267f | ||
|
70158aa0ef | ||
|
5e7fbcb0e8 | ||
|
4feb4a3820 | ||
|
6323314ad7 | ||
|
3b69af2ae6 | ||
|
c401e678f3 | ||
|
03fe885b56 | ||
|
1ebceab85a | ||
|
dd45d75cd8 | ||
|
820894a7c9 | ||
|
845228f251 | ||
|
8c331038da | ||
|
04fe684db8 | ||
|
75d05ee85d | ||
|
8ba9f14642 | ||
|
c2b91ba1cd | ||
|
5d10fdc66a |
@@ -18,7 +18,7 @@ trim_trailing_whitespace = false
|
||||
[*.{php,xml,json}]
|
||||
indent_size = 4
|
||||
|
||||
[{tsconfig.json,prettierrc.json}]
|
||||
[{tsconfig.json,prettierrc.json,package.json}]
|
||||
indent_size = 2
|
||||
|
||||
[*.neon]
|
||||
|
54
.github/workflows/REUSABLE_backend.yml
vendored
54
.github/workflows/REUSABLE_backend.yml
vendored
@@ -32,7 +32,7 @@ on:
|
||||
type: string
|
||||
required: false
|
||||
# Keep PHP versions synced with build-install-packages.yml
|
||||
default: '["8.2", "8.3"]'
|
||||
default: '["8.2", "8.3", "8.4"]'
|
||||
|
||||
php_extensions:
|
||||
description: PHP extensions to install.
|
||||
@@ -69,8 +69,8 @@ env:
|
||||
FLARUM_TEST_TMP_DIR_LOCAL: tests/integration/tmp
|
||||
COMPOSER_AUTH: ${{ secrets.composer_auth }}
|
||||
DB_DATABASE: flarum_test
|
||||
DB_USERNAME: root
|
||||
DB_PASSWORD: root
|
||||
DB_USERNAME: flarum
|
||||
DB_PASSWORD: password
|
||||
|
||||
jobs:
|
||||
test:
|
||||
@@ -94,7 +94,7 @@ jobs:
|
||||
driver: mysql
|
||||
- service: mariadb
|
||||
db: MariaDB
|
||||
driver: mysql
|
||||
driver: mariadb
|
||||
- service: 'mysql:8.1.0'
|
||||
db: MySQL 8.1
|
||||
driver: mysql
|
||||
@@ -115,7 +115,7 @@ jobs:
|
||||
- php: ${{ fromJSON(inputs.php_versions)[0] }}
|
||||
service: mariadb
|
||||
db: MariaDB
|
||||
driver: mysql
|
||||
driver: mariadb
|
||||
prefix: flarum_
|
||||
prefixStr: (prefix)
|
||||
- php: ${{ fromJSON(inputs.php_versions)[0] }}
|
||||
@@ -159,9 +159,20 @@ jobs:
|
||||
MYSQL_DATABASE: ${{ env.DB_DATABASE }}
|
||||
MYSQL_USER: ${{ env.DB_USERNAME }}
|
||||
MYSQL_PASSWORD: ${{ env.DB_PASSWORD }}
|
||||
MYSQL_ROOT_PASSWORD: ${{ env.DB_PASSWORD }}
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
ports:
|
||||
- 13306:3306
|
||||
- 3306
|
||||
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=10
|
||||
mariadb:
|
||||
image: ${{ matrix.driver == 'mariadb' && matrix.service || '' }}
|
||||
env:
|
||||
MARIADB_DATABASE: ${{ env.DB_DATABASE }}
|
||||
MARIADB_USER: ${{ env.DB_USERNAME }}
|
||||
MARIADB_PASSWORD: ${{ env.DB_PASSWORD }}
|
||||
MARIADB_ROOT_PASSWORD: root
|
||||
ports:
|
||||
- 3306
|
||||
options: --health-cmd="healthcheck.sh --connect --innodb_initialized" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||
postgres:
|
||||
image: ${{ matrix.driver == 'pgsql' && matrix.service || '' }}
|
||||
env:
|
||||
@@ -169,7 +180,7 @@ jobs:
|
||||
POSTGRES_USER: ${{ env.DB_USERNAME }}
|
||||
POSTGRES_PASSWORD: ${{ env.DB_PASSWORD }}
|
||||
ports:
|
||||
- 15432:5432
|
||||
- 5432
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
@@ -183,7 +194,10 @@ jobs:
|
||||
((github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) || github.event_name != 'pull_request')
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
@@ -194,12 +208,6 @@ jobs:
|
||||
tools: phpunit, composer:v2
|
||||
ini-values: ${{ matrix.php_ini_values }}
|
||||
|
||||
- name: Create MySQL Database
|
||||
if: ${{ matrix.driver == 'mysql' }}
|
||||
run: |
|
||||
sudo systemctl start mysql
|
||||
mysql -uroot -proot -e 'CREATE DATABASE flarum_test;' --port 13306
|
||||
|
||||
- name: Install Composer dependencies
|
||||
run: composer install
|
||||
working-directory: ${{ inputs.backend_directory }}
|
||||
@@ -222,7 +230,8 @@ jobs:
|
||||
fi
|
||||
working-directory: ${{ inputs.backend_directory }}
|
||||
env:
|
||||
DB_PORT: ${{ matrix.driver == 'mysql' && 13306 || 15432 }}
|
||||
DB_HOST: 127.0.0.1
|
||||
DB_PORT: ${{ (matrix.driver == 'mysql' && job.services.mysql.ports['3306']) || (matrix.driver == 'mariadb' && job.services.mariadb.ports['3306']) || (matrix.driver == 'pgsql' && job.services.postgres.ports['5432']) }}
|
||||
DB_PREFIX: ${{ matrix.prefix }}
|
||||
DB_DRIVER: ${{ matrix.driver }}
|
||||
COMPOSER_PROCESS_TIMEOUT: 600
|
||||
@@ -232,13 +241,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php: ${{ fromJSON(inputs.php_versions) }}
|
||||
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:8.0.30
|
||||
ports:
|
||||
- 33306:3306
|
||||
php: ${{ fromJson(inputs.php_versions) }}
|
||||
|
||||
name: 'PHPStan PHP ${{ matrix.php }}'
|
||||
|
||||
@@ -265,12 +268,13 @@ jobs:
|
||||
- name: Create MySQL Database
|
||||
run: |
|
||||
sudo systemctl start mysql
|
||||
mysql -uroot -proot -e 'CREATE DATABASE flarum_test;' --port 33306
|
||||
mysql -uroot -proot -e 'CREATE DATABASE flarum_test;' --port 3306
|
||||
|
||||
- name: Run PHPStan
|
||||
run: composer analyse:phpstan
|
||||
env:
|
||||
DB_PORT: 33306
|
||||
DB_USERNAME: root
|
||||
DB_PORT: 3306
|
||||
DB_PASSWORD: root
|
||||
COMPOSER_PROCESS_TIMEOUT: 600
|
||||
FLARUM_TEST_TMP_DIR_LOCAL: ./tmp
|
||||
|
2
.github/workflows/backend.yml
vendored
2
.github/workflows/backend.yml
vendored
@@ -8,4 +8,4 @@ jobs:
|
||||
with:
|
||||
enable_backend_testing: true
|
||||
backend_directory: .
|
||||
monorepo_tests: "framework/core extensions/akismet extensions/approval extensions/flags extensions/likes extensions/mentions extensions/nicknames extensions/statistics extensions/sticky extensions/subscriptions extensions/suspend extensions/tags extensions/messages"
|
||||
monorepo_tests: "framework/core extensions/akismet extensions/approval extensions/flags extensions/likes extensions/mentions extensions/nicknames extensions/statistics extensions/sticky extensions/subscriptions extensions/suspend extensions/tags extensions/messages php-packages/testing/tests"
|
||||
|
2
.github/workflows/build-install-packages.yml
vendored
2
.github/workflows/build-install-packages.yml
vendored
@@ -6,7 +6,7 @@ on:
|
||||
|
||||
env:
|
||||
VERSION: ${{ github.event.release.tag_name }}
|
||||
PHP_VERSIONS: '8.2 8.3'
|
||||
PHP_VERSIONS: '8.2 8.3 8.4'
|
||||
INSTALL_PACKAGES_INPUTS: '{ "flarum_version": "{0}", "php_versions": "{1}" }'
|
||||
|
||||
jobs:
|
||||
|
185
CHANGELOG.md
185
CHANGELOG.md
@@ -1,5 +1,190 @@
|
||||
# Changelog
|
||||
|
||||
## [v2.0.0-beta.3](https://github.com/flarum/framework/compare/v2.0.0-beta.2...v2.0.0-beta.3)
|
||||
### Changed
|
||||
- (a11y) misc a11y improvements by @SychO9 [#4211]
|
||||
- allow labels of `PostStreamScrubber` to be customized by @DavideIadeluca [#4181]
|
||||
- improve extensibility of Admin Pages by @DavideIadeluca [#4200]
|
||||
- improve extensibility of `IndexPage` by @DavideIadeluca [#4182]
|
||||
- improve extensibility of `PostMeta` component by @DavideIadeluca [#4196]
|
||||
- make search debounce time extensible by @DavideIadeluca [#4172]
|
||||
- Sanitize page in `Tag` (#4170) by @rob006 (15112c2f40656db8c310945e6c7255b90570379f)
|
||||
- Codebase cleanup by @xHeaven [#4161]
|
||||
- `audit-fix` by @SychO9 (fbe7be69ef573d0d39f70454bfd02ab94857db8a)
|
||||
- increase composer job timeout by @SychO9 (fa88731fe1f4473831af6ba56b186c72924307d9)
|
||||
- optimize querying post index by @SychO9 [#4178]
|
||||
- render after first post items once by @SychO9 (973f4f6f6ba8574b9d56674df94a02f060464ca4)
|
||||
- (tags) improve extensibility of `TagHero` by @DavideIadeluca [#4198]
|
||||
- allow extending `PostPreview` content by @DavideIadeluca [#4197]
|
||||
- improve extensibility of `WelcomeHero` by @DavideIadeluca [#4199]
|
||||
- make it easier to add content after the first post by @DavideIadeluca [#4186]
|
||||
### Fixed
|
||||
- (security) Session Hijacking via Authoritative Subdomain Cookie Overwrite by @novacuum (f19007f42466ebf881307670a32d14516444ac24)
|
||||
- fixes issue with smtp non-tls connections by @luceos [#4203]
|
||||
- change condition when `unread` label is shown in Scrubber by @DavideIadeluca [#4185]
|
||||
- change starting position of `aria-posinset` by @DavideIadeluca [#4191]
|
||||
- return empty object if selected mail driver is unavailable by @DavideIadeluca [#4183]
|
||||
- (tags) resolve `a11y` warnings in Admin Frontend by @DavideIadeluca [#4184]
|
||||
- (em) skip incompatible extension updates by @SychO9 [#4177]
|
||||
- (phpstan) incompatibility with recent updates by @SychO9 (1b9ff2b6fa90a9c991b6e1d9ab5bd959802bd099)
|
||||
- (webpack) chunk module path checking fails with dotted directories by @DavideIadeluca [#4179]
|
||||
- `sendmail` driver fails by @SychO9 [#4168]
|
||||
- `suspended_until` serialized as date instead of datetime by @SychO9 [#4169]
|
||||
- messages UI/UX improvement by @SychO9 [#4173]
|
||||
- messages inconsistencies by @SychO9 [#4174]
|
||||
- prevent users from seeing their own flags by @SychO9 [#4167]
|
||||
- visual bugs by @SychO9 (97e56af2cd8e97e4ef10235d3e584d0def2afffc)
|
||||
### Added
|
||||
- (messages) messages page extensible content by @SychO9 (561e22784a547c8aa92120e0972a9cc97ac21645)
|
||||
- (pm) delete own messages by @SychO9 [#4180]
|
||||
- (pm) messages anchor link by @SychO9 [#4175]
|
||||
- actions dropdown in admin user list by @DavideIadeluca [#4188]
|
||||
- advanced admin registry extenders by @SychO9 [#4209]
|
||||
- reusable component for showing IP address by @DavideIadeluca [#4187]
|
||||
|
||||
## [v2.0.0-beta.2](https://github.com/flarum/framework/compare/v2.0.0-beta.1...v2.0.0-beta.2)
|
||||
### Fixed
|
||||
- (em) incorrect extension compatibility check [#4155]
|
||||
- (webpack) produces incorrect ext namespace (a7d584f8e1ec650035dafd660a70586d1d0d6bb9)
|
||||
- bad modal alert text alignment [#4152]
|
||||
- beta.1 early bugs (a81d13e26c1c2191859493de2ad45a515ad07b90)
|
||||
- code split fails with common module [#4151]
|
||||
- composer no longer autofocusing [#4149]
|
||||
- conditional renders 0 (1cd644d27feb4eeea5cbaedd009a3af2643af396)
|
||||
- custom styles from 1.x can crash the app [#4159]
|
||||
- discussion page renders before loading is finished [#4158]
|
||||
- discussion posts not always properly loaded [#4156]
|
||||
- fixed side nav missing top spacing [#4147]
|
||||
- invisible dropdown text when header is colored (958dec594486cbc14cf8f922db324a8ffc0245e3)
|
||||
- lazy module import always returns default module [#4148]
|
||||
- mistakenly removed code (33121ed1cc260bf967f0b8c4d10ab5099410bac0)
|
||||
- select input cuts off [#4157]
|
||||
- tag selection icon alignment [#4153]
|
||||
- unexpected subscription breaks rendering [#4150]
|
||||
- use correct human time format key [#4154]
|
||||
### Changed
|
||||
- (mentions) only access related mentions if loaded (9fe17b3c24c5b9236e419a00c1230b2994b8c009)
|
||||
- extensibility improvements (00426c85e38efc91554af33644b088e72b3b3c1b)
|
||||
|
||||
## [v2.0.0-beta.1](https://github.com/flarum/framework/compare/v1.8.9...v2.0.0-beta.1)
|
||||
### Changed
|
||||
- php 8.4 [#4103]
|
||||
- JSON:API refactor [#3971]
|
||||
- (mentions) allow renderer to be used without context [#3954]
|
||||
- (flags) add pagination to flags list [#3931]
|
||||
- (mentions) add integration test for reply approval notification [#3748]
|
||||
- (mentions,emoji) tie autocomplete to editor instance [#3913]
|
||||
- (phpstan) upgrade to be compatible with latest dependency updates [#3835]
|
||||
- (tags) prevent loading tag state if loaded previously [#4009]
|
||||
- (testing) run flarum/testing packages tests back again [#3844]
|
||||
- Do not catch exceptions when testing Console commands [#3813]
|
||||
- Patch vulnerability advisory [#3966]
|
||||
- Search box improvements on tablet devices [#4076]
|
||||
- Test using MySQL 8.1 [#3870]
|
||||
- Upgrade dependencies [#3830], [#4012]
|
||||
- Upgrade `intervention/image` to 3.2 [#3947]
|
||||
- Upgrade Wikimedia/less.php to 4.1 [#3837]
|
||||
- Upgrade Mithril to 2.2 [#3831]
|
||||
- (tags) Wrong tag input width if contains CJK characters [#4127]
|
||||
- allow custom actions runner to be defined [#3988]
|
||||
- avoid using `.fa()` mixins and `@fa-var` vars [#3912]
|
||||
- cleanup composer deps (40dcaf882cd017463ca792762d4aa11de8c5c7da)
|
||||
- corrected typos in routes comments [#3840]
|
||||
- extract `FormModal` from `Modal` [#3922]
|
||||
- extract `buildSettingComponent` method into a `FormGroup` component [#3927]
|
||||
- fix typos [#4021]
|
||||
- function names (d15438846895a5cf7b5a584479ef63a6e3119925)
|
||||
- handle deprecations from 1.x [#3909]
|
||||
- ignore deprecation errors in prod [#4072]
|
||||
- improve debugging experience [#3944]
|
||||
- improve test suite [#3814]
|
||||
- increase phpstan level to 6 [#3836]
|
||||
- `HeaderPrimary.js` converted to typescript [#4052]
|
||||
- merge the app with the container & implement the ApplicationContract [#3862]
|
||||
- recover bundlewatch [#3829]
|
||||
- remove ExtenderInterface[] as a conditional option, only support callable or ::class invoke [#3904]
|
||||
- remove listing of posts in the show discussion endpoint [#4067]
|
||||
- remove unused vars in catch [#3839]
|
||||
- simplify if else conditions [#3843]
|
||||
- simplify variable assignment using null coalescing operator [#4000]
|
||||
- support composer auth in workflows [#3961]
|
||||
- switch formatter to `format-message` [#4088]
|
||||
- transpile js to es6 [#3699]
|
||||
- unify frontend initializers naming [#4003]
|
||||
- update FontAwesome v6 icon search link [#4036]
|
||||
- use `::class` syntax to fetch class name instead of `get_class()` function [#3910]
|
||||
- use `hex_color` rule for color validation [#3936]
|
||||
- use `str_contains` instead of `strpos` [#3841]
|
||||
- graceful failure from extend/override errors [#4134]
|
||||
### Fixed
|
||||
- (a11y) convert empty links to buttons [#3926]
|
||||
- (approval) `PostWasApproved` event triggered incorrectly [#3930]
|
||||
- (em) prevent use if missing php functions (94b0d67a4020e1b7027e0df53dee1d681dc822e1)
|
||||
- (em) production ready check (f6e84a0dc576d1cfe8539b7438244edf156e3b1a)
|
||||
- (testing) use cookie for testing authentication [#3924]
|
||||
- 3 char hex color is incorrectly modified to 6 [#4013]
|
||||
- Add conditional rendering for email status in MailPage.tsx [#3997]
|
||||
- Logout controller allows open redirects [#3948], [#4091]
|
||||
- allow use of an attribute named `relationships` (1ead69e9b66ae9bc335be663498b7ea706adbf73)
|
||||
- cannot install without db prefix [#4001]
|
||||
- change length of email field [#4118]
|
||||
- color input changes while typing [#3919]
|
||||
- console extender does not accept `::class` attribute for schedule [#3903]
|
||||
- default labels with `SettingDropdown` and `SelectDropdown` [#3854]
|
||||
- duplicated `mentionsUsers` in extend.php (2b56129d70d18686a73d044ff65b418eef83f388)
|
||||
- handled API errors break preloaded content [#3920]
|
||||
- improve the flarum validator [#4133]
|
||||
- installation command config path can be null (07623afacde590c45a20537add3b72a919050819)
|
||||
- larastan changed namespace [#3955]
|
||||
- larastan changes frequently (af2a32a3a4f50874c63785f5168e1df60f947553)
|
||||
- load event mentioned tags on show discussion endpoint [#3915]
|
||||
- move `UserSearchResult` to common (35f76bce60361caac8001c41c421de30f567b221)
|
||||
- notify for all posts switch field loading state [#3938]
|
||||
- only set actor on events that have it [#3914]
|
||||
- overflowing forum stats [#3940]
|
||||
- paginated list limit hard to change [#3918]
|
||||
- prevent wiping out existing database on install [#4092]
|
||||
- reset save button state when an error occurs in admin page [#4010]
|
||||
- usage of `aria-hidden=false` might cause inconsistent behavior [#4074]
|
||||
- use dynamic viewport units for height of Modals [#3951]
|
||||
- user pages history not registered [#3916]
|
||||
- validate required cli install args [#4093]
|
||||
- make `WelcomeHero` extensible [#3848]
|
||||
- Update the scheduler info link in admin (#3826)
|
||||
### Added
|
||||
- Export Registry [#3842]
|
||||
- Code Splitting [#3860]
|
||||
- Fontawesome v6 [#4020]
|
||||
- extension bisect [#3980]
|
||||
- extension list UI [#4066]
|
||||
- admin search UI [#4022]
|
||||
- add support for `PgSQL` [#3985]
|
||||
- add support for `SQLite` [#3984]
|
||||
- separate `MariaDB` driver [#4132]
|
||||
- advanced maintenance modes [#3977]
|
||||
- notification unsubscribe links [#3872]
|
||||
- email overhaul with HTML multipart [#3872]
|
||||
- date time formats from locales [#4029]
|
||||
- messages extension [#4028]
|
||||
- search drivers (revamp search system) [#3893]
|
||||
- search UI/UX revamp [#3941]
|
||||
- post search adapted with global search [#4019]
|
||||
- vanilla CSS color scheme changes [#3996]
|
||||
- (emoji) allow the user to set the CDN address [#3908]
|
||||
- (nicknames) allow OAuth to provide a nickname [#4004]
|
||||
- (sticky) make excerpt optional in sticky [#4016]
|
||||
- JS `Notification` extender [#3974]
|
||||
- add `whenExtensionDisabled` to `Conditional` extender [#3847]
|
||||
- add option for filtering read stickied on all discussions page [#4073]
|
||||
- allow resetting settings to default [#3935]
|
||||
- define `FLARUM_START` constant [#4082]
|
||||
- dispatch event to `flarum/installation-packages` on release [#3625]
|
||||
- eloquent factories (primarily for tests) [#3982]
|
||||
- frontend content flexible order priorities [#3765]
|
||||
- improve emoji autocomplete [#3923]
|
||||
- recover support for ico favicon [#4126]
|
||||
- theming and extensibility improvements [#3876]
|
||||
|
||||
## [v1.8.1](https://github.com/flarum/framework/compare/v1.8.0...v1.8.1)
|
||||
### Fixed
|
||||
* recover temporary solution for html entities in browser title (e72541e35de4f71f9d870bbd9bb46ddf586bdf1d)
|
||||
|
16
bin/test.sh
Normal file
16
bin/test.sh
Normal file
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
|
||||
MONOREPO_TEST="framework/core extensions/akismet extensions/approval extensions/flags extensions/likes extensions/mentions extensions/nicknames extensions/statistics extensions/sticky extensions/subscriptions extensions/suspend extensions/tags extensions/messages php-packages/testing/tests"
|
||||
|
||||
for test in $MONOREPO_TEST; do
|
||||
echo ""
|
||||
echo "===> Testing $test"
|
||||
echo ""
|
||||
|
||||
# composer test:setup --working-dir=$test
|
||||
composer test --working-dir=$test
|
||||
|
||||
echo ""
|
||||
echo "===> Done testing $test"
|
||||
echo ""
|
||||
done
|
@@ -171,8 +171,9 @@
|
||||
"mockery/mockery": "^1.5",
|
||||
"phpunit/phpunit": "^11.0",
|
||||
"phpstan/phpstan": "^1.10.0",
|
||||
"larastan/larastan": "^2.7",
|
||||
"symfony/var-dumper": "^7.0"
|
||||
"larastan/larastan": "2.9.14",
|
||||
"symfony/var-dumper": "^7.0",
|
||||
"flarum/testing-tests": "*@dev"
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
@@ -217,5 +218,11 @@
|
||||
},
|
||||
"scripts-descriptions": {
|
||||
"analyse:phpstan": "Run static analysis"
|
||||
}
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"type": "path",
|
||||
"url": "php-packages/testing/tests"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^2.0",
|
||||
"flarum/core": "^2.0.0-beta.3",
|
||||
"flarum/approval": "^2.0"
|
||||
},
|
||||
"autoload": {
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
|
||||
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
|
||||
backupGlobals="false"
|
||||
cacheDirectory=".phpunit.cache"
|
||||
backupStaticProperties="false"
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
|
||||
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
|
||||
backupGlobals="false"
|
||||
cacheDirectory=".phpunit.cache"
|
||||
backupStaticProperties="false"
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^2.0",
|
||||
"flarum/core": "^2.0.0-beta.3",
|
||||
"flarum/flags": "^2.0"
|
||||
},
|
||||
"autoload": {
|
||||
|
2
extensions/approval/js/dist/forum.js
generated
vendored
2
extensions/approval/js/dist/forum.js
generated
vendored
@@ -1,2 +1,2 @@
|
||||
(()=>{var t={n:o=>{var r=o&&o.__esModule?()=>o.default:()=>o;return t.d(r,{a:r}),r},d:(o,r)=>{for(var e in r)t.o(r,e)&&!t.o(o,e)&&Object.defineProperty(o,e,{enumerable:!0,get:r[e]})},o:(t,o)=>Object.prototype.hasOwnProperty.call(t,o)};(()=>{"use strict";const o=flarum.reg.get("core","common/extend"),r=flarum.reg.get("core","forum/app");var e=t.n(r);const a=flarum.reg.get("core","common/models/Discussion");var n=t.n(a);const p=flarum.reg.get("core","common/models/Post");var s=t.n(p);const i=flarum.reg.get("core","common/components/Badge");var c=t.n(i);const u=flarum.reg.get("core","forum/components/DiscussionListItem");var l=t.n(u);const d=flarum.reg.get("core","forum/components/Post");var v=t.n(d);const f=flarum.reg.get("core","forum/components/CommentPost");var g=t.n(f);const A=flarum.reg.get("core","common/components/Button");var h=t.n(A);const b=flarum.reg.get("core","forum/utils/PostControls");var y=t.n(b);e().initializers.add("flarum-approval",(()=>{n().prototype.isApproved=n().attribute("isApproved"),(0,o.extend)(n().prototype,"badges",(function(t){this.isApproved()||t.has("hidden")||t.add("awaitingApproval",m(c(),{type:"awaitingApproval",icon:"fas fa-gavel",label:e().translator.trans("flarum-approval.forum.badge.awaiting_approval_tooltip")}))})),s().prototype.isApproved=s().attribute("isApproved"),s().prototype.canApprove=s().attribute("canApprove"),(0,o.extend)(l().prototype,"elementAttrs",(function(t){this.attrs.discussion.isApproved()||(t.className+=" DiscussionListItem--unapproved")})),(0,o.extend)(v().prototype,"elementAttrs",(function(t){this.attrs.post.isApproved()||(t.className+=" Post--unapproved")})),(0,o.extend)(g().prototype,"headerItems",(function(t){this.attrs.post.isApproved()||this.attrs.post.isHidden()||t.add("unapproved",e().translator.trans("flarum-approval.forum.post.awaiting_approval_text"))})),(0,o.override)(v().prototype,"flagReason",(function(t,o){return"approval"===o.type()?e().translator.trans("flarum-approval.forum.post.awaiting_approval_text"):t(o)})),(0,o.extend)(y(),"destructiveControls",(function(t,o){!o.isApproved()&&o.canApprove()&&t.add("approve",m(h(),{icon:"fas fa-check",onclick:y().approveAction.bind(o)},e().translator.trans("flarum-approval.forum.post_controls.approve_button")),10)})),y().approveAction=function(){this.save({isApproved:!0}),1===this.number()&&this.discussion().pushAttributes({isApproved:!0})}}),-10)})(),module.exports={}})();
|
||||
(()=>{var t={n:o=>{var r=o&&o.__esModule?()=>o.default:()=>o;return t.d(r,{a:r}),r},d:(o,r)=>{for(var e in r)t.o(r,e)&&!t.o(o,e)&&Object.defineProperty(o,e,{enumerable:!0,get:r[e]})},o:(t,o)=>Object.prototype.hasOwnProperty.call(t,o)};(()=>{"use strict";const o=flarum.reg.get("core","common/extend"),r=flarum.reg.get("core","forum/app");var e=t.n(r);const a=flarum.reg.get("core","common/models/Discussion");var n=t.n(a);const p=flarum.reg.get("core","common/models/Post");var s=t.n(p);const i=flarum.reg.get("core","common/components/Badge");var c=t.n(i);const u=flarum.reg.get("core","forum/components/DiscussionListItem");var l=t.n(u);const d=flarum.reg.get("core","forum/components/Post");var v=t.n(d);const f=flarum.reg.get("core","forum/components/CommentPost");var g=t.n(f);const A=flarum.reg.get("core","common/components/Button");var b=t.n(A);const h=flarum.reg.get("core","forum/utils/PostControls");var y=t.n(h);e().initializers.add("flarum-approval",(()=>{n().prototype.isApproved=n().attribute("isApproved"),(0,o.extend)(n().prototype,"badges",(function(t){this.isApproved()||t.has("hidden")||t.add("awaitingApproval",m(c(),{type:"awaitingApproval",icon:"fas fa-gavel",label:e().translator.trans("flarum-approval.forum.badge.awaiting_approval_tooltip"),tabindex:"0"}))})),s().prototype.isApproved=s().attribute("isApproved"),s().prototype.canApprove=s().attribute("canApprove"),(0,o.extend)(l().prototype,"elementAttrs",(function(t){this.attrs.discussion.isApproved()||(t.className+=" DiscussionListItem--unapproved")})),(0,o.extend)(v().prototype,"elementAttrs",(function(t){this.attrs.post.isApproved()||(t.className+=" Post--unapproved")})),(0,o.extend)(g().prototype,"headerItems",(function(t){this.attrs.post.isApproved()||this.attrs.post.isHidden()||t.add("unapproved",e().translator.trans("flarum-approval.forum.post.awaiting_approval_text"))})),(0,o.override)(v().prototype,"flagReason",(function(t,o){return"approval"===o.type()?e().translator.trans("flarum-approval.forum.post.awaiting_approval_text"):t(o)})),(0,o.extend)(y(),"destructiveControls",(function(t,o){!o.isApproved()&&o.canApprove()&&t.add("approve",m(b(),{icon:"fas fa-check",onclick:y().approveAction.bind(o)},e().translator.trans("flarum-approval.forum.post_controls.approve_button")),10)})),y().approveAction=function(){this.save({isApproved:!0}),1===this.number()&&this.discussion().pushAttributes({isApproved:!0})}}),-10)})(),module.exports={}})();
|
||||
//# sourceMappingURL=forum.js.map
|
2
extensions/approval/js/dist/forum.js.map
generated
vendored
2
extensions/approval/js/dist/forum.js.map
generated
vendored
File diff suppressed because one or more lines are too long
@@ -18,7 +18,12 @@ app.initializers.add(
|
||||
if (!this.isApproved() && !items.has('hidden')) {
|
||||
items.add(
|
||||
'awaitingApproval',
|
||||
<Badge type="awaitingApproval" icon="fas fa-gavel" label={app.translator.trans('flarum-approval.forum.badge.awaiting_approval_tooltip')} />
|
||||
<Badge
|
||||
type="awaitingApproval"
|
||||
icon="fas fa-gavel"
|
||||
label={app.translator.trans('flarum-approval.forum.badge.awaiting_approval_tooltip')}
|
||||
tabindex="0"
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
|
||||
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
|
||||
backupGlobals="false"
|
||||
cacheDirectory=".phpunit.cache"
|
||||
backupStaticProperties="false"
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
|
||||
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
|
||||
backupGlobals="false"
|
||||
cacheDirectory=".phpunit.cache"
|
||||
backupStaticProperties="false"
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^2.0"
|
||||
"flarum/core": "^2.0.0-beta.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^2.0"
|
||||
"flarum/core": "^2.0.0-beta.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
2
extensions/embed/js/dist/forum.js
generated
vendored
2
extensions/embed/js/dist/forum.js
generated
vendored
File diff suppressed because one or more lines are too long
2
extensions/embed/js/dist/forum.js.map
generated
vendored
2
extensions/embed/js/dist/forum.js.map
generated
vendored
File diff suppressed because one or more lines are too long
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^2.0"
|
||||
"flarum/core": "^2.0.0-beta.3"
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
|
2
extensions/emoji/js/dist/forum.js
generated
vendored
2
extensions/emoji/js/dist/forum.js
generated
vendored
File diff suppressed because one or more lines are too long
2
extensions/emoji/js/dist/forum.js.map
generated
vendored
2
extensions/emoji/js/dist/forum.js.map
generated
vendored
File diff suppressed because one or more lines are too long
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^2.0"
|
||||
"flarum/core": "^2.0.0-beta.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
@@ -37,10 +37,8 @@ class ScopeFlagVisibility
|
||||
if ($actor->hasPermission('discussion.viewFlags')) {
|
||||
$query->orWhereDoesntHave('post.discussion.tags');
|
||||
}
|
||||
}
|
||||
|
||||
if (! $actor->hasPermission('discussion.viewFlags')) {
|
||||
$query->orWhere('flags.user_id', $actor->id);
|
||||
} elseif (! $actor->hasPermission('discussion.viewFlags')) {
|
||||
$query->whereRaw('1 = 0');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@@ -96,7 +96,7 @@ class ListTest extends TestCase
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function regular_user_sees_own_flags_of_visible_posts()
|
||||
public function regular_user_does_not_see_own_flags_of_visible_posts()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/flags', [
|
||||
@@ -109,7 +109,7 @@ class ListTest extends TestCase
|
||||
$data = json_decode($response->getBody()->getContents(), true)['data'];
|
||||
|
||||
$ids = Arr::pluck($data, 'id');
|
||||
$this->assertEqualsCanonicalizing(['2', '4'], $ids);
|
||||
$this->assertEqualsCanonicalizing([], $ids);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
|
@@ -122,7 +122,7 @@ class ListWithTagsTest extends TestCase
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function regular_user_sees_own_flags()
|
||||
public function regular_user_does_not_see_own_flags()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/flags', [
|
||||
@@ -135,7 +135,7 @@ class ListWithTagsTest extends TestCase
|
||||
$data = json_decode($response->getBody()->getContents(), true)['data'];
|
||||
|
||||
$ids = Arr::pluck($data, 'id');
|
||||
$this->assertEqualsCanonicalizing(['2', '4'], $ids);
|
||||
$this->assertEqualsCanonicalizing([], $ids);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
|
@@ -144,7 +144,7 @@ class IncludeFlagsVisibilityTest extends TestCase
|
||||
'user_with_general_permission_sees_where_unrestricted_tag' => [2, [6, 7, 8]],
|
||||
'user_with_tag1_permission_sees_tag1_flags' => [3, [1, 2, 3, 4, 5]],
|
||||
'normal_user_sees_none' => [4, []],
|
||||
'normal_user_sees_own' => [5, [2, 7, 4, 8]],
|
||||
'normal_user_does_not_see_own' => [5, []],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
|
||||
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
|
||||
backupGlobals="false"
|
||||
cacheDirectory=".phpunit.cache"
|
||||
backupStaticProperties="false"
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
|
||||
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
|
||||
backupGlobals="false"
|
||||
cacheDirectory=".phpunit.cache"
|
||||
backupStaticProperties="false"
|
||||
|
@@ -7,7 +7,7 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"flarum/core": "^2.0"
|
||||
"flarum/core": "^2.0.0-beta.3"
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^2.0"
|
||||
"flarum/core": "^2.0.0-beta.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
@@ -64,7 +64,7 @@ class LikePostTest extends TestCase
|
||||
|
||||
#[Test]
|
||||
#[DataProvider('allowedUsersToLike')]
|
||||
public function can_like_a_post_if_allowed(int $postId, ?int $authenticatedAs, string $message, bool $canLikeOwnPost = null)
|
||||
public function can_like_a_post_if_allowed(int $postId, ?int $authenticatedAs, string $message, ?bool $canLikeOwnPost = null)
|
||||
{
|
||||
if (! is_null($canLikeOwnPost)) {
|
||||
$this->setting('flarum-likes.like_own_post', $canLikeOwnPost);
|
||||
@@ -82,7 +82,7 @@ class LikePostTest extends TestCase
|
||||
|
||||
#[Test]
|
||||
#[DataProvider('unallowedUsersToLike')]
|
||||
public function cannot_like_a_post_if_not_allowed(int $postId, ?int $authenticatedAs, string $message, bool $canLikeOwnPost = null)
|
||||
public function cannot_like_a_post_if_not_allowed(int $postId, ?int $authenticatedAs, string $message, ?bool $canLikeOwnPost = null)
|
||||
{
|
||||
if (! is_null($canLikeOwnPost)) {
|
||||
$this->setting('flarum-likes.like_own_post', $canLikeOwnPost);
|
||||
@@ -100,7 +100,7 @@ class LikePostTest extends TestCase
|
||||
|
||||
#[Test]
|
||||
#[DataProvider('allowedUsersToLike')]
|
||||
public function can_dislike_a_post_if_liked_and_allowed(int $postId, ?int $authenticatedAs, string $message, bool $canLikeOwnPost = null)
|
||||
public function can_dislike_a_post_if_liked_and_allowed(int $postId, ?int $authenticatedAs, string $message, ?bool $canLikeOwnPost = null)
|
||||
{
|
||||
if (! is_null($canLikeOwnPost)) {
|
||||
$this->setting('flarum-likes.like_own_post', $canLikeOwnPost);
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
|
||||
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
|
||||
backupGlobals="false"
|
||||
cacheDirectory=".phpunit.cache"
|
||||
backupStaticProperties="false"
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
|
||||
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
|
||||
backupGlobals="false"
|
||||
cacheDirectory=".phpunit.cache"
|
||||
backupStaticProperties="false"
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^2.0"
|
||||
"flarum/core": "^2.0.0-beta.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
2
extensions/lock/js/dist/forum.js
generated
vendored
2
extensions/lock/js/dist/forum.js
generated
vendored
@@ -1,2 +1,2 @@
|
||||
(()=>{var o={n:e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return o.d(t,{a:t}),t},d:(e,t)=>{for(var n in t)o.o(t,n)&&!o.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},o:(o,e)=>Object.prototype.hasOwnProperty.call(o,e),r:o=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(o,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(o,"__esModule",{value:!0})}},e={};(()=>{"use strict";o.r(e),o.d(e,{extend:()=>N});const t=flarum.reg.get("core","common/extend"),n=flarum.reg.get("core","forum/app");var r=o.n(n);const s=flarum.reg.get("core","common/models/Discussion");var c=o.n(s);const a=flarum.reg.get("core","common/components/Badge");var i=o.n(a);const l=flarum.reg.get("core","forum/utils/DiscussionControls");var u=o.n(l);const d=flarum.reg.get("core","forum/components/DiscussionPage");var f=o.n(d);const k=flarum.reg.get("core","common/components/Button");var g=o.n(k);const p=flarum.reg.get("core","common/extenders");var b=o.n(p);const y=flarum.reg.get("core","forum/components/EventPost");var _=o.n(y);class v extends(_()){icon(){return this.attrs.post.content().locked?"fas fa-lock":"fas fa-unlock"}descriptionKey(){return this.attrs.post.content().locked?"flarum-lock.forum.post_stream.discussion_locked_text":"flarum-lock.forum.post_stream.discussion_unlocked_text"}}flarum.reg.add("flarum-lock","forum/components/DiscussionLockedPost",v);const x=flarum.reg.get("core","common/query/IGambit"),L=flarum.reg.get("core","common/app");var h=o.n(L);class P extends x.BooleanGambit{key(){return h().translator.trans("flarum-lock.lib.gambits.discussions.locked.key",{},!0)}filterKey(){return"locked"}}flarum.reg.add("flarum-lock","common/query/discussions/LockedGambit",P);const w=[(new(b().Search)).gambit("discussions",P)],S=flarum.reg.get("core","forum/components/Notification");var j=o.n(S);class D extends(j()){icon(){return"fas fa-lock"}href(){const o=this.attrs.notification;return r().route.discussion(o.subject(),o.content().postNumber)}content(){return r().translator.trans("flarum-lock.forum.notifications.discussion_locked_text",{user:this.attrs.notification.fromUser()})}excerpt(){return null}}flarum.reg.add("flarum-lock","forum/components/DiscussionLockedNotification",D);const N=[...w,(new(b().PostTypes)).add("discussionLocked",v),(new(b().Notification)).add("discussionLocked",D),new(b().Model)(c()).attribute("isLocked").attribute("canLock")];r().initializers.add("flarum-lock",(()=>{(0,t.extend)(c().prototype,"badges",(function(o){this.isLocked()&&o.add("locked",m(i(),{type:"locked",label:r().translator.trans("flarum-lock.forum.badge.locked_tooltip"),icon:"fas fa-lock"}))})),(0,t.extend)(u(),"moderationControls",(function(o,e){e.canLock()&&o.add("lock",m(g(),{icon:"fas fa-lock",onclick:this.lockAction.bind(e)},r().translator.trans(`flarum-lock.forum.discussion_controls.${e.isLocked()?"unlock":"lock"}_button`)))})),u().lockAction=function(){this.save({isLocked:!this.isLocked()}).then((()=>{r().current.matches(f())&&r().current.get("stream").update(),m.redraw()}))},(0,t.extend)("flarum/forum/components/NotificationGrid","notificationTypes",(function(o){o.add("discussionLocked",{name:"discussionLocked",icon:"fas fa-lock",label:r().translator.trans("flarum-lock.forum.settings.notify_discussion_locked_label")})}))}))})(),module.exports=e})();
|
||||
(()=>{var o={n:e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return o.d(t,{a:t}),t},d:(e,t)=>{for(var n in t)o.o(t,n)&&!o.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},o:(o,e)=>Object.prototype.hasOwnProperty.call(o,e),r:o=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(o,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(o,"__esModule",{value:!0})}},e={};(()=>{"use strict";o.r(e),o.d(e,{extend:()=>N});const t=flarum.reg.get("core","common/extend"),n=flarum.reg.get("core","forum/app");var r=o.n(n);const s=flarum.reg.get("core","common/models/Discussion");var c=o.n(s);const a=flarum.reg.get("core","common/components/Badge");var i=o.n(a);const l=flarum.reg.get("core","forum/utils/DiscussionControls");var u=o.n(l);const d=flarum.reg.get("core","forum/components/DiscussionPage");var f=o.n(d);const k=flarum.reg.get("core","common/components/Button");var g=o.n(k);const p=flarum.reg.get("core","common/extenders");var b=o.n(p);const y=flarum.reg.get("core","forum/components/EventPost");var _=o.n(y);class v extends(_()){icon(){return this.attrs.post.content().locked?"fas fa-lock":"fas fa-unlock"}descriptionKey(){return this.attrs.post.content().locked?"flarum-lock.forum.post_stream.discussion_locked_text":"flarum-lock.forum.post_stream.discussion_unlocked_text"}}flarum.reg.add("flarum-lock","forum/components/DiscussionLockedPost",v);const x=flarum.reg.get("core","common/query/IGambit"),L=flarum.reg.get("core","common/app");var h=o.n(L);class P extends x.BooleanGambit{key(){return h().translator.trans("flarum-lock.lib.gambits.discussions.locked.key",{},!0)}filterKey(){return"locked"}}flarum.reg.add("flarum-lock","common/query/discussions/LockedGambit",P);const w=[(new(b().Search)).gambit("discussions",P)],S=flarum.reg.get("core","forum/components/Notification");var j=o.n(S);class D extends(j()){icon(){return"fas fa-lock"}href(){const o=this.attrs.notification;return r().route.discussion(o.subject(),o.content().postNumber)}content(){return r().translator.trans("flarum-lock.forum.notifications.discussion_locked_text",{user:this.attrs.notification.fromUser()})}excerpt(){return null}}flarum.reg.add("flarum-lock","forum/components/DiscussionLockedNotification",D);const N=[...w,(new(b().PostTypes)).add("discussionLocked",v),(new(b().Notification)).add("discussionLocked",D),new(b().Model)(c()).attribute("isLocked").attribute("canLock")];r().initializers.add("flarum-lock",(()=>{(0,t.extend)(c().prototype,"badges",(function(o){this.isLocked()&&o.add("locked",m(i(),{type:"locked",label:r().translator.trans("flarum-lock.forum.badge.locked_tooltip"),icon:"fas fa-lock",tabindex:"0"}))})),(0,t.extend)(u(),"moderationControls",(function(o,e){e.canLock()&&o.add("lock",m(g(),{icon:"fas fa-lock",onclick:this.lockAction.bind(e)},r().translator.trans(`flarum-lock.forum.discussion_controls.${e.isLocked()?"unlock":"lock"}_button`)))})),u().lockAction=function(){this.save({isLocked:!this.isLocked()}).then((()=>{r().current.matches(f())&&r().current.get("stream").update(),m.redraw()}))},(0,t.extend)("flarum/forum/components/NotificationGrid","notificationTypes",(function(o){o.add("discussionLocked",{name:"discussionLocked",icon:"fas fa-lock",label:r().translator.trans("flarum-lock.forum.settings.notify_discussion_locked_label")})}))}))})(),module.exports=e})();
|
||||
//# sourceMappingURL=forum.js.map
|
2
extensions/lock/js/dist/forum.js.map
generated
vendored
2
extensions/lock/js/dist/forum.js.map
generated
vendored
File diff suppressed because one or more lines are too long
@@ -6,7 +6,10 @@ import Badge from 'flarum/common/components/Badge';
|
||||
export default function addLockBadge() {
|
||||
extend(Discussion.prototype, 'badges', function (badges) {
|
||||
if (this.isLocked()) {
|
||||
badges.add('locked', <Badge type="locked" label={app.translator.trans('flarum-lock.forum.badge.locked_tooltip')} icon="fas fa-lock" />);
|
||||
badges.add(
|
||||
'locked',
|
||||
<Badge type="locked" label={app.translator.trans('flarum-lock.forum.badge.locked_tooltip')} icon="fas fa-lock" tabindex="0" />
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@@ -18,7 +18,7 @@ class DiscussionLockedPost extends AbstractEventPost implements MergeableInterfa
|
||||
{
|
||||
public static string $type = 'discussionLocked';
|
||||
|
||||
public function saveAfter(Post $previous = null): static
|
||||
public function saveAfter(?Post $previous = null): static
|
||||
{
|
||||
// If the previous post is another 'discussion locked' post, and it's
|
||||
// by the same user, then we can merge this post into it. If we find
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^2.0"
|
||||
"flarum/core": "^2.0.0-beta.3"
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^2.0"
|
||||
"flarum/core": "^2.0.0-beta.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
@@ -25,9 +25,13 @@ class FormatGroupMentions
|
||||
public function __invoke(Renderer $renderer, mixed $context, string $xml): string
|
||||
{
|
||||
return Utils::replaceAttributes($xml, 'GROUPMENTION', function ($attributes) use ($context) {
|
||||
$group = ($context instanceof AbstractModel && $context->isRelation('mentionsGroups'))
|
||||
? $context->mentionsGroups->find($attributes['id']) // @phpstan-ignore-line
|
||||
: Group::find($attributes['id']);
|
||||
/** @var Group|null $group */
|
||||
$group = match (true) {
|
||||
$context instanceof AbstractModel && $context->isRelation('mentionsGroups') => $context->relationLoaded('mentionsGroups')
|
||||
? $context->mentionsGroups->find($attributes['id']) // @phpstan-ignore-line
|
||||
: $context->mentionsGroups()->find($attributes['id']), // @phpstan-ignore-line
|
||||
default => Group::query()->find($attributes['id']),
|
||||
};
|
||||
|
||||
if ($group) {
|
||||
$attributes['groupname'] = $group->name_plural;
|
||||
|
@@ -31,9 +31,13 @@ class FormatPostMentions
|
||||
public function __invoke(Renderer $renderer, mixed $context, string $xml): string
|
||||
{
|
||||
return Utils::replaceAttributes($xml, 'POSTMENTION', function ($attributes) use ($context) {
|
||||
$post = ($context instanceof AbstractModel && $context->isRelation('mentionsPosts'))
|
||||
? $context->mentionsPosts->find($attributes['id']) // @phpstan-ignore-line
|
||||
: Post::find($attributes['id']);
|
||||
/** @var Post|null $post */
|
||||
$post = match (true) {
|
||||
$context instanceof AbstractModel && $context->isRelation('mentionsPosts') => $context->relationLoaded('mentionsPosts')
|
||||
? $context->mentionsPosts->find($attributes['id']) // @phpstan-ignore-line
|
||||
: $context->mentionsPosts()->find($attributes['id']), // @phpstan-ignore-line
|
||||
default => Post::query()->find($attributes['id']),
|
||||
};
|
||||
|
||||
if ($post && $post->user) {
|
||||
$attributes['displayname'] = $post->user->display_name;
|
||||
|
@@ -17,13 +17,16 @@ use s9e\TextFormatter\Utils;
|
||||
|
||||
class FormatTagMentions
|
||||
{
|
||||
public function __invoke(Renderer $renderer, mixed $context, string $xml, Request $request = null): string
|
||||
public function __invoke(Renderer $renderer, mixed $context, string $xml, ?Request $request = null): string
|
||||
{
|
||||
return Utils::replaceAttributes($xml, 'TAGMENTION', function ($attributes) use ($context) {
|
||||
/** @var Tag|null $tag */
|
||||
$tag = ($context instanceof AbstractModel && $context->isRelation('mentionsTags'))
|
||||
? $context->mentionsTags->find($attributes['id']) // @phpstan-ignore-line
|
||||
: Tag::query()->find($attributes['id']);
|
||||
$tag = match (true) {
|
||||
$context instanceof AbstractModel && $context->isRelation('mentionsTags') => $context->relationLoaded('mentionsTags')
|
||||
? $context->mentionsTags->find($attributes['id']) // @phpstan-ignore-line
|
||||
: $context->mentionsTags()->find($attributes['id']), // @phpstan-ignore-line
|
||||
default => Tag::query()->find($attributes['id']),
|
||||
};
|
||||
|
||||
if ($tag) {
|
||||
$attributes['deleted'] = false;
|
||||
|
@@ -27,9 +27,13 @@ class FormatUserMentions
|
||||
public function __invoke(Renderer $renderer, mixed $context, string $xml): string
|
||||
{
|
||||
return Utils::replaceAttributes($xml, 'USERMENTION', function ($attributes) use ($context) {
|
||||
$user = ($context instanceof AbstractModel && $context->isRelation('mentionsUsers'))
|
||||
? $context->mentionsUsers->find($attributes['id']) // @phpstan-ignore-line
|
||||
: User::find($attributes['id']);
|
||||
/** @var User|null $user */
|
||||
$user = match (true) {
|
||||
$context instanceof AbstractModel && $context->isRelation('mentionsUsers') => $context->relationLoaded('mentionsUsers')
|
||||
? $context->mentionsUsers->find($attributes['id']) // @phpstan-ignore-line
|
||||
: $context->mentionsUsers()->find($attributes['id']), // @phpstan-ignore-line
|
||||
default => User::query()->find($attributes['id']),
|
||||
};
|
||||
|
||||
$attributes['deleted'] = false;
|
||||
|
||||
|
@@ -34,9 +34,13 @@ class UnparsePostMentions
|
||||
protected function updatePostMentionTags(mixed $context, string $xml): string
|
||||
{
|
||||
return Utils::replaceAttributes($xml, 'POSTMENTION', function ($attributes) use ($context) {
|
||||
$post = ($context instanceof AbstractModel && $context->isRelation('mentionsPosts'))
|
||||
? $context->mentionsPosts->find($attributes['id']) // @phpstan-ignore-line
|
||||
: Post::find($attributes['id']);
|
||||
/** @var Post|null $post */
|
||||
$post = match (true) {
|
||||
$context instanceof AbstractModel && $context->isRelation('mentionsPosts') => $context->relationLoaded('mentionsPosts')
|
||||
? $context->mentionsPosts->find($attributes['id']) // @phpstan-ignore-line
|
||||
: $context->mentionsPosts()->find($attributes['id']), // @phpstan-ignore-line
|
||||
default => Post::query()->find($attributes['id']),
|
||||
};
|
||||
|
||||
if ($post && $post->user) {
|
||||
$attributes['displayname'] = $post->user->display_name;
|
||||
|
@@ -29,9 +29,12 @@ class UnparseTagMentions
|
||||
{
|
||||
return Utils::replaceAttributes($xml, 'TAGMENTION', function (array $attributes) use ($context) {
|
||||
/** @var Tag|null $tag */
|
||||
$tag = ($context instanceof AbstractModel && $context->isRelation('mentionsTags'))
|
||||
? $context->mentionsTags->find($attributes['id']) // @phpstan-ignore-line
|
||||
: Tag::query()->find($attributes['id']);
|
||||
$tag = match (true) {
|
||||
$context instanceof AbstractModel && $context->isRelation('mentionsTags') => $context->relationLoaded('mentionsTags')
|
||||
? $context->mentionsTags->find($attributes['id']) // @phpstan-ignore-line
|
||||
: $context->mentionsTags()->find($attributes['id']), // @phpstan-ignore-line
|
||||
default => Tag::query()->find($attributes['id']),
|
||||
};
|
||||
|
||||
if ($tag) {
|
||||
$attributes['tagname'] = $tag->name;
|
||||
|
@@ -34,9 +34,13 @@ class UnparseUserMentions
|
||||
protected function updateUserMentionTags(mixed $context, string $xml): string
|
||||
{
|
||||
return Utils::replaceAttributes($xml, 'USERMENTION', function ($attributes) use ($context) {
|
||||
$user = ($context instanceof AbstractModel && $context->isRelation('mentionsUsers'))
|
||||
? $context->mentionsUsers->find($attributes['id']) // @phpstan-ignore-line
|
||||
: User::find($attributes['id']);
|
||||
/** @var User|null $user */
|
||||
$user = match (true) {
|
||||
$context instanceof AbstractModel && $context->isRelation('mentionsUsers') => $context->relationLoaded('mentionsUsers')
|
||||
? $context->mentionsUsers->find($attributes['id']) // @phpstan-ignore-line
|
||||
: $context->mentionsUsers()->find($attributes['id']), // @phpstan-ignore-line
|
||||
default => User::query()->find($attributes['id']),
|
||||
};
|
||||
|
||||
$attributes['displayname'] = $user?->display_name ?? $this->translator->trans('core.lib.username.deleted_text');
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
|
||||
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
|
||||
backupGlobals="false"
|
||||
cacheDirectory=".phpunit.cache"
|
||||
backupStaticProperties="false"
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
|
||||
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
|
||||
backupGlobals="false"
|
||||
cacheDirectory=".phpunit.cache"
|
||||
backupStaticProperties="false"
|
||||
|
@@ -7,7 +7,7 @@
|
||||
"type": "flarum-extension",
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"flarum/core": "^2.0"
|
||||
"flarum/core": "^2.0.0-beta.3"
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
|
@@ -24,7 +24,7 @@ return [
|
||||
->css(__DIR__.'/less/forum.less')
|
||||
->jsDirectory(__DIR__.'/js/dist/forum')
|
||||
->route('/messages', 'messages')
|
||||
->route('/messages/dialog/{id:\d+}', 'messages.dialog'),
|
||||
->route('/messages/dialog/{id:\d+}[/{near:\d+}]', 'messages.dialog'),
|
||||
|
||||
(new Extend\Frontend('admin'))
|
||||
->js(__DIR__.'/js/dist/admin.js')
|
||||
@@ -51,7 +51,9 @@ return [
|
||||
(new Extend\ApiResource(Resource\UserResource::class))
|
||||
->fields(fn () => [
|
||||
Schema\Boolean::make('canSendAnyMessage')
|
||||
->get(fn (object $model, Context $context) => $context->getActor()->can('sendAnyMessage')),
|
||||
->get(fn (User $user, Context $context) => $user->can('sendAnyMessage')),
|
||||
Schema\Boolean::make('canDeleteOwnMessages')
|
||||
->visible(fn (User $user, Context $context) => $context->getActor()->is($user)),
|
||||
Schema\Integer::make('messageCount')
|
||||
->get(function (object $model, Context $context) {
|
||||
return Dialog::whereVisibleTo($context->getActor())
|
||||
|
@@ -3,7 +3,7 @@ import DialogListState from '../forum/states/DialogListState';
|
||||
|
||||
declare module 'flarum/forum/routes' {
|
||||
export interface ForumRoutes {
|
||||
dialog: (tag: Dialog) => string;
|
||||
dialog: (dialog: Dialog, near?: number) => string;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,3 +19,9 @@ declare module 'flarum/forum/states/ComposerState' {
|
||||
composingMessageTo(dialog: Dialog): boolean;
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'flarum/common/models/User' {
|
||||
export default interface User {
|
||||
canSendAnyMessage(): boolean;
|
||||
}
|
||||
}
|
2
extensions/messages/js/dist-typings/admin/extend.d.ts
generated
vendored
2
extensions/messages/js/dist-typings/admin/extend.d.ts
generated
vendored
@@ -1,2 +1,2 @@
|
||||
declare const _default: (import("flarum/common/extenders/Store").default | import("flarum/common/extenders/Admin").default)[];
|
||||
declare const _default: (import("flarum/common/extenders/Store").default | import("flarum/common/extenders/Model").default | import("flarum/common/extenders/Admin").default)[];
|
||||
export default _default;
|
||||
|
2
extensions/messages/js/dist-typings/common/extend.d.ts
generated
vendored
2
extensions/messages/js/dist-typings/common/extend.d.ts
generated
vendored
@@ -1,2 +1,2 @@
|
||||
declare const _default: import("flarum/common/extenders/Store").default[];
|
||||
declare const _default: (import("flarum/common/extenders/Store").default | import("flarum/common/extenders/Model").default)[];
|
||||
export default _default;
|
||||
|
2
extensions/messages/js/dist-typings/common/models/DialogMessage.d.ts
generated
vendored
2
extensions/messages/js/dist-typings/common/models/DialogMessage.d.ts
generated
vendored
@@ -2,6 +2,7 @@ import Model from 'flarum/common/Model';
|
||||
import type Dialog from './Dialog';
|
||||
import type User from 'flarum/common/models/User';
|
||||
export default class DialogMessage extends Model {
|
||||
number(): number;
|
||||
content(): string | null | undefined;
|
||||
contentHtml(): string | null | undefined;
|
||||
renderFailed(): boolean | undefined;
|
||||
@@ -9,4 +10,5 @@ export default class DialogMessage extends Model {
|
||||
createdAt(): Date;
|
||||
dialog(): false | Dialog;
|
||||
user(): false | User;
|
||||
canDelete(): boolean;
|
||||
}
|
||||
|
1
extensions/messages/js/dist-typings/forum/components/DialogSection.d.ts
generated
vendored
1
extensions/messages/js/dist-typings/forum/components/DialogSection.d.ts
generated
vendored
@@ -10,6 +10,7 @@ export default class DialogSection<CustomAttrs extends IDialogStreamAttrs = IDia
|
||||
protected loading: boolean;
|
||||
protected messages: MessageStreamState;
|
||||
oninit(vnode: Mithril.Vnode<CustomAttrs, this>): void;
|
||||
requestParams(forgetNear?: boolean): any;
|
||||
view(): JSX.Element;
|
||||
actionItems(): ItemList<Mithril.Children>;
|
||||
controlItems(): ItemList<Mithril.Children>;
|
||||
|
2
extensions/messages/js/dist-typings/forum/components/Message.d.ts
generated
vendored
2
extensions/messages/js/dist-typings/forum/components/Message.d.ts
generated
vendored
@@ -3,8 +3,10 @@ import Mithril from 'mithril';
|
||||
import AbstractPost, { type IAbstractPostAttrs } from 'flarum/forum/components/AbstractPost';
|
||||
import type User from 'flarum/common/models/User';
|
||||
import DialogMessage from '../../common/models/DialogMessage';
|
||||
import type MessageStreamState from '../states/MessageStreamState';
|
||||
export interface IMessageAttrs extends IAbstractPostAttrs {
|
||||
message: DialogMessage;
|
||||
state: MessageStreamState;
|
||||
}
|
||||
/**
|
||||
* The `Post` component displays a single post. The basic post template just
|
||||
|
4
extensions/messages/js/dist-typings/forum/components/MessagesPage.d.ts
generated
vendored
4
extensions/messages/js/dist-typings/forum/components/MessagesPage.d.ts
generated
vendored
@@ -7,6 +7,7 @@ export interface IMessagesPageAttrs extends IPageAttrs {
|
||||
}
|
||||
export default class MessagesPage<CustomAttrs extends IMessagesPageAttrs = IMessagesPageAttrs> extends Page<CustomAttrs> {
|
||||
protected selectedDialog: Stream<Dialog | null>;
|
||||
protected currentDialogId: string | null;
|
||||
oninit(vnode: Mithril.Vnode<CustomAttrs, this>): void;
|
||||
dialogRequestParams(): {
|
||||
include: string;
|
||||
@@ -15,6 +16,7 @@ export default class MessagesPage<CustomAttrs extends IMessagesPageAttrs = IMess
|
||||
onupdate(vnode: Mithril.VnodeDOM<CustomAttrs, this>): void;
|
||||
view(): JSX.Element;
|
||||
hero(): Mithril.Children;
|
||||
contentItems(): ItemList<Mithril.Children>;
|
||||
/**
|
||||
* Build an item list for the part of the toolbar which is concerned with how
|
||||
* the results are displayed. By default this is just a select box to change
|
||||
@@ -23,7 +25,7 @@ export default class MessagesPage<CustomAttrs extends IMessagesPageAttrs = IMess
|
||||
viewItems(): ItemList<Mithril.Children>;
|
||||
/**
|
||||
* Build an item list for the part of the toolbar which is about taking action
|
||||
* on the results. By default this is just a "mark all as read" button.
|
||||
* on the results. By default, this is just a "mark all as read" button.
|
||||
*/
|
||||
actionItems(): ItemList<Mithril.Children>;
|
||||
}
|
||||
|
2
extensions/messages/js/dist-typings/forum/extend.d.ts
generated
vendored
2
extensions/messages/js/dist-typings/forum/extend.d.ts
generated
vendored
@@ -1,2 +1,2 @@
|
||||
declare const _default: (import("flarum/common/extenders/Store").default | import("flarum/common/extenders/Routes").default)[];
|
||||
declare const _default: (import("flarum/common/extenders/Store").default | import("flarum/common/extenders/Model").default | import("flarum/common/extenders/Routes").default)[];
|
||||
export default _default;
|
||||
|
17
extensions/messages/js/dist-typings/forum/utils/MessageControls.d.ts
generated
vendored
Normal file
17
extensions/messages/js/dist-typings/forum/utils/MessageControls.d.ts
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
import ItemList from 'flarum/common/utils/ItemList';
|
||||
import type Mithril from 'mithril';
|
||||
import type DialogMessage from '../../common/models/DialogMessage';
|
||||
import type Message from '../components/Message';
|
||||
declare const MessageControls: {
|
||||
controls(message: DialogMessage, context: Message<any>): ItemList<Mithril.Children>;
|
||||
sections(): {
|
||||
user: (message: DialogMessage, context: Message) => ItemList<Mithril.Children>;
|
||||
moderation: (message: DialogMessage, context: Message) => ItemList<Mithril.Children>;
|
||||
destructive: (message: DialogMessage, context: Message) => ItemList<Mithril.Children>;
|
||||
};
|
||||
userControls(message: DialogMessage, context: Message): ItemList<Mithril.Children>;
|
||||
moderationControls(message: DialogMessage, context: Message): ItemList<Mithril.Children>;
|
||||
destructiveControls(message: DialogMessage, context: Message): ItemList<Mithril.Children>;
|
||||
deleteAction(message: DialogMessage, context: Message): Promise<void> | undefined;
|
||||
};
|
||||
export default MessageControls;
|
2
extensions/messages/js/dist/admin.js
generated
vendored
2
extensions/messages/js/dist/admin.js
generated
vendored
@@ -1,2 +1,2 @@
|
||||
(()=>{var e={n:t=>{var r=t&&t.__esModule?()=>t.default:()=>t;return e.d(r,{a:r}),r},d:(t,r)=>{for(var a in r)e.o(r,a)&&!e.o(t,a)&&Object.defineProperty(t,a,{enumerable:!0,get:r[a]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r:e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})}},t={};(()=>{"use strict";e.r(t),e.d(t,{extend:()=>h});const r=flarum.reg.get("core","admin/app");var a=e.n(r);const s=flarum.reg.get("core","common/extenders");var n=e.n(s);const l=flarum.reg.get("core","common/Model");var o=e.n(l);const i=flarum.reg.get("core","common/utils/computed");var u=e.n(i);const d=flarum.reg.get("core","common/utils/string");class c extends(o()){content(){return o().attribute("content").call(this)}contentHtml(){return o().attribute("contentHtml").call(this)}renderFailed(){return o().attribute("renderFailed").call(this)}contentPlain(){return u()("contentHtml",(e=>"string"==typeof e?(0,d.getPlainContent)(e):e)).call(this)}createdAt(){return o().attribute("createdAt",o().transformDate).call(this)}dialog(){return o().hasOne("dialog").call(this)}user(){return o().hasOne("user").call(this)}}flarum.reg.add("flarum-messages","common/models/DialogMessage",c);const m=flarum.reg.get("core","common/app");var g=e.n(m);class f extends(o()){title(){return o().attribute("title").call(this)}type(){return o().attribute("type").call(this)}lastMessageAt(){return o().attribute("lastMessageAt",o().transformDate).call(this)}createdAt(){return o().attribute("createdAt",o().transformDate).call(this)}users(){return o().hasMany("users").call(this)}firstMessage(){return o().hasOne("firstMessage").call(this)}lastMessage(){return o().hasOne("lastMessage").call(this)}unreadCount(){return o().attribute("unreadCount").call(this)}lastReadMessageId(){return o().attribute("lastReadMessageId").call(this)}lastReadAt(){return o().attribute("lastReadAt",o().transformDate).call(this)}recipient(){let e=this.users();return e?e.find((e=>e&&e.id()!==g().session.user.id())):null}}flarum.reg.add("flarum-messages","common/models/Dialog",f);const h=[(new(n().Store)).add("dialogs",f).add("dialog-messages",c),(new(n().Admin)).permission((()=>({icon:"fas fa-envelope-open-text",label:a().translator.trans("flarum-messages.admin.permissions.send_messages"),permission:"dialog.sendMessage",allowGuest:!1})),"start",98)];a().initializers.add("flarum-messages",(()=>{}))})(),module.exports=t})();
|
||||
(()=>{var e={n:t=>{var a=t&&t.__esModule?()=>t.default:()=>t;return e.d(a,{a}),a},d:(t,a)=>{for(var s in a)e.o(a,s)&&!e.o(t,s)&&Object.defineProperty(t,s,{enumerable:!0,get:a[s]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r:e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})}},t={};(()=>{"use strict";e.r(t),e.d(t,{extend:()=>w});const a=flarum.reg.get("core","admin/app");var s=e.n(a);const r=flarum.reg.get("core","common/extenders");var n=e.n(r);const l=flarum.reg.get("core","common/Model");var o=e.n(l);const i=flarum.reg.get("core","common/utils/computed");var u=e.n(i);const c=flarum.reg.get("core","common/utils/string");class d extends(o()){number(){return o().attribute("number").call(this)}content(){return o().attribute("content").call(this)}contentHtml(){return o().attribute("contentHtml").call(this)}renderFailed(){return o().attribute("renderFailed").call(this)}contentPlain(){return u()("contentHtml",(e=>"string"==typeof e?(0,c.getPlainContent)(e):e)).call(this)}createdAt(){return o().attribute("createdAt",o().transformDate).call(this)}dialog(){return o().hasOne("dialog").call(this)}user(){return o().hasOne("user").call(this)}canDelete(){return o().attribute("canDelete").call(this)}}flarum.reg.add("flarum-messages","common/models/DialogMessage",d);const g=flarum.reg.get("core","common/app");var f=e.n(g);class b extends(o()){title(){return o().attribute("title").call(this)}type(){return o().attribute("type").call(this)}lastMessageAt(){return o().attribute("lastMessageAt",o().transformDate).call(this)}createdAt(){return o().attribute("createdAt",o().transformDate).call(this)}users(){return o().hasMany("users").call(this)}firstMessage(){return o().hasOne("firstMessage").call(this)}lastMessage(){return o().hasOne("lastMessage").call(this)}unreadCount(){return o().attribute("unreadCount").call(this)}lastReadMessageId(){return o().attribute("lastReadMessageId").call(this)}lastReadAt(){return o().attribute("lastReadAt",o().transformDate).call(this)}recipient(){let e=this.users();return e?e.find((e=>e&&e.id()!==f().session.user.id())):null}}flarum.reg.add("flarum-messages","common/models/Dialog",b);const p=flarum.reg.get("core","common/models/User");var _=e.n(p);const h=[(new(n().Store)).add("dialogs",b).add("dialog-messages",d),new(n().Model)(_()).attribute("canSendAnyMessage").attribute("canDeleteOwnMessage")],y=flarum.reg.get("core","admin/components/SettingDropdown");var v=e.n(y);const w=[...h,(new(n().Admin)).permission((()=>({icon:"fas fa-envelope-open-text",label:s().translator.trans("flarum-messages.admin.permissions.send_messages_label"),permission:"dialog.sendMessage",allowGuest:!1})),"start",95).permission((()=>({icon:"far fa-trash-alt",label:s().translator.trans("flarum-messages.admin.permissions.delete_own_messages_label"),id:"flarum-messages.allow_delete_own_messages",setting:()=>(parseInt(s().data.settings["flarum-messages.allow_delete_own_messages"],10),m(v(),{default:"0",key:"flarum-messages.allow_delete_own_messages",options:[{value:"-1",label:s().translator.trans("core.admin.permissions_controls.allow_indefinitely_button")},{value:"10",label:s().translator.trans("core.admin.permissions_controls.allow_ten_minutes_button")},{value:"reply",label:s().translator.trans("core.admin.permissions_controls.allow_until_reply_button")},{value:"0",label:s().translator.trans("core.admin.permissions_controls.allow_never_button")}]}))})),"reply",80)];s().initializers.add("flarum-messages",(()=>{}))})(),module.exports=t})();
|
||||
//# sourceMappingURL=admin.js.map
|
2
extensions/messages/js/dist/admin.js.map
generated
vendored
2
extensions/messages/js/dist/admin.js.map
generated
vendored
File diff suppressed because one or more lines are too long
2
extensions/messages/js/dist/forum.js
generated
vendored
2
extensions/messages/js/dist/forum.js
generated
vendored
File diff suppressed because one or more lines are too long
2
extensions/messages/js/dist/forum.js.map
generated
vendored
2
extensions/messages/js/dist/forum.js.map
generated
vendored
File diff suppressed because one or more lines are too long
2
extensions/messages/js/dist/forum/components/MessagesPage.js
generated
vendored
2
extensions/messages/js/dist/forum/components/MessagesPage.js
generated
vendored
File diff suppressed because one or more lines are too long
2
extensions/messages/js/dist/forum/components/MessagesPage.js.map
generated
vendored
2
extensions/messages/js/dist/forum/components/MessagesPage.js.map
generated
vendored
File diff suppressed because one or more lines are too long
@@ -11,7 +11,7 @@
|
||||
"flarum-tsconfig": "^2.0.0",
|
||||
"typescript": "^4.5.4",
|
||||
"typescript-coverage-report": "^0.6.1",
|
||||
"@flarum/jest-config": "^1.0.1"
|
||||
"@flarum/jest-config": "^2.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "webpack --mode development --watch",
|
||||
|
21
extensions/messages/js/src/@types/shims.d.ts
vendored
21
extensions/messages/js/src/@types/shims.d.ts
vendored
@@ -1,21 +0,0 @@
|
||||
import type Dialog from '../common/models/Dialog';
|
||||
import DialogListState from '../forum/states/DialogListState';
|
||||
|
||||
declare module 'flarum/forum/routes' {
|
||||
export interface ForumRoutes {
|
||||
dialog: (tag: Dialog) => string;
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'flarum/forum/ForumApplication' {
|
||||
export default interface ForumApplication {
|
||||
dialogs: DialogListState;
|
||||
dropdownDialogs: DialogListState;
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'flarum/forum/states/ComposerState' {
|
||||
export default interface ComposerState {
|
||||
composingMessageTo(dialog: Dialog): boolean;
|
||||
}
|
||||
}
|
@@ -1,18 +0,0 @@
|
||||
import Extend from 'flarum/common/extenders';
|
||||
import commonExtend from '../common/extend';
|
||||
import app from 'flarum/admin/app';
|
||||
|
||||
export default [
|
||||
...commonExtend,
|
||||
|
||||
new Extend.Admin().permission(
|
||||
() => ({
|
||||
icon: 'fas fa-envelope-open-text',
|
||||
label: app.translator.trans('flarum-messages.admin.permissions.send_messages'),
|
||||
permission: 'dialog.sendMessage',
|
||||
allowGuest: false,
|
||||
}),
|
||||
'start',
|
||||
98
|
||||
),
|
||||
];
|
45
extensions/messages/js/src/admin/extend.tsx
Normal file
45
extensions/messages/js/src/admin/extend.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import Extend from 'flarum/common/extenders';
|
||||
import commonExtend from '../common/extend';
|
||||
import app from 'flarum/admin/app';
|
||||
import SettingDropdown from 'flarum/admin/components/SettingDropdown';
|
||||
|
||||
export default [
|
||||
...commonExtend,
|
||||
|
||||
new Extend.Admin()
|
||||
.permission(
|
||||
() => ({
|
||||
icon: 'fas fa-envelope-open-text',
|
||||
label: app.translator.trans('flarum-messages.admin.permissions.send_messages_label'),
|
||||
permission: 'dialog.sendMessage',
|
||||
allowGuest: false,
|
||||
}),
|
||||
'start',
|
||||
95
|
||||
)
|
||||
.permission(
|
||||
() => ({
|
||||
icon: 'far fa-trash-alt',
|
||||
label: app.translator.trans('flarum-messages.admin.permissions.delete_own_messages_label'),
|
||||
id: 'flarum-messages.allow_delete_own_messages',
|
||||
setting: () => {
|
||||
const minutes = parseInt(app.data.settings['flarum-messages.allow_delete_own_messages'], 10);
|
||||
|
||||
return (
|
||||
<SettingDropdown
|
||||
default={'0'}
|
||||
key="flarum-messages.allow_delete_own_messages"
|
||||
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') },
|
||||
{ value: '0', label: app.translator.trans('core.admin.permissions_controls.allow_never_button') },
|
||||
]}
|
||||
/>
|
||||
);
|
||||
},
|
||||
}),
|
||||
'reply',
|
||||
80
|
||||
),
|
||||
];
|
@@ -1,9 +1,14 @@
|
||||
import DialogMessage from './models/DialogMessage';
|
||||
import Dialog from './models/Dialog';
|
||||
import Extend from 'flarum/common/extenders';
|
||||
import User from 'flarum/common/models/User';
|
||||
|
||||
export default [
|
||||
new Extend.Store()
|
||||
.add('dialogs', Dialog) //
|
||||
.add('dialog-messages', DialogMessage), //
|
||||
|
||||
new Extend.Model(User) //
|
||||
.attribute<boolean>('canSendAnyMessage')
|
||||
.attribute<boolean>('canDeleteOwnMessage'),
|
||||
];
|
||||
|
@@ -5,6 +5,9 @@ import type Dialog from './Dialog';
|
||||
import type User from 'flarum/common/models/User';
|
||||
|
||||
export default class DialogMessage extends Model {
|
||||
number() {
|
||||
return Model.attribute<number>('number').call(this);
|
||||
}
|
||||
content() {
|
||||
return Model.attribute<string | null | undefined>('content').call(this);
|
||||
}
|
||||
@@ -33,4 +36,8 @@ export default class DialogMessage extends Model {
|
||||
user() {
|
||||
return Model.hasOne<User>('user').call(this);
|
||||
}
|
||||
|
||||
canDelete() {
|
||||
return Model.attribute<boolean>('canDelete').call(this);
|
||||
}
|
||||
}
|
||||
|
@@ -24,14 +24,27 @@ export default class DialogSection<CustomAttrs extends IDialogStreamAttrs = IDia
|
||||
oninit(vnode: Mithril.Vnode<CustomAttrs, this>) {
|
||||
super.oninit(vnode);
|
||||
|
||||
this.messages = new MessageStreamState({
|
||||
this.messages = new MessageStreamState(this.requestParams());
|
||||
|
||||
this.messages.refresh();
|
||||
}
|
||||
|
||||
requestParams(forgetNear = false): any {
|
||||
const params: any = {
|
||||
filter: {
|
||||
dialog: this.attrs.dialog.id(),
|
||||
},
|
||||
sort: '-createdAt',
|
||||
});
|
||||
sort: '-number',
|
||||
};
|
||||
|
||||
this.messages.refresh();
|
||||
const near = m.route.param('near');
|
||||
|
||||
if (near && !forgetNear) {
|
||||
params.page = params.page || {};
|
||||
params.page.near = parseInt(near);
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
view() {
|
||||
@@ -42,11 +55,14 @@ export default class DialogSection<CustomAttrs extends IDialogStreamAttrs = IDia
|
||||
<div className="DialogSection-header">
|
||||
<Avatar user={recipient} />
|
||||
<div className="DialogSection-header-info">
|
||||
{(recipient && (
|
||||
<Link href={app.route.user(recipient!)}>
|
||||
<h2>{username(recipient)}</h2>
|
||||
</Link>
|
||||
)) || <h2>{username(recipient)}</h2>}
|
||||
<h2 className="DialogSection-header-info-title">
|
||||
{(recipient && <Link href={app.route.user(recipient!)}>{username(recipient)}</Link>) || username(recipient)}
|
||||
{recipient && recipient.canSendAnyMessage() ? null : (
|
||||
<span className="DialogSection-header-info-helperText">
|
||||
{app.translator.trans('flarum-messages.forum.dialog_section.cannot_reply_text')}
|
||||
</span>
|
||||
)}
|
||||
</h2>
|
||||
<div className="badges">{listItems(recipient?.badges().toArray() || [])}</div>
|
||||
</div>
|
||||
<div className="DialogSection-header-actions">{this.actionItems().toArray()}</div>
|
||||
@@ -59,6 +75,13 @@ export default class DialogSection<CustomAttrs extends IDialogStreamAttrs = IDia
|
||||
actionItems() {
|
||||
const items = new ItemList<Mithril.Children>();
|
||||
|
||||
items.add(
|
||||
'back',
|
||||
<Button className="Button Button--icon DialogSection-back" icon="fas fa-arrow-left" onclick={this.attrs.onback}>
|
||||
{app.translator.trans('flarum-messages.forum.dialog_section.back_label')}
|
||||
</Button>
|
||||
);
|
||||
|
||||
items.add(
|
||||
'details',
|
||||
<Dropdown
|
||||
|
@@ -9,9 +9,12 @@ import Comment from 'flarum/forum/components/Comment';
|
||||
import PostUser from 'flarum/forum/components/PostUser';
|
||||
import PostMeta from 'flarum/forum/components/PostMeta';
|
||||
import classList from 'flarum/common/utils/classList';
|
||||
import MessageControls from '../utils/MessageControls';
|
||||
import type MessageStreamState from '../states/MessageStreamState';
|
||||
|
||||
export interface IMessageAttrs extends IAbstractPostAttrs {
|
||||
message: DialogMessage;
|
||||
state: MessageStreamState;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -29,7 +32,7 @@ export default abstract class Message<CustomAttrs extends IMessageAttrs = IMessa
|
||||
}
|
||||
|
||||
controls(): Mithril.Children[] {
|
||||
return [];
|
||||
return MessageControls.controls(this.attrs.message, this).toArray();
|
||||
}
|
||||
|
||||
freshness(): Date {
|
||||
@@ -97,7 +100,7 @@ export default abstract class Message<CustomAttrs extends IMessageAttrs = IMessa
|
||||
}
|
||||
|
||||
avatar(): Mithril.Children {
|
||||
return this.attrs.message.user() ? <Avatar user={this.attrs.message.user()} /> : '';
|
||||
return this.attrs.message.user() ? <Avatar user={this.attrs.message.user()} className="Post-avatar" /> : '';
|
||||
}
|
||||
|
||||
headerItems() {
|
||||
@@ -105,7 +108,21 @@ export default abstract class Message<CustomAttrs extends IMessageAttrs = IMessa
|
||||
const message = this.attrs.message;
|
||||
|
||||
items.add('user', <PostUser post={message} />, 100);
|
||||
items.add('meta', <PostMeta post={message} />);
|
||||
items.add(
|
||||
'meta',
|
||||
<PostMeta
|
||||
post={message}
|
||||
permalink={() => {
|
||||
const dialog = message.dialog();
|
||||
|
||||
if (!dialog) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return app.forum.attribute('baseOrigin') + app.route.dialog(dialog, message.number());
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
return items;
|
||||
}
|
||||
|
@@ -77,18 +77,20 @@ export default class MessageStream<CustomAttrs extends IDialogStreamAttrs = IDia
|
||||
content() {
|
||||
const items: Mithril.Children[] = [];
|
||||
|
||||
const messages = this.attrs.state.getAllItems().sort((a, b) => a.createdAt().getTime() - b.createdAt().getTime());
|
||||
const messages = Array.from(new Map(this.attrs.state.getAllItems().map((msg) => [msg.id(), msg])).values()).sort(
|
||||
(a, b) => a.number() - b.number()
|
||||
);
|
||||
|
||||
const ReplyPlaceholder = this.replyPlaceholderComponent();
|
||||
const LoadingPost = this.loadingPostComponent();
|
||||
|
||||
if (messages[0].id() !== (this.attrs.dialog.data.relationships?.firstMessage.data as ModelIdentifier).id) {
|
||||
items.push(
|
||||
<div className="MessageStream-item" key="loadPrevious">
|
||||
<div className="MessageStream-item" key="loadNext">
|
||||
<Button
|
||||
onclick={() => this.whileMaintainingScroll(() => this.attrs.state.loadNext())}
|
||||
type="button"
|
||||
className="Button Button--block MessageStream-loadPrev"
|
||||
className="Button Button--block MessageStream-loadNext"
|
||||
>
|
||||
{app.translator.trans('flarum-messages.forum.messages_page.stream.load_previous_button')}
|
||||
</Button>
|
||||
@@ -97,7 +99,7 @@ export default class MessageStream<CustomAttrs extends IDialogStreamAttrs = IDia
|
||||
|
||||
if (LoadingPost) {
|
||||
items.push(
|
||||
<div className="MessageStream-item" key="loading-prev">
|
||||
<div className="MessageStream-item" key="loading-next">
|
||||
<LoadingPost />
|
||||
</div>
|
||||
);
|
||||
@@ -106,9 +108,31 @@ export default class MessageStream<CustomAttrs extends IDialogStreamAttrs = IDia
|
||||
|
||||
messages.forEach((message, index) => items.push(this.messageItem(message, index)));
|
||||
|
||||
if (ReplyPlaceholder) {
|
||||
if (messages[messages.length - 1].id() !== (this.attrs.dialog.data.relationships?.lastMessage.data as ModelIdentifier).id) {
|
||||
if (LoadingPost) {
|
||||
items.push(
|
||||
<div className="MessageStream-item" key="loading-prev">
|
||||
<LoadingPost />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
items.push(
|
||||
<div className="MessageStream-item" key="reply" /*data-index={this.attrs.state.count()}*/>
|
||||
<div className="MessageStream-item" key="loadPrev">
|
||||
<Button
|
||||
onclick={() => this.whileMaintainingScroll(() => this.attrs.state.loadPrev())}
|
||||
type="button"
|
||||
className="Button Button--block MessageStream-loadPrev"
|
||||
>
|
||||
{app.translator.trans('flarum-messages.forum.messages_page.stream.load_next_button')}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (app.session.user!.canSendAnyMessage() && ReplyPlaceholder) {
|
||||
items.push(
|
||||
<div className="MessageStream-item" key="reply">
|
||||
<ReplyPlaceholder
|
||||
discussion={this.attrs.dialog}
|
||||
onclick={() => {
|
||||
@@ -135,9 +159,9 @@ export default class MessageStream<CustomAttrs extends IDialogStreamAttrs = IDia
|
||||
|
||||
messageItem(message: DialogMessage, index: number) {
|
||||
return (
|
||||
<div className="MessageStream-item" key={index} data-id={message.id()}>
|
||||
<div className="MessageStream-item" key={index} data-id={message.id()} data-number={message.number()}>
|
||||
{this.timeGap(message)}
|
||||
<Message message={message} />
|
||||
<Message message={message} state={this.attrs.state} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -177,7 +201,7 @@ export default class MessageStream<CustomAttrs extends IDialogStreamAttrs = IDia
|
||||
return this.attrs.state.loadNext();
|
||||
}
|
||||
|
||||
if (this.element.scrollTop + this.element.clientHeight === this.element.scrollHeight && this.attrs.state.hasPrev()) {
|
||||
if (this.element.scrollTop + this.element.clientHeight >= this.element.scrollHeight && this.attrs.state.hasPrev()) {
|
||||
return this.attrs.state.loadPrev();
|
||||
}
|
||||
|
||||
@@ -186,16 +210,34 @@ export default class MessageStream<CustomAttrs extends IDialogStreamAttrs = IDia
|
||||
}
|
||||
|
||||
scrollToBottom() {
|
||||
this.element.scrollTop = this.element.scrollHeight;
|
||||
const near = m.route.param('near');
|
||||
|
||||
if (near) {
|
||||
const $message = this.element.querySelector(`.MessageStream-item[data-number="${near}"]`);
|
||||
|
||||
if ($message) {
|
||||
this.element.scrollTop = $message.getBoundingClientRect().top - this.element.getBoundingClientRect().top;
|
||||
$message.classList.add('flash');
|
||||
|
||||
// forget near
|
||||
window.history.replaceState(null, '', app.route.dialog(this.attrs.dialog));
|
||||
} else {
|
||||
this.element.scrollTop = this.element.scrollHeight;
|
||||
}
|
||||
} else {
|
||||
this.element.scrollTop = this.element.scrollHeight;
|
||||
}
|
||||
}
|
||||
|
||||
whileMaintainingScroll(callback: () => null | Promise<void>) {
|
||||
const scrollTop = this.element.scrollTop;
|
||||
const scrollHeight = this.element.scrollHeight;
|
||||
|
||||
const closerToBottomThanTop = scrollTop > (scrollHeight - this.element.clientHeight) / 2;
|
||||
|
||||
const result = callback();
|
||||
|
||||
if (result instanceof Promise) {
|
||||
if (result instanceof Promise && !closerToBottomThanTop) {
|
||||
result.then(() => {
|
||||
requestAnimationFrame(() => {
|
||||
this.element.scrollTop = this.element.scrollHeight - scrollHeight + scrollTop;
|
||||
|
@@ -14,11 +14,13 @@ import listItems from 'flarum/common/helpers/listItems';
|
||||
import ItemList from 'flarum/common/utils/ItemList';
|
||||
import Dropdown from 'flarum/common/components/Dropdown';
|
||||
import Button from 'flarum/common/components/Button';
|
||||
import classList from 'flarum/common/utils/classList';
|
||||
|
||||
export interface IMessagesPageAttrs extends IPageAttrs {}
|
||||
|
||||
export default class MessagesPage<CustomAttrs extends IMessagesPageAttrs = IMessagesPageAttrs> extends Page<CustomAttrs> {
|
||||
protected selectedDialog = Stream<Dialog | null>(null);
|
||||
protected currentDialogId: string | null = null;
|
||||
|
||||
oninit(vnode: Mithril.Vnode<CustomAttrs, this>) {
|
||||
super.oninit(vnode);
|
||||
@@ -49,6 +51,7 @@ export default class MessagesPage<CustomAttrs extends IMessagesPageAttrs = IMess
|
||||
|
||||
protected async initDialog() {
|
||||
const dialogId = m.route.param('id');
|
||||
this.currentDialogId = dialogId;
|
||||
|
||||
const title = app.translator.trans('flarum-messages.forum.messages_page.title', {}, true);
|
||||
|
||||
@@ -94,19 +97,12 @@ export default class MessagesPage<CustomAttrs extends IMessagesPageAttrs = IMess
|
||||
) : !app.dialogs.hasItems() ? (
|
||||
<InfoTile icon="far fa-envelope-open">{app.translator.trans('flarum-messages.forum.messages_page.empty_text')}</InfoTile>
|
||||
) : (
|
||||
<div className="MessagesPage-content">
|
||||
<div className="MessagesPage-sidebar" key="sidebar">
|
||||
<div className="IndexPage-toolbar" key="toolbar">
|
||||
<ul className="IndexPage-toolbar-view">{listItems(this.viewItems().toArray())}</ul>
|
||||
<ul className="IndexPage-toolbar-action">{listItems(this.actionItems().toArray())}</ul>
|
||||
</div>
|
||||
<DialogList key="list" state={app.dialogs} activeDialog={this.selectedDialog()} />
|
||||
</div>
|
||||
{this.selectedDialog() ? (
|
||||
<DialogSection key="dialog" dialog={this.selectedDialog()} />
|
||||
) : (
|
||||
<LoadingIndicator key="loading" display="block" />
|
||||
)}
|
||||
<div
|
||||
className={classList('MessagesPage-content', {
|
||||
'MessagesPage-content--onDialog': this.currentDialogId,
|
||||
})}
|
||||
>
|
||||
{this.contentItems().toArray()}
|
||||
</div>
|
||||
)}
|
||||
</PageStructure>
|
||||
@@ -128,6 +124,40 @@ export default class MessagesPage<CustomAttrs extends IMessagesPageAttrs = IMess
|
||||
);
|
||||
}
|
||||
|
||||
contentItems() {
|
||||
const items = new ItemList<Mithril.Children>();
|
||||
|
||||
items.add(
|
||||
'sidebar',
|
||||
<div className="MessagesPage-sidebar" key="sidebar">
|
||||
<div className="IndexPage-toolbar" key="toolbar">
|
||||
<ul className="IndexPage-toolbar-view">{listItems(this.viewItems().toArray())}</ul>
|
||||
<ul className="IndexPage-toolbar-action">{listItems(this.actionItems().toArray())}</ul>
|
||||
</div>
|
||||
<DialogList key="list" state={app.dialogs} activeDialog={this.selectedDialog()} />
|
||||
</div>,
|
||||
100
|
||||
);
|
||||
|
||||
items.add(
|
||||
'dialog',
|
||||
this.selectedDialog() ? (
|
||||
<DialogSection
|
||||
key="dialog"
|
||||
dialog={this.selectedDialog()}
|
||||
onback={() => {
|
||||
this.currentDialogId = null;
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<LoadingIndicator key="loading" display="block" />
|
||||
),
|
||||
80
|
||||
);
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an item list for the part of the toolbar which is concerned with how
|
||||
* the results are displayed. By default this is just a select box to change
|
||||
@@ -168,7 +198,7 @@ export default class MessagesPage<CustomAttrs extends IMessagesPageAttrs = IMess
|
||||
|
||||
/**
|
||||
* Build an item list for the part of the toolbar which is about taking action
|
||||
* on the results. By default this is just a "mark all as read" button.
|
||||
* on the results. By default, this is just a "mark all as read" button.
|
||||
*/
|
||||
actionItems() {
|
||||
const items = new ItemList<Mithril.Children>();
|
||||
|
@@ -14,8 +14,6 @@ export default class MessagesSidebar<CustomAttrs extends IMessagesSidebarAttrs =
|
||||
items(): ItemList<Mithril.Children> {
|
||||
const items = super.items();
|
||||
|
||||
const canSendAnyMessage = app.session.user!.attribute<boolean>('canSendAnyMessage');
|
||||
|
||||
items.remove('newDiscussion');
|
||||
|
||||
items.add(
|
||||
@@ -27,9 +25,11 @@ export default class MessagesSidebar<CustomAttrs extends IMessagesSidebarAttrs =
|
||||
onclick={() => {
|
||||
return this.newMessageAction();
|
||||
}}
|
||||
disabled={!canSendAnyMessage}
|
||||
disabled={!app.session.user!.canSendAnyMessage()}
|
||||
>
|
||||
{app.translator.trans('flarum-messages.forum.messages_page.new_message_button')}
|
||||
{app.session.user!.canSendAnyMessage()
|
||||
? app.translator.trans('flarum-messages.forum.messages_page.send_message_button')
|
||||
: app.translator.trans('flarum-messages.forum.messages_page.cannot_send_message_button')}
|
||||
</Button>,
|
||||
10
|
||||
);
|
||||
|
@@ -9,5 +9,6 @@ export default [
|
||||
new Extend.Routes() //
|
||||
.add('messages', '/messages', () => import('./components/MessagesPage'))
|
||||
.add('dialog', '/messages/dialog/:id', () => import('./components/MessagesPage'))
|
||||
.helper('dialog', (dialog: Dialog) => app.route('dialog', { id: dialog.id() })),
|
||||
.add('dialog.message', '/messages/dialog/:id/:near', () => import('./components/MessagesPage'))
|
||||
.helper('dialog', (dialog: Dialog, near?: number) => app.route(near ? 'dialog.message' : 'dialog', { id: dialog.id(), near: near })),
|
||||
];
|
||||
|
@@ -8,6 +8,7 @@ import Button from 'flarum/common/components/Button';
|
||||
import type Dialog from '../common/models/Dialog';
|
||||
import DialogsDropdown from './components/DialogsDropdown';
|
||||
import DialogListState from './states/DialogListState';
|
||||
import type User from 'flarum/common/models/User';
|
||||
|
||||
export { default as extend } from './extend';
|
||||
|
||||
@@ -44,14 +45,14 @@ app.initializers.add('flarum-messages', () => {
|
||||
});
|
||||
|
||||
extend(HeaderSecondary.prototype, 'items', function (items) {
|
||||
if (app.session.user?.attribute<boolean>('canSendAnyMessage')) {
|
||||
if (app.session.user?.canSendAnyMessage()) {
|
||||
items.add('messages', <DialogsDropdown state={app.dropdownDialogs} />, 15);
|
||||
}
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
extend(UserControls, 'userControls', (items, user) => {
|
||||
if (app.session.user?.attribute<boolean>('canSendAnyMessage')) {
|
||||
extend(UserControls, 'userControls', (items, user: User) => {
|
||||
if (app.session.user?.canSendAnyMessage()) {
|
||||
items.add(
|
||||
'sendMessage',
|
||||
<Button
|
||||
@@ -66,6 +67,7 @@ app.initializers.add('flarum-messages', () => {
|
||||
.then(() => app.composer.show());
|
||||
});
|
||||
}}
|
||||
helperText={user.canSendAnyMessage() ? null : app.translator.trans('flarum-messages.forum.user_controls.cannot_reply_text')}
|
||||
>
|
||||
{app.translator.trans('flarum-messages.forum.user_controls.send_message_button')}
|
||||
</Button>
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import PaginatedListState, { PaginatedListParams } from 'flarum/common/states/PaginatedListState';
|
||||
import DialogMessage from '../../common/models/DialogMessage';
|
||||
import { ApiQueryParamsPlural } from 'flarum/common/Store';
|
||||
|
||||
export interface MessageStreamParams extends PaginatedListParams {
|
||||
//
|
||||
|
67
extensions/messages/js/src/forum/utils/MessageControls.tsx
Normal file
67
extensions/messages/js/src/forum/utils/MessageControls.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import ItemList from 'flarum/common/utils/ItemList';
|
||||
import Separator from 'flarum/common/components/Separator';
|
||||
import type Mithril from 'mithril';
|
||||
import type DialogMessage from '../../common/models/DialogMessage';
|
||||
import type Message from '../components/Message';
|
||||
import Button from 'flarum/common/components/Button';
|
||||
import app from 'flarum/forum/app';
|
||||
import extractText from 'flarum/common/utils/extractText';
|
||||
|
||||
const MessageControls = {
|
||||
controls(message: DialogMessage, context: Message<any>) {
|
||||
const items = new ItemList<Mithril.Children>();
|
||||
|
||||
Object.entries(this.sections()).forEach(([section, method]) => {
|
||||
const controls = method.call(this, message, context).toArray();
|
||||
|
||||
if (controls.length) {
|
||||
controls.forEach((item) => items.add(item.itemName, item));
|
||||
items.add(section + 'Separator', <Separator />);
|
||||
}
|
||||
});
|
||||
|
||||
return items;
|
||||
},
|
||||
|
||||
sections() {
|
||||
return {
|
||||
user: this.userControls,
|
||||
moderation: this.moderationControls,
|
||||
destructive: this.destructiveControls,
|
||||
};
|
||||
},
|
||||
|
||||
userControls(message: DialogMessage, context: Message) {
|
||||
return new ItemList<Mithril.Children>();
|
||||
},
|
||||
|
||||
moderationControls(message: DialogMessage, context: Message) {
|
||||
return new ItemList<Mithril.Children>();
|
||||
},
|
||||
|
||||
destructiveControls(message: DialogMessage, context: Message) {
|
||||
const items = new ItemList<Mithril.Children>();
|
||||
|
||||
if (message.canDelete()) {
|
||||
items.add(
|
||||
'delete',
|
||||
<Button icon="far fa-trash-alt" onclick={() => this.deleteAction(message, context)}>
|
||||
{app.translator.trans('flarum-messages.forum.message_controls.delete_button')}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return items;
|
||||
},
|
||||
|
||||
deleteAction(message: DialogMessage, context: Message) {
|
||||
if (!confirm(extractText(app.translator.trans('flarum-messages.forum.message_controls.delete_confirmation')))) return;
|
||||
|
||||
return message.delete().then(() => {
|
||||
context.attrs.state.remove(message);
|
||||
m.redraw();
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default MessageControls;
|
@@ -1,17 +1,68 @@
|
||||
.MessagesPage-sidebar {
|
||||
flex-shrink: 0;
|
||||
width: 280px;
|
||||
.MessagesPage {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.MessagesPage-content {
|
||||
--messages-page-gap: 32px;
|
||||
display: flex;
|
||||
gap: 32px;
|
||||
gap: var(--messages-page-gap);
|
||||
|
||||
.Avatar {
|
||||
--size: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.MessagesPage-sidebar {
|
||||
flex-shrink: 0;
|
||||
width: 100%;
|
||||
|
||||
.MessagesPage-content--onDialog & {
|
||||
// margin-inline-start: calc(~"0px - 100% - var(--messages-page-gap)");
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media @tablet-up {
|
||||
width: 280px;
|
||||
|
||||
.MessagesPage-content--onDialog & {
|
||||
// margin-inline-start: 0;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.DialogSection {
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
|
||||
@media @tablet-up {
|
||||
padding-inline-start: 32px;
|
||||
}
|
||||
|
||||
&-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid var(--control-bg);
|
||||
|
||||
a {
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
&-actions {
|
||||
margin-inline-start: auto;
|
||||
}
|
||||
|
||||
&-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.MessageComposer-recipients {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -145,34 +196,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.DialogSection {
|
||||
flex-grow: 1;
|
||||
padding-inline-start: 32px;
|
||||
|
||||
&-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid var(--control-bg);
|
||||
|
||||
a {
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
&-actions {
|
||||
margin-inline-start: auto;
|
||||
}
|
||||
|
||||
&-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.Message {
|
||||
padding-right: 0;
|
||||
|
||||
@@ -191,8 +214,41 @@
|
||||
}
|
||||
|
||||
.MessageStream, .DialogList {
|
||||
max-height: calc(100vh - var(--header-height) - 140px - 235px);
|
||||
--additional-gap: 52px;
|
||||
max-height: calc(100vh - var(--header-height) - 140px - var(--additional-gap));
|
||||
overflow: auto;
|
||||
|
||||
@media @tablet-up {
|
||||
--additional-gap: 235px;
|
||||
}
|
||||
}
|
||||
|
||||
.MessageStream .ReplyPlaceholder {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.DialogSection-header-actions {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.DialogSection-header-info-title {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.DialogSection-header-info-helperText {
|
||||
font-size: 0.8rem;
|
||||
font-weight: normal;
|
||||
color: var(--control-color);
|
||||
}
|
||||
|
||||
.DialogSection-back {
|
||||
display: flex;
|
||||
|
||||
@media @tablet-up {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.DialogList-loadMore {
|
||||
|
@@ -3,7 +3,8 @@ flarum-messages:
|
||||
# Translations in this namespace are used by the admin interface.
|
||||
admin:
|
||||
permissions:
|
||||
send_messages: Send private messages
|
||||
send_messages_label: Send private messages
|
||||
delete_own_messages_label: Delete own messages
|
||||
|
||||
# Translations in this namespace are used by the forum user interface.
|
||||
forum:
|
||||
@@ -21,6 +22,8 @@ flarum-messages:
|
||||
view_all: View all messages
|
||||
|
||||
dialog_section:
|
||||
back_label: Go back
|
||||
cannot_reply_text: This user cannot reply
|
||||
controls:
|
||||
details_button: Details
|
||||
controls_toggle_label: Dialog control actions
|
||||
@@ -40,17 +43,22 @@ flarum-messages:
|
||||
newest_button: Newest
|
||||
oldest_button: Oldest
|
||||
|
||||
message_controls:
|
||||
delete_button: Delete
|
||||
delete_confirmation: Are you sure you want to delete this message? This action cannot be undone.
|
||||
|
||||
messages_page:
|
||||
empty_text: You have no messages yet. When you send or receive messages, they
|
||||
will appear here.
|
||||
cannot_send_message_button: Can't Send a Message
|
||||
empty_text: No new messages
|
||||
hero:
|
||||
title: Messages
|
||||
subtitle: Your private conversations with other users
|
||||
mark_all_as_read_tooltip: Mark all as read
|
||||
new_message_button: Send a Message
|
||||
refresh_tooltip: Refresh
|
||||
send_message_button: Send a Message
|
||||
stream:
|
||||
load_previous_button: Load previous messages
|
||||
load_next_button: Load next messages
|
||||
start_of_the_conversation: Start of the conversation
|
||||
time_lapsed_text: => core.forum.post_stream.time_lapsed_text
|
||||
title: Messages
|
||||
@@ -63,6 +71,7 @@ flarum-messages:
|
||||
|
||||
user_controls:
|
||||
send_message_button: Send a message
|
||||
cannot_reply_text: This user cannot reply
|
||||
|
||||
notifications:
|
||||
message_received_text: Message Received notification from {user}
|
||||
|
@@ -0,0 +1,50 @@
|
||||
<?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('dialog_messages', function (Blueprint $table) {
|
||||
$table->unsignedBigInteger('number')->nullable()->after('content');
|
||||
});
|
||||
|
||||
$numbers = [];
|
||||
|
||||
$schema->getConnection()
|
||||
->table('dialogs')
|
||||
->orderBy('id')
|
||||
->each(function (object $dialog) use ($schema, &$numbers) {
|
||||
$numbers[$dialog->id] = 0;
|
||||
|
||||
$schema->getConnection()
|
||||
->table('dialog_messages')
|
||||
->where('dialog_id', $dialog->id)
|
||||
->orderBy('id')
|
||||
->each(function (object $message) use ($schema, &$numbers) {
|
||||
$schema->getConnection()
|
||||
->table('dialog_messages')
|
||||
->where('id', $message->id)
|
||||
->update(['number' => ++$numbers[$message->dialog_id]]);
|
||||
});
|
||||
|
||||
unset($numbers[$dialog->id]);
|
||||
});
|
||||
|
||||
$schema->table('dialog_messages', function (Blueprint $table) {
|
||||
$table->unsignedBigInteger('number')->nullable(false)->change();
|
||||
});
|
||||
},
|
||||
'down' => function (Builder $schema) {
|
||||
$schema->table('dialog_messages', function (Blueprint $table) {
|
||||
$table->dropColumn('number');
|
||||
});
|
||||
}
|
||||
];
|
@@ -9,14 +9,36 @@
|
||||
|
||||
namespace Flarum\Messages\Access;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Flarum\Messages\DialogMessage;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Flarum\User\Access\AbstractPolicy;
|
||||
use Flarum\User\User;
|
||||
|
||||
class DialogMessagePolicy extends AbstractPolicy
|
||||
{
|
||||
public function update(User $actor, DialogMessage $dialogMessage): bool
|
||||
public function __construct(
|
||||
protected SettingsRepositoryInterface $settings
|
||||
) {
|
||||
}
|
||||
|
||||
public function update(User $actor, DialogMessage $message): ?bool
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function delete(User $actor, DialogMessage $message): bool|null|string
|
||||
{
|
||||
if ($message->user_id === $actor->id) {
|
||||
$allowHiding = $this->settings->get('flarum-messages.allow_delete_own_messages');
|
||||
|
||||
if ($allowHiding === '-1'
|
||||
|| ($allowHiding === 'reply' && $message->number >= $message->dialog->lastMessage->number)
|
||||
|| (is_numeric($allowHiding) && $message->created_at->diffInMinutes(new Carbon, true) < $allowHiding)) {
|
||||
return $this->allow();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@@ -27,6 +27,7 @@ use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Tobyz\JsonApiServer\Context as OriginalContext;
|
||||
use Tobyz\JsonApiServer\Exception\BadRequestException;
|
||||
|
||||
/**
|
||||
* @extends Resource\AbstractDatabaseResource<DialogMessage>
|
||||
@@ -77,6 +78,11 @@ class DialogMessageResource extends Resource\AbstractDatabaseResource
|
||||
return $actor->can('sendAnyMessage');
|
||||
}
|
||||
}),
|
||||
Endpoint\Delete::make()
|
||||
->authenticated()
|
||||
->visible(function (DialogMessage $message, Context $context): bool {
|
||||
return $context->getActor()->can('delete', $message);
|
||||
}),
|
||||
Endpoint\Index::make()
|
||||
->authenticated()
|
||||
->defaultInclude([
|
||||
@@ -86,6 +92,7 @@ class DialogMessageResource extends Resource\AbstractDatabaseResource
|
||||
'mentionsGroups',
|
||||
'mentionsTags',
|
||||
])
|
||||
->defaultSort('-number')
|
||||
->eagerLoad(function () {
|
||||
if ($this->extensions->isEnabled('flarum-mentions')) {
|
||||
return ['mentionsUsers', 'mentionsPosts', 'mentionsGroups', 'mentionsTags'];
|
||||
@@ -93,6 +100,35 @@ class DialogMessageResource extends Resource\AbstractDatabaseResource
|
||||
|
||||
return [];
|
||||
})
|
||||
->extractOffset(function (Context $context, array $defaultExtracts): int {
|
||||
$queryParams = $context->request->getQueryParams();
|
||||
$near = intval(Arr::get($queryParams, 'page.near'));
|
||||
|
||||
if ($near > 1) {
|
||||
$sort = $defaultExtracts['sort'];
|
||||
$filter = $defaultExtracts['filter'];
|
||||
$dialogId = $filter['dialog'] ?? null;
|
||||
|
||||
if (count($filter) > 1 || ! $dialogId || ($sort && $sort !== ['number' => 'desc'])) {
|
||||
throw new BadRequestException(
|
||||
'You can only use page[near] with filter[dialog] and the default sort order'
|
||||
);
|
||||
}
|
||||
|
||||
$limit = $defaultExtracts['limit'];
|
||||
|
||||
$index = DialogMessage::query()
|
||||
->where('dialog_id', $dialogId)
|
||||
->where('number', '>=', $near)
|
||||
->orderBy('number', 'desc')
|
||||
->whereVisibleTo($context->getActor())
|
||||
->count();
|
||||
|
||||
return max(0, $index - $limit / 2);
|
||||
}
|
||||
|
||||
return $defaultExtracts['offset'];
|
||||
})
|
||||
->paginate(),
|
||||
];
|
||||
}
|
||||
@@ -101,6 +137,7 @@ class DialogMessageResource extends Resource\AbstractDatabaseResource
|
||||
{
|
||||
return [
|
||||
|
||||
Schema\Number::make('number'),
|
||||
Schema\Str::make('content')
|
||||
->requiredOnCreate()
|
||||
->writableOnCreate()
|
||||
@@ -134,6 +171,12 @@ class DialogMessageResource extends Resource\AbstractDatabaseResource
|
||||
->items(1)
|
||||
->set(fn () => null),
|
||||
|
||||
// Read-only.
|
||||
Schema\Boolean::make('canDelete')
|
||||
->get(function (DialogMessage $message, Context $context) {
|
||||
return $context->getActor()->can('delete', $message);
|
||||
}),
|
||||
|
||||
Schema\Relationship\ToOne::make('user')
|
||||
->type('users')
|
||||
->includable(),
|
||||
@@ -161,7 +204,7 @@ class DialogMessageResource extends Resource\AbstractDatabaseResource
|
||||
public function sorts(): array
|
||||
{
|
||||
return [
|
||||
SortColumn::make('createdAt'),
|
||||
SortColumn::make('number'),
|
||||
];
|
||||
}
|
||||
|
||||
|
@@ -21,12 +21,14 @@ use Flarum\Tags\Tag;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Query\Expression;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $dialog_id
|
||||
* @property int|null $user_id
|
||||
* @property string $content
|
||||
* @property int|Expression $number
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
* @property-read Dialog $dialog
|
||||
@@ -48,6 +50,28 @@ class DialogMessage extends AbstractModel implements Formattable
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
protected $casts = [
|
||||
'dialog_id' => 'integer',
|
||||
'user_id' => 'integer',
|
||||
'number' => 'integer',
|
||||
];
|
||||
|
||||
public static function boot()
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
static::creating(function (self $message) {
|
||||
$db = static::getConnectionResolver()->connection();
|
||||
|
||||
$message->number = new Expression('('.
|
||||
$db->table('dialog_messages', 'dm')
|
||||
->whereRaw($db->getTablePrefix().'dm.dialog_id = '.intval($message->dialog_id))
|
||||
->selectRaw('COALESCE(MAX('.$db->getTablePrefix().'dm.number), 0) + 1')
|
||||
->toSql()
|
||||
.')');
|
||||
});
|
||||
}
|
||||
|
||||
public function dialog(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Dialog::class);
|
||||
|
@@ -39,12 +39,12 @@ class ListTest extends TestCase
|
||||
['id' => 104, 'type' => 'direct'],
|
||||
],
|
||||
DialogMessage::class => [
|
||||
['id' => 102, 'dialog_id' => 102, 'user_id' => 3, 'content' => 'Hello, Gale!'],
|
||||
['id' => 103, 'dialog_id' => 102, 'user_id' => 4, 'content' => 'Hello, Astarion!'],
|
||||
['id' => 104, 'dialog_id' => 103, 'user_id' => 3, 'content' => 'Hello, Karlach!'],
|
||||
['id' => 105, 'dialog_id' => 103, 'user_id' => 5, 'content' => 'Hello, Astarion!'],
|
||||
['id' => 106, 'dialog_id' => 104, 'user_id' => 4, 'content' => 'Hello, Karlach!'],
|
||||
['id' => 107, 'dialog_id' => 104, 'user_id' => 5, 'content' => 'Hello, Gale!'],
|
||||
['id' => 102, 'dialog_id' => 102, 'user_id' => 3, 'content' => 'Hello, Gale!', 'number' => 1],
|
||||
['id' => 103, 'dialog_id' => 102, 'user_id' => 4, 'content' => 'Hello, Astarion!', 'number' => 2],
|
||||
['id' => 104, 'dialog_id' => 103, 'user_id' => 3, 'content' => 'Hello, Karlach!', 'number' => 1],
|
||||
['id' => 105, 'dialog_id' => 103, 'user_id' => 5, 'content' => 'Hello, Astarion!', 'number' => 2],
|
||||
['id' => 106, 'dialog_id' => 104, 'user_id' => 4, 'content' => 'Hello, Karlach!', 'number' => 1],
|
||||
['id' => 107, 'dialog_id' => 104, 'user_id' => 5, 'content' => 'Hello, Gale!', 'number' => 2],
|
||||
],
|
||||
'dialog_user' => [
|
||||
['dialog_id' => 102, 'user_id' => 3, 'joined_at' => Carbon::now()],
|
||||
@@ -125,4 +125,49 @@ class ListTest extends TestCase
|
||||
'Karlach can see messages in dialogs with Astarion and Gale' => [5, [104, 105, 106, 107]],
|
||||
];
|
||||
}
|
||||
|
||||
public function test_can_list_near_accessible_dialog_messages(): void
|
||||
{
|
||||
$messages = [];
|
||||
|
||||
for ($i = 1; $i <= 40; $i++) {
|
||||
$messages[] = ['id' => 200 + $i, 'dialog_id' => 200, 'user_id' => $i % 2 === 0 ? 3 : 4, 'content' => '<t>Hello, Gale!</t>', 'number' => $i];
|
||||
}
|
||||
|
||||
$this->prepareDatabase([
|
||||
Dialog::class => [
|
||||
['id' => 200, 'type' => 'direct'],
|
||||
],
|
||||
DialogMessage::class => $messages,
|
||||
'dialog_user' => [
|
||||
['dialog_id' => 200, 'user_id' => 3, 'joined_at' => Carbon::now()],
|
||||
['dialog_id' => 200, 'user_id' => 4, 'joined_at' => Carbon::now()],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->database()->table('dialogs')->where('id', '!=', 200)->delete();
|
||||
$this->database()->table('dialog_messages')->where('dialog_id', '!=', 200)->delete();
|
||||
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/dialog-messages', [
|
||||
'authenticatedAs' => 3,
|
||||
])->withQueryParams([
|
||||
'include' => 'dialog',
|
||||
'page' => ['near' => 10],
|
||||
'filter' => ['dialog' => 200],
|
||||
]),
|
||||
);
|
||||
|
||||
$json = $response->getBody()->getContents();
|
||||
$prettyJson = json_encode($json, JSON_PRETTY_PRINT);
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode(), $prettyJson);
|
||||
$this->assertJson($json);
|
||||
|
||||
$data = json_decode($json, true)['data'];
|
||||
$prettyJson = json_encode(json_decode($json), JSON_PRETTY_PRINT);
|
||||
|
||||
$this->assertEquals(40, $this->database()->table('dialog_messages')->count());
|
||||
$this->assertCount(19, $data, $prettyJson);
|
||||
}
|
||||
}
|
||||
|
@@ -36,7 +36,7 @@ class CreateTest extends TestCase
|
||||
['id' => 102, 'type' => 'direct'],
|
||||
],
|
||||
DialogMessage::class => [
|
||||
['id' => 102, 'dialog_id' => 102, 'user_id' => 4, 'content' => 'Hello, Karlach!'],
|
||||
['id' => 102, 'dialog_id' => 102, 'user_id' => 4, 'content' => 'Hello, Karlach!', 'number' => 1],
|
||||
],
|
||||
'dialog_user' => [
|
||||
['dialog_id' => 102, 'user_id' => 4, 'joined_at' => Carbon::now()],
|
||||
|
@@ -37,16 +37,16 @@ class UpdateTest extends TestCase
|
||||
['id' => 102, 'type' => 'direct', 'last_message_id' => 111],
|
||||
],
|
||||
DialogMessage::class => [
|
||||
['id' => 102, 'dialog_id' => 102, 'user_id' => 4, 'content' => '<p>Hello, Alice!</p>'],
|
||||
['id' => 103, 'dialog_id' => 102, 'user_id' => 3, 'content' => '<p>Hello, Bob!</p>'],
|
||||
['id' => 104, 'dialog_id' => 102, 'user_id' => 4, 'content' => '<p>Hello, Alice!</p>'],
|
||||
['id' => 105, 'dialog_id' => 102, 'user_id' => 3, 'content' => '<p>Hello, Bob!</p>'],
|
||||
['id' => 106, 'dialog_id' => 102, 'user_id' => 4, 'content' => '<p>Hello, Alice!</p>'],
|
||||
['id' => 107, 'dialog_id' => 102, 'user_id' => 3, 'content' => '<p>Hello, Bob!</p>'],
|
||||
['id' => 108, 'dialog_id' => 102, 'user_id' => 4, 'content' => '<p>Hello, Alice!</p>'],
|
||||
['id' => 109, 'dialog_id' => 102, 'user_id' => 3, 'content' => '<p>Hello, Bob!</p>'],
|
||||
['id' => 110, 'dialog_id' => 102, 'user_id' => 4, 'content' => '<p>Hello, Alice!</p>'],
|
||||
['id' => 111, 'dialog_id' => 102, 'user_id' => 3, 'content' => '<p>Hello, Bob!</p>'],
|
||||
['id' => 102, 'dialog_id' => 102, 'user_id' => 4, 'content' => '<p>Hello, Alice!</p>', 'number' => 1],
|
||||
['id' => 103, 'dialog_id' => 102, 'user_id' => 3, 'content' => '<p>Hello, Bob!</p>', 'number' => 2],
|
||||
['id' => 104, 'dialog_id' => 102, 'user_id' => 4, 'content' => '<p>Hello, Alice!</p>', 'number' => 3],
|
||||
['id' => 105, 'dialog_id' => 102, 'user_id' => 3, 'content' => '<p>Hello, Bob!</p>', 'number' => 4],
|
||||
['id' => 106, 'dialog_id' => 102, 'user_id' => 4, 'content' => '<p>Hello, Alice!</p>', 'number' => 5],
|
||||
['id' => 107, 'dialog_id' => 102, 'user_id' => 3, 'content' => '<p>Hello, Bob!</p>', 'number' => 6],
|
||||
['id' => 108, 'dialog_id' => 102, 'user_id' => 4, 'content' => '<p>Hello, Alice!</p>', 'number' => 7],
|
||||
['id' => 109, 'dialog_id' => 102, 'user_id' => 3, 'content' => '<p>Hello, Bob!</p>', 'number' => 8],
|
||||
['id' => 110, 'dialog_id' => 102, 'user_id' => 4, 'content' => '<p>Hello, Alice!</p>', 'number' => 9],
|
||||
['id' => 111, 'dialog_id' => 102, 'user_id' => 3, 'content' => '<p>Hello, Bob!</p>', 'number' => 10],
|
||||
],
|
||||
'dialog_user' => [
|
||||
['dialog_id' => 102, 'user_id' => 3, 'last_read_message_id' => 0, 'last_read_at' => null, 'joined_at' => Carbon::now()],
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
|
||||
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
|
||||
backupGlobals="false"
|
||||
cacheDirectory=".phpunit.cache"
|
||||
backupStaticProperties="false"
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
|
||||
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
|
||||
backupGlobals="false"
|
||||
cacheDirectory=".phpunit.cache"
|
||||
backupStaticProperties="false"
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^2.0"
|
||||
"flarum/core": "^2.0.0-beta.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
|
||||
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
|
||||
backupGlobals="false"
|
||||
cacheDirectory=".phpunit.cache"
|
||||
backupStaticProperties="false"
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
|
||||
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
|
||||
backupGlobals="false"
|
||||
cacheDirectory=".phpunit.cache"
|
||||
backupStaticProperties="false"
|
||||
|
@@ -22,7 +22,7 @@
|
||||
"source": "https://github.com/flarum/extension-manager"
|
||||
},
|
||||
"require": {
|
||||
"flarum/core": "^2.0",
|
||||
"flarum/core": "^2.0.0-beta.3",
|
||||
"composer/composer": "^2.7"
|
||||
},
|
||||
"require-dev": {
|
||||
|
@@ -46,6 +46,11 @@ return [
|
||||
&& is_writable($paths->base.'/composer.lock');
|
||||
|
||||
$document->payload['flarum-extension-manager.using_sync_queue'] = resolve(Queue::class) instanceof SyncQueue;
|
||||
|
||||
$document->payload['flarum-extension-manager.missing_functions'] = array_values(array_filter(
|
||||
['proc_open', 'escapeshellarg'],
|
||||
fn (string $function): bool => ! function_exists($function)
|
||||
));
|
||||
}),
|
||||
|
||||
new Extend\Locales(__DIR__.'/locale'),
|
||||
|
2
extensions/package-manager/js/dist-typings/models/ExternalExtension.d.ts
generated
vendored
2
extensions/package-manager/js/dist-typings/models/ExternalExtension.d.ts
generated
vendored
@@ -19,8 +19,8 @@ export default class ExternalExtension extends Model {
|
||||
locale: () => string;
|
||||
latestFlarumVersionSupported: () => string;
|
||||
downloads: () => number;
|
||||
isSupported: () => boolean;
|
||||
readonly installed = false;
|
||||
isSupported(): boolean;
|
||||
isProductionReady(): boolean;
|
||||
toLocalExtension(): Extension;
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user