mirror of
https://github.com/flarum/core.git
synced 2025-08-13 11:54:32 +02:00
Compare commits
94 Commits
dependabot
...
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 |
@@ -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]
|
||||
|
66
CHANGELOG.md
66
CHANGELOG.md
@@ -1,5 +1,71 @@
|
||||
# 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]
|
||||
|
@@ -171,7 +171,7 @@
|
||||
"mockery/mockery": "^1.5",
|
||||
"phpunit/phpunit": "^11.0",
|
||||
"phpstan/phpstan": "^1.10.0",
|
||||
"larastan/larastan": "2.9.12",
|
||||
"larastan/larastan": "2.9.14",
|
||||
"symfony/var-dumper": "^7.0",
|
||||
"flarum/testing-tests": "*@dev"
|
||||
},
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^2.0.0-beta.1",
|
||||
"flarum/core": "^2.0.0-beta.3",
|
||||
"flarum/approval": "^2.0"
|
||||
},
|
||||
"autoload": {
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^2.0.0-beta.1",
|
||||
"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"
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^2.0.0-beta.1"
|
||||
"flarum/core": "^2.0.0-beta.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^2.0.0-beta.1"
|
||||
"flarum/core": "^2.0.0-beta.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^2.0.0-beta.1"
|
||||
"flarum/core": "^2.0.0-beta.3"
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^2.0.0-beta.1"
|
||||
"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, []],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"flarum/core": "^2.0.0-beta.1"
|
||||
"flarum/core": "^2.0.0-beta.3"
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^2.0.0-beta.1"
|
||||
"flarum/core": "^2.0.0-beta.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^2.0.0-beta.1"
|
||||
"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" />
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^2.0.0-beta.1"
|
||||
"flarum/core": "^2.0.0-beta.3"
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^2.0.0-beta.1"
|
||||
"flarum/core": "^2.0.0-beta.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
@@ -7,7 +7,7 @@
|
||||
"type": "flarum-extension",
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"flarum/core": "^2.0.0-beta.1"
|
||||
"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
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()],
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^2.0.0-beta.1"
|
||||
"flarum/core": "^2.0.0-beta.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
@@ -22,7 +22,7 @@
|
||||
"source": "https://github.com/flarum/extension-manager"
|
||||
},
|
||||
"require": {
|
||||
"flarum/core": "^2.0.0-beta.1",
|
||||
"flarum/core": "^2.0.0-beta.3",
|
||||
"composer/composer": "^2.7"
|
||||
},
|
||||
"require-dev": {
|
||||
|
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;
|
||||
}
|
||||
|
2
extensions/package-manager/js/dist/admin.js
generated
vendored
2
extensions/package-manager/js/dist/admin.js
generated
vendored
File diff suppressed because one or more lines are too long
2
extensions/package-manager/js/dist/admin.js.map
generated
vendored
2
extensions/package-manager/js/dist/admin.js.map
generated
vendored
File diff suppressed because one or more lines are too long
@@ -22,21 +22,9 @@ export default class ExternalExtension extends Model {
|
||||
locale = Model.attribute<string>('locale');
|
||||
latestFlarumVersionSupported = Model.attribute<string>('latestFlarumVersionSupported');
|
||||
downloads = Model.attribute<number>('downloads');
|
||||
isSupported = Model.attribute<boolean>('isSupported');
|
||||
readonly installed = false;
|
||||
|
||||
public isSupported(): boolean {
|
||||
const currentVersion = app.data.settings.version;
|
||||
const latestCompatibleVersion = this.latestFlarumVersionSupported();
|
||||
|
||||
// If stability is not the same, it's not compatible.
|
||||
if (currentVersion.split('-')[1] !== latestCompatibleVersion.split('-')[1]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Minor versions are compatible.
|
||||
return currentVersion.split('.')[0] === latestCompatibleVersion.split('.')[0];
|
||||
}
|
||||
|
||||
public isProductionReady(): boolean {
|
||||
return isProductionReady(this.highestVersion());
|
||||
}
|
||||
|
@@ -9,6 +9,7 @@
|
||||
|
||||
namespace Flarum\ExtensionManager\Api\Resource;
|
||||
|
||||
use Composer\Semver\Semver;
|
||||
use Flarum\Api\Endpoint;
|
||||
use Flarum\Api\Resource\AbstractResource;
|
||||
use Flarum\Api\Resource\Contracts\Countable;
|
||||
@@ -31,7 +32,7 @@ use Tobyz\JsonApiServer\Schema\CustomFilter;
|
||||
|
||||
class ExternalExtensionResource extends AbstractResource implements Listable, Paginatable, Countable
|
||||
{
|
||||
protected int|null $totalResults = null;
|
||||
protected ?int $totalResults = null;
|
||||
|
||||
public function __construct(
|
||||
protected Repository $cache,
|
||||
@@ -81,6 +82,11 @@ class ExternalExtensionResource extends AbstractResource implements Listable, Pa
|
||||
Schema\Boolean::make('compatibleWithLatestFlarum')
|
||||
->property('compatible_with_latest_flarum'),
|
||||
Schema\Integer::make('downloads'),
|
||||
|
||||
Schema\Boolean::make('isSupported')
|
||||
->get(function (Extension $extension) {
|
||||
return Semver::satisfies(Application::VERSION, $extension->latest_flarum_version_supported);
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
|
@@ -9,6 +9,7 @@
|
||||
|
||||
namespace Flarum\ExtensionManager\Command;
|
||||
|
||||
use Composer\Semver\Semver;
|
||||
use Flarum\Extension\Extension;
|
||||
use Flarum\Extension\ExtensionManager;
|
||||
use Flarum\ExtensionManager\Composer\ComposerAdapter;
|
||||
@@ -16,16 +17,21 @@ use Flarum\ExtensionManager\Composer\ComposerJson;
|
||||
use Flarum\ExtensionManager\Exception\ComposerCommandFailedException;
|
||||
use Flarum\ExtensionManager\Settings\LastUpdateCheck;
|
||||
use Flarum\ExtensionManager\Support\Util;
|
||||
use Flarum\Foundation\Application;
|
||||
use GuzzleHttp\Client;
|
||||
use Illuminate\Support\Collection;
|
||||
use Symfony\Component\Console\Input\ArrayInput;
|
||||
|
||||
class CheckForUpdatesHandler
|
||||
{
|
||||
protected array $meta = [];
|
||||
|
||||
public function __construct(
|
||||
protected ComposerAdapter $composer,
|
||||
protected LastUpdateCheck $lastUpdateCheck,
|
||||
protected ExtensionManager $extensions,
|
||||
protected ComposerJson $composerJson
|
||||
protected ComposerJson $composerJson,
|
||||
protected Client $http
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -97,6 +103,10 @@ class CheckForUpdatesHandler
|
||||
|
||||
$mainPackageUpdate['required-as'] = $composerJson['require'][$mainPackageUpdate['name']] ?? null;
|
||||
|
||||
if (! $this->compatibleWithCurrentFlarumVersion($mainPackageUpdate)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$updates->push($mainPackageUpdate);
|
||||
}
|
||||
|
||||
@@ -136,4 +146,49 @@ class CheckForUpdatesHandler
|
||||
|
||||
return $output->getContents();
|
||||
}
|
||||
|
||||
private function compatibleWithCurrentFlarumVersion(array $mainPackageUpdate): bool
|
||||
{
|
||||
if (empty($mainPackageUpdate['latest-major']) || str_contains($mainPackageUpdate['latest-major'], 'dev-')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (! empty($this->meta[$mainPackageUpdate['name']])) {
|
||||
$json = $this->meta[$mainPackageUpdate['name']];
|
||||
} else {
|
||||
$response = $this->http->get("https://repo.packagist.org/p2/{$mainPackageUpdate['name']}.json");
|
||||
|
||||
$body = $response->getBody()->getContents();
|
||||
|
||||
if ($response->getStatusCode() > 299 || $response->getStatusCode() < 200) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$json = json_decode($body, true);
|
||||
|
||||
$this->meta[$mainPackageUpdate['name']] = $json;
|
||||
}
|
||||
|
||||
$packages = new Collection($json['packages'][$mainPackageUpdate['name']] ?? []);
|
||||
|
||||
if ($packages->isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$package = $packages->firstWhere('version', $mainPackageUpdate['latest-major']);
|
||||
|
||||
if (! $package) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$flarumVersion = Application::VERSION;
|
||||
|
||||
$require = $package['require']['flarum/core'] ?? null;
|
||||
|
||||
if (! $require || str_contains($require, 'dev-')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return Semver::satisfies($flarumVersion, $require);
|
||||
}
|
||||
}
|
||||
|
@@ -20,6 +20,11 @@ use Throwable;
|
||||
|
||||
class ComposerCommandJob extends AbstractJob implements ShouldBeUnique
|
||||
{
|
||||
/**
|
||||
* The number of seconds the job can run before timing out.
|
||||
*/
|
||||
public int $timeout = 60 * 3;
|
||||
|
||||
public function __construct(
|
||||
protected AbstractActionCommand $command,
|
||||
protected string $phpVersion
|
||||
|
@@ -21,6 +21,9 @@ class Util
|
||||
if (str_starts_with($currentVersion, 'v')) {
|
||||
$currentVersion = substr($currentVersion, 1);
|
||||
}
|
||||
if (str_starts_with($latestVersion, 'v')) {
|
||||
$latestVersion = substr($latestVersion, 1);
|
||||
}
|
||||
|
||||
$currentVersion = explode('.', $currentVersion);
|
||||
$latestVersion = explode('.', $latestVersion);
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^2.0.0-beta.1",
|
||||
"flarum/core": "^2.0.0-beta.3",
|
||||
"pusher/pusher-php-server": "^7.2"
|
||||
},
|
||||
"require-dev": {
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^2.0.0-beta.1"
|
||||
"flarum/core": "^2.0.0-beta.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^2.0.0-beta.1"
|
||||
"flarum/core": "^2.0.0-beta.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
2
extensions/sticky/js/dist/forum.js
generated
vendored
2
extensions/sticky/js/dist/forum.js
generated
vendored
@@ -1,2 +1,2 @@
|
||||
(()=>{var t={n:e=>{var s=e&&e.__esModule?()=>e.default:()=>e;return t.d(s,{a:s}),s},d:(e,s)=>{for(var r in s)t.o(s,r)&&!t.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:s[r]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e),r:t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})}},e={};(()=>{"use strict";t.r(e),t.d(e,{extend:()=>B});const s=flarum.reg.get("core","forum/app");var r=t.n(s);const o=flarum.reg.get("core","common/extend"),n=flarum.reg.get("core","common/models/Discussion");var c=t.n(n);const i=flarum.reg.get("core","common/components/Badge");var a=t.n(i);const u=flarum.reg.get("core","forum/utils/DiscussionControls");var l=t.n(u);const d=flarum.reg.get("core","forum/components/DiscussionPage");var f=t.n(d);const y=flarum.reg.get("core","common/components/Button");var g=t.n(y);const p=flarum.reg.get("core","forum/states/DiscussionListState");var k=t.n(p);const b=flarum.reg.get("core","forum/components/DiscussionListItem");var v=t.n(b);const S=flarum.reg.get("core","forum/components/IndexPage");var h=t.n(S);const x=flarum.reg.get("core","common/utils/string"),P=flarum.reg.get("core","common/utils/classList");var _=t.n(P);const D=flarum.reg.get("core","common/extenders");var w=t.n(D);const I=flarum.reg.get("core","forum/components/EventPost");var O=t.n(I);class j extends(O()){icon(){return"fas fa-thumbtack"}descriptionKey(){return this.attrs.post.content().sticky?"flarum-sticky.forum.post_stream.discussion_stickied_text":"flarum-sticky.forum.post_stream.discussion_unstickied_text"}}flarum.reg.add("flarum-sticky","forum/components/DiscussionStickiedPost",j);const q=flarum.reg.get("core","common/query/IGambit"),L=flarum.reg.get("core","common/app");var M=t.n(L);class A extends q.BooleanGambit{key(){return M().translator.trans("flarum-sticky.lib.gambits.discussions.sticky.key",{},!0)}filterKey(){return"sticky"}}flarum.reg.add("flarum-sticky","common/query/discussions/StickyGambit",A);const B=[(new(w().Search)).gambit("discussions",A),(new(w().PostTypes)).add("discussionStickied",j),new(w().Model)(c()).attribute("isSticky").attribute("canSticky")];r().initializers.add("flarum-sticky",(()=>{(0,o.extend)(c().prototype,"badges",(function(t){this.isSticky()&&t.add("sticky",m(a(),{type:"sticky",label:r().translator.trans("flarum-sticky.forum.badge.sticky_tooltip"),icon:"fas fa-thumbtack"}),10)})),(0,o.extend)(l(),"moderationControls",(function(t,e){e.canSticky()&&t.add("sticky",m(g(),{icon:"fas fa-thumbtack",onclick:this.stickyAction.bind(e)},r().translator.trans(`flarum-sticky.forum.discussion_controls.${e.isSticky()?"unsticky":"sticky"}_button`)))})),l().stickyAction=function(){this.save({isSticky:!this.isSticky()}).then((()=>{r().current.matches(f())&&r().current.get("stream").update(),m.redraw()}))},(0,o.extend)(k().prototype,"requestParams",(function(t){r().forum.attribute("excerptDisplayEnabled")&&(r().current.matches(h())||r().current.matches(f()))&&t.include.push("firstPost")})),(0,o.extend)(v().prototype,"infoItems",(function(t){const e=this.attrs.discussion;if(r().forum.attribute("excerptDisplayEnabled")&&e.isSticky()&&!this.attrs.params.q&&!e.lastReadPostNumber()){const s=e.firstPost();if(s){const e=(0,x.truncate)(s.contentPlain(),175);t.add("excerpt",m("div",null,e),-100)}}})),(0,o.extend)(v().prototype,"elementAttrs",(function(t){this.attrs.discussion.isSticky()&&(t.className=_()(t.className,"DiscussionListItem--sticky"))}))}))})(),module.exports=e})();
|
||||
(()=>{var t={n:e=>{var s=e&&e.__esModule?()=>e.default:()=>e;return t.d(s,{a:s}),s},d:(e,s)=>{for(var r in s)t.o(s,r)&&!t.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:s[r]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e),r:t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})}},e={};(()=>{"use strict";t.r(e),t.d(e,{extend:()=>B});const s=flarum.reg.get("core","forum/app");var r=t.n(s);const o=flarum.reg.get("core","common/extend"),n=flarum.reg.get("core","common/models/Discussion");var c=t.n(n);const i=flarum.reg.get("core","common/components/Badge");var a=t.n(i);const u=flarum.reg.get("core","forum/utils/DiscussionControls");var l=t.n(u);const d=flarum.reg.get("core","forum/components/DiscussionPage");var f=t.n(d);const y=flarum.reg.get("core","common/components/Button");var g=t.n(y);const p=flarum.reg.get("core","forum/states/DiscussionListState");var k=t.n(p);const b=flarum.reg.get("core","forum/components/DiscussionListItem");var v=t.n(b);const S=flarum.reg.get("core","forum/components/IndexPage");var h=t.n(S);const x=flarum.reg.get("core","common/utils/string"),P=flarum.reg.get("core","common/utils/classList");var _=t.n(P);const D=flarum.reg.get("core","common/extenders");var w=t.n(D);const I=flarum.reg.get("core","forum/components/EventPost");var O=t.n(I);class j extends(O()){icon(){return"fas fa-thumbtack"}descriptionKey(){return this.attrs.post.content().sticky?"flarum-sticky.forum.post_stream.discussion_stickied_text":"flarum-sticky.forum.post_stream.discussion_unstickied_text"}}flarum.reg.add("flarum-sticky","forum/components/DiscussionStickiedPost",j);const q=flarum.reg.get("core","common/query/IGambit"),L=flarum.reg.get("core","common/app");var M=t.n(L);class A extends q.BooleanGambit{key(){return M().translator.trans("flarum-sticky.lib.gambits.discussions.sticky.key",{},!0)}filterKey(){return"sticky"}}flarum.reg.add("flarum-sticky","common/query/discussions/StickyGambit",A);const B=[(new(w().Search)).gambit("discussions",A),(new(w().PostTypes)).add("discussionStickied",j),new(w().Model)(c()).attribute("isSticky").attribute("canSticky")];r().initializers.add("flarum-sticky",(()=>{(0,o.extend)(c().prototype,"badges",(function(t){this.isSticky()&&t.add("sticky",m(a(),{type:"sticky",label:r().translator.trans("flarum-sticky.forum.badge.sticky_tooltip"),icon:"fas fa-thumbtack",tabindex:"0"}),10)})),(0,o.extend)(l(),"moderationControls",(function(t,e){e.canSticky()&&t.add("sticky",m(g(),{icon:"fas fa-thumbtack",onclick:this.stickyAction.bind(e)},r().translator.trans(`flarum-sticky.forum.discussion_controls.${e.isSticky()?"unsticky":"sticky"}_button`)))})),l().stickyAction=function(){this.save({isSticky:!this.isSticky()}).then((()=>{r().current.matches(f())&&r().current.get("stream").update(),m.redraw()}))},(0,o.extend)(k().prototype,"requestParams",(function(t){r().forum.attribute("excerptDisplayEnabled")&&(r().current.matches(h())||r().current.matches(f()))&&t.include.push("firstPost")})),(0,o.extend)(v().prototype,"infoItems",(function(t){const e=this.attrs.discussion;if(r().forum.attribute("excerptDisplayEnabled")&&e.isSticky()&&!this.attrs.params.q&&!e.lastReadPostNumber()){const s=e.firstPost();if(s){const e=(0,x.truncate)(s.contentPlain(),175);t.add("excerpt",m("div",null,e),-100)}}})),(0,o.extend)(v().prototype,"elementAttrs",(function(t){this.attrs.discussion.isSticky()&&(t.className=_()(t.className,"DiscussionListItem--sticky"))}))}))})(),module.exports=e})();
|
||||
//# sourceMappingURL=forum.js.map
|
2
extensions/sticky/js/dist/forum.js.map
generated
vendored
2
extensions/sticky/js/dist/forum.js.map
generated
vendored
File diff suppressed because one or more lines are too long
@@ -8,7 +8,7 @@ export default function addStickyBadge() {
|
||||
if (this.isSticky()) {
|
||||
badges.add(
|
||||
'sticky',
|
||||
<Badge type="sticky" label={app.translator.trans('flarum-sticky.forum.badge.sticky_tooltip')} icon="fas fa-thumbtack" />,
|
||||
<Badge type="sticky" label={app.translator.trans('flarum-sticky.forum.badge.sticky_tooltip')} icon="fas fa-thumbtack" tabindex="0" />,
|
||||
10
|
||||
);
|
||||
}
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^2.0.0-beta.1"
|
||||
"flarum/core": "^2.0.0-beta.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
2
extensions/subscriptions/js/dist/forum.js
generated
vendored
2
extensions/subscriptions/js/dist/forum.js
generated
vendored
File diff suppressed because one or more lines are too long
2
extensions/subscriptions/js/dist/forum.js.map
generated
vendored
2
extensions/subscriptions/js/dist/forum.js.map
generated
vendored
File diff suppressed because one or more lines are too long
@@ -9,11 +9,25 @@ export default function addSubscriptionBadge() {
|
||||
|
||||
switch (this.subscription()) {
|
||||
case 'follow':
|
||||
badge = <Badge label={app.translator.trans('flarum-subscriptions.forum.badge.following_tooltip')} icon="fas fa-star" type="following" />;
|
||||
badge = (
|
||||
<Badge
|
||||
label={app.translator.trans('flarum-subscriptions.forum.badge.following_tooltip')}
|
||||
icon="fas fa-star"
|
||||
type="following"
|
||||
tabindex="0"
|
||||
/>
|
||||
);
|
||||
break;
|
||||
|
||||
case 'ignore':
|
||||
badge = <Badge label={app.translator.trans('flarum-subscriptions.forum.badge.ignoring_tooltip')} icon="far fa-eye-slash" type="ignoring" />;
|
||||
badge = (
|
||||
<Badge
|
||||
label={app.translator.trans('flarum-subscriptions.forum.badge.ignoring_tooltip')}
|
||||
icon="far fa-eye-slash"
|
||||
type="ignoring"
|
||||
tabindex="0"
|
||||
/>
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@@ -51,7 +51,7 @@ export default class SubscriptionMenu<CustomAttrs extends ISubscriptionMenuAttrs
|
||||
const discussion = this.attrs.discussion;
|
||||
const subscription = discussion.subscription();
|
||||
|
||||
const buttonAttrs = this.possibleButtonAttrs[subscription];
|
||||
const buttonAttrs = this.possibleButtonAttrs[subscription ?? 'null'] ?? this.possibleButtonAttrs.null;
|
||||
|
||||
const preferences = app.session.user!.preferences()!;
|
||||
const notifyEmail = preferences['notify_newPost_email'];
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^2.0.0-beta.1"
|
||||
"flarum/core": "^2.0.0-beta.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
2
extensions/suspend/js/dist/forum.js
generated
vendored
2
extensions/suspend/js/dist/forum.js
generated
vendored
File diff suppressed because one or more lines are too long
2
extensions/suspend/js/dist/forum.js.map
generated
vendored
2
extensions/suspend/js/dist/forum.js.map
generated
vendored
File diff suppressed because one or more lines are too long
@@ -28,7 +28,7 @@ app.initializers.add('flarum-suspend', () => {
|
||||
if (new Date() < until) {
|
||||
items.add(
|
||||
'suspended',
|
||||
<Badge icon="fas fa-ban" type="suspended" label={app.translator.trans('flarum-suspend.forum.user_badge.suspended_tooltip')} />,
|
||||
<Badge icon="fas fa-ban" type="suspended" label={app.translator.trans('flarum-suspend.forum.user_badge.suspended_tooltip')} tabindex="0" />,
|
||||
100
|
||||
);
|
||||
}
|
||||
|
@@ -26,7 +26,7 @@ class UserResourceFields
|
||||
Schema\Str::make('suspendMessage')
|
||||
->writable($canSuspend)
|
||||
->visible(fn (User $user, Context $context) => $context->getActor()->id === $user->id || $canSuspend($user, $context)),
|
||||
Schema\Date::make('suspendedUntil')
|
||||
Schema\DateTime::make('suspendedUntil')
|
||||
->writable($canSuspend)
|
||||
->visible(fn (User $user, Context $context) => $context->getActor()->id === $user->id || $canSuspend($user, $context))
|
||||
->nullable(),
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^2.0.0-beta.1"
|
||||
"flarum/core": "^2.0.0-beta.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
10
extensions/tags/js/dist-typings/forum/components/TagHero.d.ts
generated
vendored
10
extensions/tags/js/dist-typings/forum/components/TagHero.d.ts
generated
vendored
@@ -1,5 +1,15 @@
|
||||
export default class TagHero extends Component<import("flarum/common/Component").ComponentAttrs, undefined> {
|
||||
constructor();
|
||||
view(): JSX.Element;
|
||||
/**
|
||||
* @returns {ItemList<Mithril.Children>}
|
||||
*/
|
||||
viewItems(): ItemList<Mithril.Children>;
|
||||
/**
|
||||
* @returns {ItemList<Mithril.Children>}
|
||||
*/
|
||||
contentItems(): ItemList<Mithril.Children>;
|
||||
}
|
||||
import Component from "flarum/common/Component";
|
||||
import ItemList from "flarum/common/utils/ItemList";
|
||||
import Mithril from "mithril";
|
||||
|
2
extensions/tags/js/dist/admin.js
generated
vendored
2
extensions/tags/js/dist/admin.js
generated
vendored
File diff suppressed because one or more lines are too long
2
extensions/tags/js/dist/admin.js.map
generated
vendored
2
extensions/tags/js/dist/admin.js.map
generated
vendored
File diff suppressed because one or more lines are too long
2
extensions/tags/js/dist/common/components/TagSelectionModal.js
generated
vendored
2
extensions/tags/js/dist/common/components/TagSelectionModal.js
generated
vendored
File diff suppressed because one or more lines are too long
2
extensions/tags/js/dist/common/components/TagSelectionModal.js.map
generated
vendored
2
extensions/tags/js/dist/common/components/TagSelectionModal.js.map
generated
vendored
File diff suppressed because one or more lines are too long
2
extensions/tags/js/dist/forum.js
generated
vendored
2
extensions/tags/js/dist/forum.js
generated
vendored
File diff suppressed because one or more lines are too long
2
extensions/tags/js/dist/forum.js.map
generated
vendored
2
extensions/tags/js/dist/forum.js.map
generated
vendored
File diff suppressed because one or more lines are too long
2
extensions/tags/js/dist/forum/components/TagDiscussionModal.js
generated
vendored
2
extensions/tags/js/dist/forum/components/TagDiscussionModal.js
generated
vendored
File diff suppressed because one or more lines are too long
2
extensions/tags/js/dist/forum/components/TagDiscussionModal.js.map
generated
vendored
2
extensions/tags/js/dist/forum/components/TagDiscussionModal.js.map
generated
vendored
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user