mirror of
https://github.com/flarum/core.git
synced 2025-08-29 11:00:12 +02:00
Compare commits
15 Commits
dk/info-v2
...
v2.0.0-bet
Author | SHA1 | Date | |
---|---|---|---|
|
4e95d06190 | ||
|
e1a91dcd77 | ||
|
fbe7be69ef | ||
|
7f946ca0dd | ||
|
cc05a6dd3b | ||
|
649be7cb03 | ||
|
f19007f424 | ||
|
15112c2f40 | ||
|
00ef0dd9d0 | ||
|
cae706a638 | ||
|
2be1932e54 | ||
|
2339c23aae | ||
|
2b08c30a22 | ||
|
fa88731fe1 | ||
|
65d8c16580 |
42
CHANGELOG.md
42
CHANGELOG.md
@@ -1,5 +1,47 @@
|
||||
# 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]
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^2.0.0-beta.2",
|
||||
"flarum/core": "^2.0.0-beta.3",
|
||||
"flarum/approval": "^2.0"
|
||||
},
|
||||
"autoload": {
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^2.0.0-beta.2",
|
||||
"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.2"
|
||||
"flarum/core": "^2.0.0-beta.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^2.0.0-beta.2"
|
||||
"flarum/core": "^2.0.0-beta.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^2.0.0-beta.2"
|
||||
"flarum/core": "^2.0.0-beta.3"
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^2.0.0-beta.2"
|
||||
"flarum/core": "^2.0.0-beta.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
@@ -7,7 +7,7 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"flarum/core": "^2.0.0-beta.2"
|
||||
"flarum/core": "^2.0.0-beta.3"
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^2.0.0-beta.2"
|
||||
"flarum/core": "^2.0.0-beta.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^2.0.0-beta.2"
|
||||
"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.2"
|
||||
"flarum/core": "^2.0.0-beta.3"
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^2.0.0-beta.2"
|
||||
"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.2"
|
||||
"flarum/core": "^2.0.0-beta.3"
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^2.0.0-beta.2"
|
||||
"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.2",
|
||||
"flarum/core": "^2.0.0-beta.3",
|
||||
"composer/composer": "^2.7"
|
||||
},
|
||||
"require-dev": {
|
||||
|
@@ -32,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,
|
||||
|
@@ -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
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^2.0.0-beta.2",
|
||||
"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.2"
|
||||
"flarum/core": "^2.0.0-beta.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^2.0.0-beta.2"
|
||||
"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.2"
|
||||
"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;
|
||||
}
|
||||
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^2.0.0-beta.2"
|
||||
"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
|
||||
);
|
||||
}
|
||||
|
@@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"flarum/core": "^2.0.0-beta.2"
|
||||
"flarum/core": "^2.0.0-beta.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
@@ -41,7 +41,7 @@ class Tag
|
||||
$slug = Arr::pull($queryParams, 'slug');
|
||||
$sort = Arr::pull($queryParams, 'sort');
|
||||
$q = Arr::pull($queryParams, 'q', '');
|
||||
$page = Arr::pull($queryParams, 'page', 1);
|
||||
$page = max(1, intval(Arr::pull($queryParams, 'page')));
|
||||
$filters = Arr::pull($queryParams, 'filter', []);
|
||||
|
||||
$sortMap = $this->resource->sortMap();
|
||||
|
2
framework/core/js/dist-typings/admin/AdminApplication.d.ts
generated
vendored
2
framework/core/js/dist-typings/admin/AdminApplication.d.ts
generated
vendored
@@ -87,7 +87,7 @@ export default class AdminApplication extends Application {
|
||||
data: AdminApplicationData;
|
||||
route: typeof Application.prototype.route & AdminRoutes;
|
||||
constructor();
|
||||
protected beforeMount(): void;
|
||||
protected runBeforeMount(): void;
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
|
6
framework/core/js/dist-typings/admin/components/AppearancePage.d.ts
generated
vendored
6
framework/core/js/dist-typings/admin/components/AppearancePage.d.ts
generated
vendored
@@ -8,7 +8,11 @@ export default class AppearancePage extends AdminPage {
|
||||
title: string | any[];
|
||||
description: string | any[];
|
||||
};
|
||||
content(): JSX.Element;
|
||||
content(): (Mithril.Children & {
|
||||
itemName: string;
|
||||
})[];
|
||||
contentItems(): ItemList<Mithril.Children>;
|
||||
brandingItems(): ItemList<Mithril.Children>;
|
||||
colorItems(): ItemList<Mithril.Children>;
|
||||
onsaved(): void;
|
||||
static register(): void;
|
||||
|
26
framework/core/js/dist-typings/admin/utils/AdminRegistry.d.ts
generated
vendored
26
framework/core/js/dist-typings/admin/utils/AdminRegistry.d.ts
generated
vendored
@@ -54,7 +54,19 @@ export default class AdminRegistry {
|
||||
* label: app.translator.trans('flarum-flags.admin.settings.guidelines_url_label')
|
||||
* }, 15) // priority is optional (ItemList)
|
||||
*/
|
||||
registerSetting(content: SettingConfigInput, priority?: number): this;
|
||||
registerSetting(content: SettingConfigInput, priority?: number, key?: string | null): this;
|
||||
/**
|
||||
* This function allows you to change the configuration of a setting.
|
||||
*/
|
||||
setSetting(key: string, content: SettingConfigInput | ((original: SettingConfigInput) => SettingConfigInput)): this;
|
||||
/**
|
||||
* This function allows you to change the priority of a setting.
|
||||
*/
|
||||
setSettingPriority(key: string, priority: number): this;
|
||||
/**
|
||||
* This function allows you to remove a setting.
|
||||
*/
|
||||
removeSetting(key: string): this;
|
||||
/**
|
||||
* This function registers your permission with Flarum
|
||||
*
|
||||
@@ -67,6 +79,18 @@ export default class AdminRegistry {
|
||||
* }, 'moderate', 65)
|
||||
*/
|
||||
registerPermission(content: PermissionConfig, permissionType: PermissionType, priority?: number): this;
|
||||
/**
|
||||
* This function allows you to change the configuration of a permission.
|
||||
*/
|
||||
setPermission(key: string, content: PermissionConfig | ((original: PermissionConfig) => PermissionConfig), permissionType: PermissionType): this;
|
||||
/**
|
||||
* This function allows you to change the priority of a permission.
|
||||
*/
|
||||
setPermissionPriority(key: string, permissionType: PermissionType, priority: number): this;
|
||||
/**
|
||||
* This function allows you to remove a permission.
|
||||
*/
|
||||
removePermission(key: string, permissionType: PermissionType): this;
|
||||
/**
|
||||
* Replace the default extension page with a custom component.
|
||||
* This component would typically extend ExtensionPage
|
||||
|
4
framework/core/js/dist-typings/common/Application.d.ts
generated
vendored
4
framework/core/js/dist-typings/common/Application.d.ts
generated
vendored
@@ -210,10 +210,12 @@ export default class Application {
|
||||
*/
|
||||
currentInitializerExtension: string | null;
|
||||
private handledErrors;
|
||||
private beforeMounts;
|
||||
load(payload: Application['data']): void;
|
||||
protected initialize(): CallableFunction[];
|
||||
boot(): void;
|
||||
protected beforeMount(): void;
|
||||
beforeMount(callback: () => void): void;
|
||||
protected runBeforeMount(): void;
|
||||
bootExtensions(extensions: Record<string, {
|
||||
extend?: IExtender[];
|
||||
}>): void;
|
||||
|
55
framework/core/js/dist-typings/common/extenders/Admin.d.ts
generated
vendored
55
framework/core/js/dist-typings/common/extenders/Admin.d.ts
generated
vendored
@@ -1,29 +1,66 @@
|
||||
import IExtender, { IExtensionModule } from './IExtender';
|
||||
import type AdminApplication from '../../admin/AdminApplication';
|
||||
import type { CustomExtensionPage, SettingConfigInternal } from '../../admin/utils/AdminRegistry';
|
||||
import type { CustomExtensionPage, SettingConfigInput } from '../../admin/utils/AdminRegistry';
|
||||
import type { PermissionConfig, PermissionType } from '../../admin/components/PermissionGrid';
|
||||
import type Mithril from 'mithril';
|
||||
import type { GeneralIndexItem } from '../../admin/states/GeneralSearchIndex';
|
||||
export default class Admin implements IExtender<AdminApplication> {
|
||||
protected context: string | null;
|
||||
protected settings: {
|
||||
setting?: () => SettingConfigInternal | null;
|
||||
setting?: () => SettingConfigInput | null;
|
||||
customSetting?: () => Mithril.Children;
|
||||
priority: number;
|
||||
}[];
|
||||
protected settingReplacements: {
|
||||
setting: string;
|
||||
replacement: (original: SettingConfigInput) => SettingConfigInput;
|
||||
}[];
|
||||
protected settingPriorityChanges: {
|
||||
setting: string;
|
||||
priority: number;
|
||||
}[];
|
||||
protected settingRemovals: string[];
|
||||
protected permissions: {
|
||||
permission: () => PermissionConfig | null;
|
||||
type: PermissionType;
|
||||
priority: number;
|
||||
}[];
|
||||
protected permissionsReplacements: {
|
||||
permission: string;
|
||||
type: PermissionType;
|
||||
replacement: (original: PermissionConfig) => PermissionConfig;
|
||||
}[];
|
||||
protected permissionsPriorityChanges: {
|
||||
permission: string;
|
||||
type: PermissionType;
|
||||
priority: number;
|
||||
}[];
|
||||
protected permissionsRemovals: {
|
||||
permission: string;
|
||||
type: PermissionType;
|
||||
}[];
|
||||
protected customPage: CustomExtensionPage | null;
|
||||
protected generalIndexes: {
|
||||
settings?: () => GeneralIndexItem[];
|
||||
permissions?: () => GeneralIndexItem[];
|
||||
};
|
||||
constructor(context?: string | null);
|
||||
/**
|
||||
* Register a setting to be shown on the extension's settings page.
|
||||
*/
|
||||
setting(setting: () => SettingConfigInternal | null, priority?: number): this;
|
||||
setting(setting: () => SettingConfigInput | null, priority?: number): this;
|
||||
/**
|
||||
* Replace an existing setting's configuration.
|
||||
*/
|
||||
replaceSetting(setting: string, replacement: (original: SettingConfigInput) => SettingConfigInput): this;
|
||||
/**
|
||||
* Change the priority of an existing setting.
|
||||
*/
|
||||
setSettingPriority(setting: string, priority: number): this;
|
||||
/**
|
||||
* Remove a setting from the extension's settings page.
|
||||
*/
|
||||
removeSetting(setting: string): this;
|
||||
/**
|
||||
* Register a custom setting to be shown on the extension's settings page.
|
||||
*/
|
||||
@@ -32,6 +69,18 @@ export default class Admin implements IExtender<AdminApplication> {
|
||||
* Register a permission to be shown on the extension's permissions page.
|
||||
*/
|
||||
permission(permission: () => PermissionConfig | null, type: PermissionType, priority?: number): this;
|
||||
/**
|
||||
* Replace an existing permission's configuration.
|
||||
*/
|
||||
replacePermission(permission: string, replacement: (original: PermissionConfig) => PermissionConfig, type: PermissionType): this;
|
||||
/**
|
||||
* Change the priority of an existing permission.
|
||||
*/
|
||||
setPermissionPriority(permission: string, type: PermissionType, priority: number): this;
|
||||
/**
|
||||
* Remove a permission from the extension's permissions page.
|
||||
*/
|
||||
removePermission(permission: string, type: PermissionType): this;
|
||||
/**
|
||||
* Register a custom page to be shown in the admin interface.
|
||||
*/
|
||||
|
5
framework/core/js/dist-typings/common/utils/a11y.d.ts
generated
vendored
Normal file
5
framework/core/js/dist-typings/common/utils/a11y.d.ts
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* Fix a11y skip links by manually focusing on the href target element.
|
||||
* This prevents unwanted/unexpected reloads of the page.
|
||||
*/
|
||||
export declare function prepareSkipLinks(): void;
|
2
framework/core/js/dist/admin.js
generated
vendored
2
framework/core/js/dist/admin.js
generated
vendored
File diff suppressed because one or more lines are too long
2
framework/core/js/dist/admin.js.map
generated
vendored
2
framework/core/js/dist/admin.js.map
generated
vendored
File diff suppressed because one or more lines are too long
2
framework/core/js/dist/forum.js
generated
vendored
2
framework/core/js/dist/forum.js
generated
vendored
File diff suppressed because one or more lines are too long
2
framework/core/js/dist/forum.js.map
generated
vendored
2
framework/core/js/dist/forum.js.map
generated
vendored
File diff suppressed because one or more lines are too long
@@ -129,12 +129,14 @@ export default class AdminApplication extends Application {
|
||||
this.route = (Object.getPrototypeOf(Object.getPrototypeOf(this)) as Application).route.bind(this);
|
||||
}
|
||||
|
||||
protected beforeMount(): void {
|
||||
protected runBeforeMount(): void {
|
||||
BasicsPage.register();
|
||||
AppearancePage.register();
|
||||
MailPage.register();
|
||||
AdvancedPage.register();
|
||||
PermissionsPage.register();
|
||||
|
||||
super.runBeforeMount();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -22,57 +22,91 @@ export default class AppearancePage extends AdminPage {
|
||||
}
|
||||
|
||||
content() {
|
||||
return (
|
||||
<>
|
||||
<Form>
|
||||
<FieldSet
|
||||
className="AppearancePage-colors"
|
||||
label={app.translator.trans('core.admin.appearance.colors_heading')}
|
||||
description={app.translator.trans('core.admin.appearance.colors_text')}
|
||||
>
|
||||
{this.colorItems().toArray()}
|
||||
</FieldSet>
|
||||
</Form>
|
||||
return this.contentItems().toArray();
|
||||
}
|
||||
|
||||
<Form>
|
||||
<div className="Form-group">
|
||||
<label>{app.translator.trans('core.admin.appearance.logo_heading')}</label>
|
||||
<div className="helpText">{app.translator.trans('core.admin.appearance.logo_text')}</div>
|
||||
<UploadImageButton name="logo" routePath="logo" value={app.data.settings.logo_path} url={app.forum.attribute('logoUrl')} />
|
||||
</div>
|
||||
contentItems(): ItemList<Mithril.Children> {
|
||||
const items = new ItemList<Mithril.Children>();
|
||||
|
||||
<div className="Form-group">
|
||||
<label>{app.translator.trans('core.admin.appearance.favicon_heading')}</label>
|
||||
<div className="helpText">{app.translator.trans('core.admin.appearance.favicon_text')}</div>
|
||||
<UploadImageButton name="favicon" routePath="favicon" value={app.data.settings.favicon_path} url={app.forum.attribute('faviconUrl')} />
|
||||
</div>
|
||||
|
||||
<div className="Form-group">
|
||||
<label>{app.translator.trans('core.admin.appearance.custom_header_heading')}</label>
|
||||
<div className="helpText">{app.translator.trans('core.admin.appearance.custom_header_text')}</div>
|
||||
<Button className="Button" onclick={() => app.modal.show(EditCustomHeaderModal)}>
|
||||
{app.translator.trans('core.admin.appearance.edit_header_button')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="Form-group">
|
||||
<label>{app.translator.trans('core.admin.appearance.custom_footer_heading')}</label>
|
||||
<div className="helpText">{app.translator.trans('core.admin.appearance.custom_footer_text')}</div>
|
||||
<Button className="Button" onclick={() => app.modal.show(EditCustomFooterModal)}>
|
||||
{app.translator.trans('core.admin.appearance.edit_footer_button')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="Form-group">
|
||||
<label>{app.translator.trans('core.admin.appearance.custom_styles_heading')}</label>
|
||||
<div className="helpText">{app.translator.trans('core.admin.appearance.custom_styles_text')}</div>
|
||||
<Button className="Button" onclick={() => app.modal.show(EditCustomCssModal)}>
|
||||
{app.translator.trans('core.admin.appearance.edit_css_button')}
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</>
|
||||
items.add(
|
||||
'colors',
|
||||
<Form>
|
||||
<FieldSet
|
||||
className="AppearancePage-colors"
|
||||
label={app.translator.trans('core.admin.appearance.colors_heading')}
|
||||
description={app.translator.trans('core.admin.appearance.colors_text')}
|
||||
>
|
||||
{this.colorItems().toArray()}
|
||||
</FieldSet>
|
||||
</Form>,
|
||||
100
|
||||
);
|
||||
|
||||
items.add('branding', <Form>{this.brandingItems().toArray()}</Form>, 90);
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
brandingItems(): ItemList<Mithril.Children> {
|
||||
const items = new ItemList<Mithril.Children>();
|
||||
|
||||
items.add(
|
||||
'logo',
|
||||
<div className="Form-group">
|
||||
<label>{app.translator.trans('core.admin.appearance.logo_heading')}</label>
|
||||
<div className="helpText">{app.translator.trans('core.admin.appearance.logo_text')}</div>
|
||||
<UploadImageButton name="logo" routePath="logo" value={app.data.settings.logo_path} url={app.forum.attribute('logoUrl')} />
|
||||
</div>,
|
||||
100
|
||||
);
|
||||
|
||||
items.add(
|
||||
'favicon',
|
||||
<div className="Form-group">
|
||||
<label>{app.translator.trans('core.admin.appearance.favicon_heading')}</label>
|
||||
<div className="helpText">{app.translator.trans('core.admin.appearance.favicon_text')}</div>
|
||||
<UploadImageButton name="favicon" routePath="favicon" value={app.data.settings.favicon_path} url={app.forum.attribute('faviconUrl')} />
|
||||
</div>,
|
||||
90
|
||||
);
|
||||
|
||||
items.add(
|
||||
'custom-header',
|
||||
<div className="Form-group">
|
||||
<label>{app.translator.trans('core.admin.appearance.custom_header_heading')}</label>
|
||||
<div className="helpText">{app.translator.trans('core.admin.appearance.custom_header_text')}</div>
|
||||
<Button className="Button" onclick={() => app.modal.show(EditCustomHeaderModal)}>
|
||||
{app.translator.trans('core.admin.appearance.edit_header_button')}
|
||||
</Button>
|
||||
</div>,
|
||||
80
|
||||
);
|
||||
|
||||
items.add(
|
||||
'custom-footer',
|
||||
<div className="Form-group">
|
||||
<label>{app.translator.trans('core.admin.appearance.custom_footer_heading')}</label>
|
||||
<div className="helpText">{app.translator.trans('core.admin.appearance.custom_footer_text')}</div>
|
||||
<Button className="Button" onclick={() => app.modal.show(EditCustomFooterModal)}>
|
||||
{app.translator.trans('core.admin.appearance.edit_footer_button')}
|
||||
</Button>
|
||||
</div>,
|
||||
70
|
||||
);
|
||||
|
||||
items.add(
|
||||
'custom-css',
|
||||
<div className="Form-group">
|
||||
<label>{app.translator.trans('core.admin.appearance.custom_styles_heading')}</label>
|
||||
<div className="helpText">{app.translator.trans('core.admin.appearance.custom_styles_text')}</div>
|
||||
<Button className="Button" onclick={() => app.modal.show(EditCustomCssModal)}>
|
||||
{app.translator.trans('core.admin.appearance.edit_css_button')}
|
||||
</Button>
|
||||
</div>,
|
||||
60
|
||||
);
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
colorItems() {
|
||||
|
@@ -9,7 +9,7 @@ import Icon from '../../common/components/Icon';
|
||||
import PermissionGrid from './PermissionGrid';
|
||||
import escapeRegExp from '../../common/utils/escapeRegExp';
|
||||
import { GeneralIndexData, GeneralIndexItem } from '../states/GeneralSearchIndex';
|
||||
import { ExtensionConfig, SettingConfigInternal } from '../utils/AdminRegistry';
|
||||
import { ExtensionConfig, SettingConfigInput } from '../utils/AdminRegistry';
|
||||
import ItemList from '../../common/utils/ItemList';
|
||||
|
||||
export class GeneralSearchResult {
|
||||
@@ -94,7 +94,7 @@ export default class GeneralSearchSource implements GlobalSearchSource {
|
||||
for (const extensionId in data) {
|
||||
// settings
|
||||
const settings = data[extensionId]!.settings;
|
||||
let normalizedSettings: GeneralIndexItem[] | SettingConfigInternal[] = [];
|
||||
let normalizedSettings: GeneralIndexItem[] | SettingConfigInput[] = [];
|
||||
|
||||
if (settings instanceof ItemList) {
|
||||
normalizedSettings = settings?.toArray();
|
||||
@@ -113,7 +113,7 @@ export default class GeneralSearchSource implements GlobalSearchSource {
|
||||
const group = app.generalIndex.getGroup(extensionId);
|
||||
|
||||
if (this.itemHasQuery(label, query) || this.itemHasQuery(help, query)) {
|
||||
const id = extensionId + '-' + ('setting' in setting ? setting : setting.id);
|
||||
const id = extensionId + '-' + ('setting' in setting ? setting : 'id' in setting ? setting.id : '');
|
||||
|
||||
results.push(
|
||||
new GeneralSearchResult(
|
||||
|
@@ -71,7 +71,7 @@ export default class AdminRegistry {
|
||||
* label: app.translator.trans('flarum-flags.admin.settings.guidelines_url_label')
|
||||
* }, 15) // priority is optional (ItemList)
|
||||
*/
|
||||
registerSetting(content: SettingConfigInput, priority = 0): this {
|
||||
registerSetting(content: SettingConfigInput, priority = 0, key: string | null = null): this {
|
||||
if (this.state.currentExtension === null) {
|
||||
throw new Error(noActiveExtensionErrorMessage);
|
||||
}
|
||||
@@ -83,7 +83,7 @@ export default class AdminRegistry {
|
||||
// To support multiple such items for one extension, we assign a random ID.
|
||||
// 36 is arbitrary length, but makes collisions very unlikely.
|
||||
if (tmpContent instanceof Function) {
|
||||
tmpContent.setting = Math.random().toString(36);
|
||||
tmpContent.setting = key || Math.random().toString(36);
|
||||
}
|
||||
|
||||
const settings = this.state.data[this.state.currentExtension].settings || new ItemList();
|
||||
@@ -94,6 +94,62 @@ export default class AdminRegistry {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function allows you to change the configuration of a setting.
|
||||
*/
|
||||
setSetting(key: string, content: SettingConfigInput | ((original: SettingConfigInput) => SettingConfigInput)): this {
|
||||
if (this.state.currentExtension === null) {
|
||||
throw new Error(noActiveExtensionErrorMessage);
|
||||
}
|
||||
|
||||
const settings = this.state.data[this.state.currentExtension].settings || new ItemList();
|
||||
|
||||
if (settings.has(key)) {
|
||||
if (content instanceof Function) {
|
||||
const original = settings.get(key);
|
||||
content = content(original) as SettingConfigInternal;
|
||||
}
|
||||
|
||||
settings.setContent(key, content as SettingConfigInternal);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function allows you to change the priority of a setting.
|
||||
*/
|
||||
setSettingPriority(key: string, priority: number): this {
|
||||
if (this.state.currentExtension === null) {
|
||||
throw new Error(noActiveExtensionErrorMessage);
|
||||
}
|
||||
|
||||
const settings = this.state.data[this.state.currentExtension].settings || new ItemList();
|
||||
|
||||
if (settings.has(key)) {
|
||||
settings.setPriority(key, priority);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function allows you to remove a setting.
|
||||
*/
|
||||
removeSetting(key: string): this {
|
||||
if (this.state.currentExtension === null) {
|
||||
throw new Error(noActiveExtensionErrorMessage);
|
||||
}
|
||||
|
||||
const settings = this.state.data[this.state.currentExtension].settings || new ItemList();
|
||||
|
||||
if (settings.has(key)) {
|
||||
settings.remove(key);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function registers your permission with Flarum
|
||||
*
|
||||
@@ -125,6 +181,65 @@ export default class AdminRegistry {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function allows you to change the configuration of a permission.
|
||||
*/
|
||||
setPermission(key: string, content: PermissionConfig | ((original: PermissionConfig) => PermissionConfig), permissionType: PermissionType): this {
|
||||
if (this.state.currentExtension === null) {
|
||||
throw new Error(noActiveExtensionErrorMessage);
|
||||
}
|
||||
|
||||
const permissions = this.state.data[this.state.currentExtension].permissions || {};
|
||||
const permissionsForType = permissions[permissionType] || new ItemList();
|
||||
|
||||
if (permissionsForType.has(key)) {
|
||||
if (content instanceof Function) {
|
||||
const original = permissionsForType.get(key);
|
||||
content = content(original) as PermissionConfig;
|
||||
}
|
||||
|
||||
permissionsForType.setContent(key, content);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function allows you to change the priority of a permission.
|
||||
*/
|
||||
setPermissionPriority(key: string, permissionType: PermissionType, priority: number): this {
|
||||
if (this.state.currentExtension === null) {
|
||||
throw new Error(noActiveExtensionErrorMessage);
|
||||
}
|
||||
|
||||
const permissions = this.state.data[this.state.currentExtension].permissions;
|
||||
const permissionsForType = permissions?.[permissionType] || new ItemList();
|
||||
|
||||
if (permissionsForType.has(key)) {
|
||||
permissionsForType.setPriority(key, priority);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function allows you to remove a permission.
|
||||
*/
|
||||
removePermission(key: string, permissionType: PermissionType): this {
|
||||
if (this.state.currentExtension === null) {
|
||||
throw new Error(noActiveExtensionErrorMessage);
|
||||
}
|
||||
|
||||
const permissions = this.state.data[this.state.currentExtension].permissions;
|
||||
const permissionsForType = permissions?.[permissionType] || new ItemList();
|
||||
|
||||
if (permissionsForType.has(key)) {
|
||||
permissionsForType.remove(key);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the default extension page with a custom component.
|
||||
* This component would typically extend ExtensionPage
|
||||
|
@@ -39,6 +39,7 @@ import IExtender from './extenders/IExtender';
|
||||
import AccessToken from './models/AccessToken';
|
||||
import SearchManager from './SearchManager';
|
||||
import { ColorScheme } from './components/ThemeMode';
|
||||
import { prepareSkipLinks } from './utils/a11y';
|
||||
|
||||
export type FlarumScreens = 'phone' | 'tablet' | 'desktop' | 'desktop-hd';
|
||||
|
||||
@@ -286,6 +287,8 @@ export default class Application {
|
||||
|
||||
private handledErrors: { extension: null | string; errorId: string; error: any }[] = [];
|
||||
|
||||
private beforeMounts: (() => void)[] = [];
|
||||
|
||||
public load(payload: Application['data']) {
|
||||
this.data = payload;
|
||||
this.translator.setLocale(payload.locale);
|
||||
@@ -326,7 +329,7 @@ export default class Application {
|
||||
|
||||
this.session = new Session(this.store.getById<User>('users', String(this.data.session.userId)) ?? null, this.data.session.csrfToken);
|
||||
|
||||
this.beforeMount();
|
||||
this.runBeforeMount();
|
||||
|
||||
this.mount();
|
||||
|
||||
@@ -335,8 +338,13 @@ export default class Application {
|
||||
caughtInitializationErrors.forEach((handler) => handler());
|
||||
}
|
||||
|
||||
protected beforeMount(): void {
|
||||
// ...
|
||||
public beforeMount(callback: () => void) {
|
||||
this.beforeMounts.push(callback);
|
||||
}
|
||||
|
||||
protected runBeforeMount(): void {
|
||||
this.beforeMounts.forEach((callback) => callback());
|
||||
this.beforeMounts = [];
|
||||
}
|
||||
|
||||
public bootExtensions(extensions: Record<string, { extend?: IExtender[] }>) {
|
||||
@@ -387,6 +395,8 @@ export default class Application {
|
||||
this.initColorScheme();
|
||||
|
||||
liveHumanTimes();
|
||||
|
||||
prepareSkipLinks();
|
||||
}
|
||||
|
||||
private initColorScheme(forumDefault: string | null = null): void {
|
||||
|
@@ -123,6 +123,14 @@ export default class Dropdown<CustomAttrs extends IDropdownAttrs = IDropdownAttr
|
||||
|
||||
m.redraw();
|
||||
});
|
||||
|
||||
this.$().on('focusout', (e: JQuery.FocusOutEvent) => {
|
||||
// Check if the new focused element is outside of this dropdown
|
||||
if (!this.$().has(e.relatedTarget as Element).length) {
|
||||
this.$().trigger('hidden.bs.dropdown');
|
||||
}
|
||||
});
|
||||
// Focusing out of the dropdown should close it.
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,25 +1,65 @@
|
||||
import IExtender, { IExtensionModule } from './IExtender';
|
||||
import type AdminApplication from '../../admin/AdminApplication';
|
||||
import type { CustomExtensionPage, SettingConfigInternal } from '../../admin/utils/AdminRegistry';
|
||||
import type { CustomExtensionPage, SettingConfigInput } from '../../admin/utils/AdminRegistry';
|
||||
import type { PermissionConfig, PermissionType } from '../../admin/components/PermissionGrid';
|
||||
import type Mithril from 'mithril';
|
||||
import type { GeneralIndexItem } from '../../admin/states/GeneralSearchIndex';
|
||||
|
||||
export default class Admin implements IExtender<AdminApplication> {
|
||||
protected settings: { setting?: () => SettingConfigInternal | null; customSetting?: () => Mithril.Children; priority: number }[] = [];
|
||||
protected context: string | null;
|
||||
|
||||
protected settings: { setting?: () => SettingConfigInput | null; customSetting?: () => Mithril.Children; priority: number }[] = [];
|
||||
protected settingReplacements: { setting: string; replacement: (original: SettingConfigInput) => SettingConfigInput }[] = [];
|
||||
protected settingPriorityChanges: { setting: string; priority: number }[] = [];
|
||||
protected settingRemovals: string[] = [];
|
||||
protected permissions: { permission: () => PermissionConfig | null; type: PermissionType; priority: number }[] = [];
|
||||
protected permissionsReplacements: { permission: string; type: PermissionType; replacement: (original: PermissionConfig) => PermissionConfig }[] =
|
||||
[];
|
||||
protected permissionsPriorityChanges: { permission: string; type: PermissionType; priority: number }[] = [];
|
||||
protected permissionsRemovals: { permission: string; type: PermissionType }[] = [];
|
||||
protected customPage: CustomExtensionPage | null = null;
|
||||
protected generalIndexes: { settings?: () => GeneralIndexItem[]; permissions?: () => GeneralIndexItem[] } = {};
|
||||
|
||||
constructor(context: string | null = null) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a setting to be shown on the extension's settings page.
|
||||
*/
|
||||
setting(setting: () => SettingConfigInternal | null, priority = 0) {
|
||||
setting(setting: () => SettingConfigInput | null, priority = 0) {
|
||||
this.settings.push({ setting, priority });
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace an existing setting's configuration.
|
||||
*/
|
||||
replaceSetting(setting: string, replacement: (original: SettingConfigInput) => SettingConfigInput) {
|
||||
this.settingReplacements.push({ setting, replacement });
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the priority of an existing setting.
|
||||
*/
|
||||
setSettingPriority(setting: string, priority: number) {
|
||||
this.settingPriorityChanges.push({ setting, priority });
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a setting from the extension's settings page.
|
||||
*/
|
||||
removeSetting(setting: string) {
|
||||
this.settingRemovals.push(setting);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a custom setting to be shown on the extension's settings page.
|
||||
*/
|
||||
@@ -38,6 +78,33 @@ export default class Admin implements IExtender<AdminApplication> {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace an existing permission's configuration.
|
||||
*/
|
||||
replacePermission(permission: string, replacement: (original: PermissionConfig) => PermissionConfig, type: PermissionType) {
|
||||
this.permissionsReplacements.push({ permission, type, replacement });
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the priority of an existing permission.
|
||||
*/
|
||||
setPermissionPriority(permission: string, type: PermissionType, priority: number) {
|
||||
this.permissionsPriorityChanges.push({ permission, type, priority });
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a permission from the extension's permissions page.
|
||||
*/
|
||||
removePermission(permission: string, type: PermissionType) {
|
||||
this.permissionsRemovals.push({ permission, type });
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a custom page to be shown in the admin interface.
|
||||
*/
|
||||
@@ -57,38 +124,64 @@ export default class Admin implements IExtender<AdminApplication> {
|
||||
}
|
||||
|
||||
extend(app: AdminApplication, extension: IExtensionModule) {
|
||||
app.registry.for(extension.name);
|
||||
app.beforeMount(() => {
|
||||
app.registry.for(this.context || extension.name);
|
||||
|
||||
this.settings.forEach(({ setting, customSetting, priority }) => {
|
||||
const settingConfig = setting ? setting() : customSetting!;
|
||||
this.settings.forEach(({ setting, customSetting, priority }) => {
|
||||
const settingConfig = setting ? setting() : customSetting!;
|
||||
|
||||
if (settingConfig) {
|
||||
app.registry.registerSetting(settingConfig, priority);
|
||||
if (settingConfig) {
|
||||
app.registry.registerSetting(settingConfig, priority);
|
||||
}
|
||||
});
|
||||
|
||||
this.settingReplacements.forEach(({ setting, replacement }) => {
|
||||
app.registry.setSetting(setting, replacement);
|
||||
});
|
||||
|
||||
this.settingPriorityChanges.forEach(({ setting, priority }) => {
|
||||
app.registry.setSettingPriority(setting, priority);
|
||||
});
|
||||
|
||||
this.settingRemovals.forEach((setting) => {
|
||||
app.registry.removeSetting(setting);
|
||||
});
|
||||
|
||||
this.permissions.forEach(({ permission, type, priority }) => {
|
||||
const permissionConfig = permission();
|
||||
|
||||
if (permissionConfig) {
|
||||
app.registry.registerPermission(permissionConfig, type, priority);
|
||||
}
|
||||
});
|
||||
|
||||
this.permissionsReplacements.forEach(({ permission, type, replacement }) => {
|
||||
app.registry.setPermission(permission, replacement, type);
|
||||
});
|
||||
|
||||
this.permissionsPriorityChanges.forEach(({ permission, type, priority }) => {
|
||||
app.registry.setPermissionPriority(permission, type, priority);
|
||||
});
|
||||
|
||||
this.permissionsRemovals.forEach(({ permission, type }) => {
|
||||
app.registry.removePermission(permission, type);
|
||||
});
|
||||
|
||||
if (this.customPage) {
|
||||
app.registry.registerPage(this.customPage);
|
||||
}
|
||||
});
|
||||
|
||||
this.permissions.forEach(({ permission, type, priority }) => {
|
||||
const permissionConfig = permission();
|
||||
app.generalIndex.for(extension.name);
|
||||
|
||||
if (permissionConfig) {
|
||||
app.registry.registerPermission(permissionConfig, type, priority);
|
||||
}
|
||||
});
|
||||
Object.keys(this.generalIndexes).forEach((key) => {
|
||||
if (key !== 'settings' && key !== 'permissions') return;
|
||||
|
||||
if (this.customPage) {
|
||||
app.registry.registerPage(this.customPage);
|
||||
}
|
||||
const callback = this.generalIndexes[key];
|
||||
|
||||
app.generalIndex.for(extension.name);
|
||||
|
||||
Object.keys(this.generalIndexes).forEach((key) => {
|
||||
if (key !== 'settings' && key !== 'permissions') return;
|
||||
|
||||
const callback = this.generalIndexes[key];
|
||||
|
||||
if (callback) {
|
||||
app.generalIndex.add(key, callback());
|
||||
}
|
||||
if (callback) {
|
||||
app.generalIndex.add(key, callback());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -131,7 +131,7 @@ export default class Discussion extends Model {
|
||||
const items = new ItemList<Mithril.Children>();
|
||||
|
||||
if (this.isHidden()) {
|
||||
items.add('hidden', <Badge type="hidden" icon="fas fa-trash" label={app.translator.trans('core.lib.badge.hidden_tooltip')} />);
|
||||
items.add('hidden', <Badge type="hidden" icon="fas fa-trash" label={app.translator.trans('core.lib.badge.hidden_tooltip')} tabindex="0" />);
|
||||
}
|
||||
|
||||
return items;
|
||||
|
21
framework/core/js/src/common/utils/a11y.ts
Normal file
21
framework/core/js/src/common/utils/a11y.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Fix a11y skip links by manually focusing on the href target element.
|
||||
* This prevents unwanted/unexpected reloads of the page.
|
||||
*/
|
||||
export function prepareSkipLinks() {
|
||||
document.querySelectorAll('.sr-only-focusable-custom:not([data-prepared])').forEach((el) => {
|
||||
el.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
const target = el.getAttribute('href')!;
|
||||
const $target = document.querySelector(target) as HTMLElement;
|
||||
|
||||
if ($target) {
|
||||
$target.setAttribute('tabindex', '-1');
|
||||
$target.focus();
|
||||
$target.removeAttribute('tabindex');
|
||||
|
||||
$target.dataset.prepared = 'true';
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
@@ -42,9 +42,10 @@ export default class AvatarEditor extends Component {
|
||||
return (
|
||||
<div className={classList(['AvatarEditor', 'Dropdown', this.attrs.className, this.loading && 'loading', this.isDraggedOver && 'dragover'])}>
|
||||
<Avatar user={user} loading="eager" />
|
||||
<a
|
||||
<button
|
||||
className={user.avatarUrl() ? 'Dropdown-toggle' : 'Dropdown-toggle AvatarEditor--noAvatar'}
|
||||
title={app.translator.trans('core.forum.user.avatar_upload_tooltip')}
|
||||
ariaLabel={app.translator.trans('core.forum.user.avatar_upload_tooltip')}
|
||||
data-toggle="dropdown"
|
||||
onclick={this.quickUpload.bind(this)}
|
||||
ondragover={this.enableDragover.bind(this)}
|
||||
@@ -60,7 +61,7 @@ export default class AvatarEditor extends Component {
|
||||
) : (
|
||||
<Icon name={'fas fa-plus-circle'} />
|
||||
)}
|
||||
</a>
|
||||
</button>
|
||||
<ul className="Dropdown-menu Menu">{listItems(this.controlItems().toArray())}</ul>
|
||||
</div>
|
||||
);
|
||||
|
@@ -76,15 +76,15 @@ export default class DiscussionListItem<CustomAttrs extends IDiscussionListItemA
|
||||
viewItems(): ItemList<Mithril.Children> {
|
||||
const items = new ItemList<Mithril.Children>();
|
||||
|
||||
items.add('slidableUnderneath', this.slidableUnderneathView(), 90);
|
||||
items.add('content', this.contentView(), 80);
|
||||
|
||||
const controls = DiscussionControls.controls(this.attrs.discussion, this).toArray();
|
||||
|
||||
if (controls.length) {
|
||||
items.add('controls', this.controlsView(controls), 100);
|
||||
items.add('controls', this.controlsView(controls), 70);
|
||||
}
|
||||
|
||||
items.add('slidableUnderneath', this.slidableUnderneathView(), 90);
|
||||
items.add('content', this.contentView(), 80);
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
@@ -312,7 +312,7 @@ export default class DiscussionListItem<CustomAttrs extends IDiscussionListItemA
|
||||
className="DiscussionListItem-count"
|
||||
icon={showUnread ? [<Icon name={'fas fa-check _checkmark'} />, <Icon name={'fas fa-comment _comment'} />] : <Icon name={'far fa-comment'} />}
|
||||
label={showUnread ? abbreviateNumber(discussion.unreadCount()) : abbreviateNumber(discussion.replyCount())}
|
||||
a11yLabel={app.translator.trans(a11yKey, { count: discussion.replyCount() })}
|
||||
ariaLabel={app.translator.trans(a11yKey, { count: discussion.unreadCount() })}
|
||||
onclick={showUnread ? this.markAsRead.bind(this) : undefined}
|
||||
/>
|
||||
);
|
||||
|
@@ -2,6 +2,7 @@ import app from '../../forum/app';
|
||||
import DiscussionList from './DiscussionList';
|
||||
import Component from '../../common/Component';
|
||||
import DiscussionPage from './DiscussionPage';
|
||||
import { prepareSkipLinks } from '../../common/utils/a11y';
|
||||
|
||||
const hotEdge = (e) => {
|
||||
if (e.pageX < 10) app.pane.show();
|
||||
@@ -22,7 +23,14 @@ export default class DiscussionListPane extends Component {
|
||||
return;
|
||||
}
|
||||
|
||||
return <aside className="DiscussionListPane">{this.enoughSpace() && <DiscussionList state={this.attrs.state} />}</aside>;
|
||||
return (
|
||||
<aside className="DiscussionListPane">
|
||||
<a href="#page-main" class="sr-only sr-only-focusable-custom" oncreate={() => prepareSkipLinks()}>
|
||||
{app.translator.trans('core.forum.discussion_list.skip_discussion_list_pane')}
|
||||
</a>
|
||||
{this.enoughSpace() && <DiscussionList state={this.attrs.state} />}
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
||||
oncreate(vnode) {
|
||||
@@ -34,7 +42,12 @@ export default class DiscussionListPane extends Component {
|
||||
// and hide the pane respectively. We also create a 10px 'hot edge' on the
|
||||
// left of the screen to activate the pane.
|
||||
const pane = app.pane;
|
||||
$list.hover(pane.show.bind(pane), pane.onmouseleave.bind(pane));
|
||||
$list.on('mouseenter', pane.show.bind(pane));
|
||||
$list.on('mouseleave', pane.onmouseleave.bind(pane));
|
||||
// a11y: when tabbing into the pane (focus) we should also show the pane.
|
||||
// and when tabbing out, we should hide the pane.
|
||||
$list.on('focus', 'a, .Button', pane.show.bind(pane));
|
||||
$list.on('blur', 'a, .Button', pane.onmouseleave.bind(pane));
|
||||
|
||||
$(document).on('mousemove', hotEdge);
|
||||
|
||||
|
@@ -1,9 +1,11 @@
|
||||
import app from '../app';
|
||||
import Component from '../../common/Component';
|
||||
import type { ComponentAttrs } from '../../common/Component';
|
||||
import type Mithril from 'mithril';
|
||||
import classList from '../../common/utils/classList';
|
||||
import ItemList from '../../common/utils/ItemList';
|
||||
import LoadingIndicator from '../../common/components/LoadingIndicator';
|
||||
import { prepareSkipLinks } from '../../common/utils/a11y';
|
||||
|
||||
export interface PageStructureAttrs extends ComponentAttrs {
|
||||
hero?: () => Mithril.Children;
|
||||
@@ -51,7 +53,11 @@ export default class PageStructure<CustomAttrs extends PageStructureAttrs = Page
|
||||
}
|
||||
|
||||
main(): Mithril.Children {
|
||||
return <div className="Page-main">{this.attrs.loading ? this.loadingItems().toArray() : this.mainItems().toArray()}</div>;
|
||||
return (
|
||||
<div className="Page-main" id="page-main">
|
||||
{this.attrs.loading ? this.loadingItems().toArray() : this.mainItems().toArray()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
containerItems(): ItemList<Mithril.Children> {
|
||||
@@ -70,6 +76,14 @@ export default class PageStructure<CustomAttrs extends PageStructureAttrs = Page
|
||||
sidebarItems(): ItemList<Mithril.Children> {
|
||||
const items = new ItemList<Mithril.Children>();
|
||||
|
||||
items.add(
|
||||
'skipToMainContent',
|
||||
<a href="#main-content" className="sr-only sr-only-focusable-custom" oncreate={() => prepareSkipLinks()}>
|
||||
{app.translator.trans('core.forum.index.skip_to_main_content')}
|
||||
</a>,
|
||||
200
|
||||
);
|
||||
|
||||
items.add('sidebar', (this.attrs.sidebar && this.attrs.sidebar()) || null, 100);
|
||||
|
||||
return items;
|
||||
@@ -88,6 +102,10 @@ export default class PageStructure<CustomAttrs extends PageStructureAttrs = Page
|
||||
}
|
||||
|
||||
providedContent(): Mithril.Children {
|
||||
return <div className="Page-content">{this.content}</div>;
|
||||
return (
|
||||
<div className="Page-content" id="main-content">
|
||||
{this.content}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -20,10 +20,6 @@
|
||||
border-radius: 100%;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
span& {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
.Avatar--size(@size) {
|
||||
|
@@ -199,3 +199,13 @@ blockquote ol:last-child {
|
||||
.text-colored {
|
||||
color: var(--color);
|
||||
}
|
||||
|
||||
.sr-only-focusable-custom:focus {
|
||||
clip: unset;
|
||||
width: auto;
|
||||
height: auto;
|
||||
border-width: medium;
|
||||
background: #000;
|
||||
color: #fff;
|
||||
z-index: 100;
|
||||
}
|
||||
|
@@ -136,6 +136,9 @@
|
||||
|
||||
@screen-phone-max: (@screen-tablet - 0.02);
|
||||
|
||||
@screen-small-phone: 320px;
|
||||
@screen-small-phone-max: (@screen-small-phone + 0.02);
|
||||
|
||||
@screen-tablet: 768px;
|
||||
@screen-tablet-max: (@screen-desktop - 0.02);
|
||||
|
||||
@@ -148,6 +151,7 @@
|
||||
@screen-desktop-xxxl: 3000px;
|
||||
|
||||
@phone: ~"(max-width: @{screen-phone-max})";
|
||||
@small-phone: ~"(max-width: @{screen-small-phone-max})";
|
||||
@tablet: ~"(min-width: @{screen-tablet}) and (max-width: @{screen-tablet-max})";
|
||||
@desktop: ~"(min-width: @{screen-desktop}) and (max-width: @{screen-desktop-max})";
|
||||
@desktop-hd: ~"(min-width: @{screen-desktop-hd})";
|
||||
|
@@ -15,6 +15,8 @@
|
||||
inset: 0;
|
||||
border-radius: 100%;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
border: 0;
|
||||
@@ -23,6 +25,7 @@
|
||||
opacity: 0.7;
|
||||
}
|
||||
&:hover .Dropdown-toggle,
|
||||
.Dropdown-toggle:focus,
|
||||
&.open .Dropdown-toggle,
|
||||
&.loading .Dropdown-toggle,
|
||||
&.dragover .Dropdown-toggle {
|
||||
|
@@ -306,7 +306,7 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&:hover, &:focus {
|
||||
._checkmark {
|
||||
display: block;
|
||||
}
|
||||
@@ -317,3 +317,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media @small-phone {
|
||||
.DiscussionListItem-info {
|
||||
max-width: 160px;
|
||||
}
|
||||
}
|
||||
|
@@ -459,6 +459,7 @@ core:
|
||||
empty_text: It looks as though there are no discussions here.
|
||||
load_more_button: => core.ref.load_more
|
||||
replied_text: "{username} replied {ago}"
|
||||
skip_discussion_list_pane: Skip the discussion list pane
|
||||
started_text: "{username} started {ago}"
|
||||
total_replies_a11y_label: "{count, plural, one {# reply} other {# replies}}"
|
||||
unread_replies_a11y_label: "{count, plural, one {# unread reply} other {# unread replies}}. Mark unread {count, plural, one {reply} other {replies}} as read."
|
||||
@@ -496,6 +497,7 @@ core:
|
||||
meta_title_text: => core.ref.all_discussions
|
||||
refresh_tooltip: => core.ref.refresh
|
||||
start_discussion_button: => core.ref.start_a_discussion
|
||||
skip_to_main_content: Skip to main content
|
||||
toggle_sidenav_dropdown_accessible_label: Toggle navigation dropdown menu
|
||||
|
||||
# These translations are used by the sorting control above the discussion list.
|
||||
@@ -911,6 +913,10 @@ core:
|
||||
next_page_button: => core.ref.next_page
|
||||
previous_page_button: => core.ref.previous_page
|
||||
|
||||
# Translations in this namespace are displayed by the basic HTML forum layout.
|
||||
layout:
|
||||
skip_to_content: Skip to content
|
||||
|
||||
# Translations in this namespace are displayed by the Log Out confirmation interface.
|
||||
log_out:
|
||||
log_out_button: => core.ref.log_out
|
||||
|
@@ -54,9 +54,7 @@ class AdminPayload
|
||||
$document->payload['extensions'] = $this->extensions->getExtensions()->toArray();
|
||||
|
||||
$document->payload['displayNameDrivers'] = array_keys($this->container->make('flarum.user.display_name.supported_drivers'));
|
||||
$document->payload['slugDrivers'] = array_map(function ($resourceDrivers) {
|
||||
return array_keys($resourceDrivers);
|
||||
}, $this->container->make('flarum.http.slugDrivers'));
|
||||
$document->payload['slugDrivers'] = array_map(array_keys(...), $this->container->make('flarum.http.slugDrivers'));
|
||||
$document->payload['searchDrivers'] = $this->getSearchDrivers();
|
||||
|
||||
$document->payload['phpVersion'] = $this->appInfo->identifyPHPVersion();
|
||||
|
@@ -108,27 +108,27 @@ class Context extends BaseContext
|
||||
return $this->parameters[$key] ?? $default;
|
||||
}
|
||||
|
||||
public function creating(string|null $resource = null): bool
|
||||
public function creating(?string $resource = null): bool
|
||||
{
|
||||
return $this->endpoint instanceof Endpoint\Create && (! $resource || is_a($this->collection, $resource));
|
||||
}
|
||||
|
||||
public function updating(string|null $resource = null): bool
|
||||
public function updating(?string $resource = null): bool
|
||||
{
|
||||
return $this->endpoint instanceof Endpoint\Update && (! $resource || is_a($this->collection, $resource));
|
||||
}
|
||||
|
||||
public function deleting(string|null $resource = null): bool
|
||||
public function deleting(?string $resource = null): bool
|
||||
{
|
||||
return $this->endpoint instanceof Endpoint\Delete && (! $resource || is_a($this->collection, $resource));
|
||||
}
|
||||
|
||||
public function showing(string|null $resource = null): bool
|
||||
public function showing(?string $resource = null): bool
|
||||
{
|
||||
return $this->endpoint instanceof Endpoint\Show && (! $resource || is_a($this->collection, $resource));
|
||||
}
|
||||
|
||||
public function listing(string|null $resource = null): bool
|
||||
public function listing(?string $resource = null): bool
|
||||
{
|
||||
return $this->endpoint instanceof Endpoint\Index && (! $resource || is_a($this->collection, $resource));
|
||||
}
|
||||
|
@@ -27,7 +27,7 @@ class UninstallExtensionController extends AbstractDeleteController
|
||||
|
||||
$name = Arr::get($request->getQueryParams(), 'name');
|
||||
|
||||
if ($this->extensions->getExtension($name) == null) {
|
||||
if ($this->extensions->getExtension($name) === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -103,7 +103,7 @@ trait ExtractsListingParams
|
||||
return [
|
||||
'filter' => RequestUtil::extractFilter($context->request),
|
||||
'sort' => RequestUtil::extractSort($context->request, $this->defaultSort, $this->getAvailableSorts($context)),
|
||||
'limit' => $limit = (RequestUtil::extractLimit($context->request, $this->limit, $this->maxLimit) ?? null),
|
||||
'limit' => $limit = RequestUtil::extractLimit($context->request, $this->limit, $this->maxLimit),
|
||||
'offset' => RequestUtil::extractOffset($context->request, $limit),
|
||||
];
|
||||
}
|
||||
|
@@ -60,7 +60,7 @@ trait HasAuthorization
|
||||
: ($this->authenticated)($context));
|
||||
}
|
||||
|
||||
public function getAuthorized(Context $context): string|null
|
||||
public function getAuthorized(Context $context): ?string
|
||||
{
|
||||
if (! is_callable($this->ability)) {
|
||||
return $this->ability;
|
||||
|
@@ -13,7 +13,6 @@ use Closure;
|
||||
use Flarum\Api\Resource\AbstractDatabaseResource;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Support\Str;
|
||||
use Tobyz\JsonApiServer\Context;
|
||||
|
||||
@@ -147,13 +146,10 @@ trait HasEagerLoading
|
||||
|
||||
protected function compileWhereEagerLoads(Context $context): array
|
||||
{
|
||||
$relations = [];
|
||||
|
||||
foreach ($this->loadRelationWhere as $name => $callable) {
|
||||
$relations[$name] = function ($query) use ($callable, $context) {
|
||||
$callable($query, $context);
|
||||
};
|
||||
}
|
||||
$relations = array_map(
|
||||
callback: fn ($callable) => fn ($query) => $callable($query, $context),
|
||||
array: $this->loadRelationWhere
|
||||
);
|
||||
|
||||
return $relations;
|
||||
}
|
||||
|
@@ -89,7 +89,7 @@ class Create extends Endpoint
|
||||
final protected function fillDefaultValues(Context $context, array &$data): void
|
||||
{
|
||||
foreach ($context->fields($context->resource) as $field) {
|
||||
if (! has_value($data, $field) && ($default = $field->default)) {
|
||||
if (($default = $field->default) && ! has_value($data, $field)) {
|
||||
set_value($data, $field, $default($context->withField($field)));
|
||||
}
|
||||
}
|
||||
|
@@ -43,7 +43,6 @@ class Index extends Endpoint
|
||||
use HasCustomHooks;
|
||||
|
||||
public Closure $paginationResolver;
|
||||
public ?string $defaultSort = null;
|
||||
protected ?Closure $query = null;
|
||||
|
||||
public function __construct(string $name)
|
||||
|
@@ -16,7 +16,7 @@ use Psr\Http\Server\RequestHandlerInterface as Handler;
|
||||
|
||||
class FakeHttpMethods implements Middleware
|
||||
{
|
||||
const HEADER_NAME = 'x-http-method-override';
|
||||
public const HEADER_NAME = 'x-http-method-override';
|
||||
|
||||
public function process(Request $request, Handler $handler): Response
|
||||
{
|
||||
|
@@ -42,7 +42,9 @@ class ThrottleApi implements Middleware
|
||||
// Anything else is ignored.
|
||||
if ($result === false) {
|
||||
return false;
|
||||
} elseif ($result === true) {
|
||||
}
|
||||
|
||||
if ($result === true) {
|
||||
$throttle = true;
|
||||
}
|
||||
}
|
||||
|
@@ -74,9 +74,9 @@ abstract class AbstractDatabaseResource extends AbstractResource implements
|
||||
{
|
||||
if ($field instanceof Relationship) {
|
||||
return $this->getRelationshipValue($model, $field, $context);
|
||||
} else {
|
||||
return $this->getAttributeValue($model, $field, $context);
|
||||
}
|
||||
|
||||
return $this->getAttributeValue($model, $field, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -79,18 +79,18 @@ class AccessTokenResource extends AbstractDatabaseResource
|
||||
{
|
||||
return [
|
||||
Schema\Str::make('token')
|
||||
->visible(function (AccessToken $token, Context $context) {
|
||||
->visible(function (#[\SensitiveParameter] AccessToken $token, Context $context) {
|
||||
return $context->getActor()->id === $token->user_id && ! in_array('token', $token->getHidden(), true);
|
||||
}),
|
||||
Schema\Integer::make('userId'),
|
||||
Schema\DateTime::make('createdAt'),
|
||||
Schema\DateTime::make('lastActivityAt'),
|
||||
Schema\Boolean::make('isCurrent')
|
||||
->get(function (AccessToken $token, Context $context) {
|
||||
->get(function (#[\SensitiveParameter] AccessToken $token, Context $context) {
|
||||
return $token->token === $context->request->getAttribute('session')->get('access_token');
|
||||
}),
|
||||
Schema\Boolean::make('isSessionToken')
|
||||
->get(function (AccessToken $token) {
|
||||
->get(function (#[\SensitiveParameter] AccessToken $token) {
|
||||
return in_array($token->type, [SessionAccessToken::$type, RememberAccessToken::$type], true);
|
||||
}),
|
||||
Schema\Str::make('title')
|
||||
@@ -99,7 +99,7 @@ class AccessTokenResource extends AbstractDatabaseResource
|
||||
->maxLength(255),
|
||||
Schema\Str::make('lastIpAddress'),
|
||||
Schema\Str::make('device')
|
||||
->get(function (AccessToken $token) {
|
||||
->get(function (#[\SensitiveParameter] AccessToken $token) {
|
||||
$agent = new Agent();
|
||||
$agent->setUserAgent($token->last_user_agent);
|
||||
|
||||
|
@@ -21,15 +21,11 @@ interface Listable
|
||||
{
|
||||
/**
|
||||
* Create a query object for the current request.
|
||||
*
|
||||
* @param Context $context
|
||||
*/
|
||||
public function query(Context $context): object;
|
||||
|
||||
/**
|
||||
* Get results from the given query.
|
||||
*
|
||||
* @param Context $context
|
||||
*/
|
||||
public function results(object $query, Context $context): iterable;
|
||||
|
||||
|
@@ -377,7 +377,7 @@ class UserResource extends AbstractDatabaseResource
|
||||
return $model;
|
||||
}
|
||||
|
||||
private function applyToken(User $user, RegistrationToken $token): void
|
||||
private function applyToken(User $user, #[\SensitiveParameter] RegistrationToken $token): void
|
||||
{
|
||||
foreach ($token->user_attributes as $k => $v) {
|
||||
if ($k === 'avatar_url') {
|
||||
@@ -435,7 +435,7 @@ class UserResource extends AbstractDatabaseResource
|
||||
|
||||
try {
|
||||
$response = $client->get($url);
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Exception) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -446,7 +446,7 @@ class UserResource extends AbstractDatabaseResource
|
||||
return $response->getBody()->getContents();
|
||||
}
|
||||
|
||||
private function fulfillToken(User $user, RegistrationToken $token): void
|
||||
private function fulfillToken(User $user, #[\SensitiveParameter] RegistrationToken $token): void
|
||||
{
|
||||
$token->delete();
|
||||
|
||||
|
@@ -93,8 +93,8 @@ class Serializer extends \Tobyz\JsonApiServer\Serializer
|
||||
|
||||
$this->whenResolved($value, function (mixed $value) use ($key, $field, $context) {
|
||||
if (
|
||||
($value = $field->serializeValue($value, $context)) ||
|
||||
! $field instanceof Relationship
|
||||
! $field instanceof Relationship ||
|
||||
($value = $field->serializeValue($value, $context))
|
||||
) {
|
||||
set_value($this->map[$key], $field, $value);
|
||||
}
|
||||
|
@@ -23,10 +23,10 @@ use Symfony\Component\Console\Event\ConsoleErrorEvent;
|
||||
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
|
||||
class Server
|
||||
readonly class Server
|
||||
{
|
||||
public function __construct(
|
||||
private readonly SiteInterface $site
|
||||
private SiteInterface $site
|
||||
) {
|
||||
}
|
||||
|
||||
|
@@ -51,8 +51,6 @@ class DatabaseMigrationRepository implements MigrationRepositoryInterface
|
||||
|
||||
/**
|
||||
* Create the migration repository data store.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function createRepository(): void
|
||||
{
|
||||
|
@@ -50,7 +50,7 @@ class Migrator
|
||||
// First we will just make sure that there are any migrations to run. If there
|
||||
// aren't, we will just make a note of it to the developer so they're aware
|
||||
// that all the migrations have been run against this database system.
|
||||
if (count($migrations) == 0) {
|
||||
if (count($migrations) === 0) {
|
||||
$this->note('<info>Nothing to migrate.</info>');
|
||||
|
||||
return;
|
||||
@@ -115,7 +115,7 @@ class Migrator
|
||||
public function reset(string $path, ?Extension $extension = null): int
|
||||
{
|
||||
$migrations = array_reverse($this->repository->getRan(
|
||||
$extension ? $extension->getId() : null
|
||||
$extension?->getId()
|
||||
));
|
||||
|
||||
$count = count($migrations);
|
||||
@@ -176,11 +176,8 @@ class Migrator
|
||||
|
||||
/**
|
||||
* Get all of the migration files in a given path.
|
||||
*
|
||||
* @param string $path
|
||||
* @return array
|
||||
*/
|
||||
public function getMigrationFiles($path)
|
||||
public function getMigrationFiles(string $path): array
|
||||
{
|
||||
$files = $this->files->glob($path.'/*_*.php');
|
||||
|
||||
|
@@ -283,8 +283,6 @@ class Discussion extends AbstractModel
|
||||
/**
|
||||
* Query the discussion's participants (a list of unique users who have
|
||||
* posted in the discussion).
|
||||
*
|
||||
* @return Builder
|
||||
*/
|
||||
public function participants(): Builder
|
||||
{
|
||||
|
@@ -30,7 +30,7 @@ class DiscussionMetadataUpdater
|
||||
{
|
||||
$discussion = $event->post->discussion;
|
||||
|
||||
if ($discussion && $discussion->exists) {
|
||||
if ($discussion?->exists) {
|
||||
$discussion->refreshCommentCount();
|
||||
$discussion->refreshLastPost();
|
||||
$discussion->refreshParticipantCount();
|
||||
@@ -58,7 +58,7 @@ class DiscussionMetadataUpdater
|
||||
{
|
||||
$discussion = $event->post->discussion;
|
||||
|
||||
if ($discussion && $discussion->exists) {
|
||||
if ($discussion?->exists) {
|
||||
$discussion->refreshCommentCount();
|
||||
$discussion->refreshParticipantCount();
|
||||
$discussion->refreshLastPost();
|
||||
@@ -70,7 +70,7 @@ class DiscussionMetadataUpdater
|
||||
{
|
||||
$discussion = $post->discussion;
|
||||
|
||||
if ($discussion && $discussion->exists) {
|
||||
if ($discussion?->exists) {
|
||||
$discussion->refreshCommentCount();
|
||||
$discussion->refreshParticipantCount();
|
||||
|
||||
|
@@ -36,7 +36,6 @@ class DiscussionRepository
|
||||
/**
|
||||
* Get a query containing the IDs of discussions which a user has read completely.
|
||||
*
|
||||
* @param User $user
|
||||
* @return Builder<Discussion>
|
||||
*/
|
||||
public function getReadIdsQuery(User $user): Builder
|
||||
@@ -52,7 +51,6 @@ class DiscussionRepository
|
||||
* Scope a query to only include records that are visible to a user.
|
||||
*
|
||||
* @param Builder<Discussion> $query
|
||||
* @param User|null $user
|
||||
* @return Builder<Discussion>
|
||||
*/
|
||||
protected function scopeVisibleTo(Builder $query, ?User $user = null): Builder
|
||||
|
@@ -42,7 +42,6 @@ class FulltextFilter extends AbstractFulltextFilter
|
||||
|
||||
protected function sqlite(DatabaseSearchState $state, string $value): void
|
||||
{
|
||||
/** @var Builder $query */
|
||||
$query = $state->getQuery();
|
||||
|
||||
$query->where(function (Builder $query) use ($state, $value) {
|
||||
@@ -63,7 +62,6 @@ class FulltextFilter extends AbstractFulltextFilter
|
||||
|
||||
protected function mysql(DatabaseSearchState $state, string $value): void
|
||||
{
|
||||
/** @var Builder $query */
|
||||
$query = $state->getQuery();
|
||||
|
||||
// Replace all non-word characters with spaces.
|
||||
@@ -119,7 +117,6 @@ class FulltextFilter extends AbstractFulltextFilter
|
||||
{
|
||||
$searchConfig = $this->settings->get('pgsql_search_configuration');
|
||||
|
||||
/** @var Builder $query */
|
||||
$query = $state->getQuery();
|
||||
|
||||
$grammar = $query->getGrammar();
|
||||
|
@@ -35,8 +35,6 @@ class Auth implements ExtenderInterface
|
||||
* password checkers can run.
|
||||
* - `false` if the given password is invalid, and no other checkers should be considered.
|
||||
* Evaluation will be immediately halted if any checkers return `false`.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function addPasswordChecker(string $identifier, callable|string $callback): self
|
||||
{
|
||||
@@ -49,7 +47,6 @@ class Auth implements ExtenderInterface
|
||||
* Remove a password checker.
|
||||
*
|
||||
* @param string $identifier: The unique identifier of the password checker to remove.
|
||||
* @return self
|
||||
*/
|
||||
public function removePasswordChecker(string $identifier): self
|
||||
{
|
||||
|
@@ -38,7 +38,6 @@ class Conditional implements ExtenderInterface
|
||||
*
|
||||
* @param string $extensionId The ID of the extension.
|
||||
* @param callable|string $extenders A callable returning an array of extenders, or an invokable class string.
|
||||
* @return self
|
||||
*/
|
||||
public function whenExtensionEnabled(string $extensionId, callable|string $extenders): self
|
||||
{
|
||||
@@ -52,7 +51,6 @@ class Conditional implements ExtenderInterface
|
||||
*
|
||||
* @param string $extensionId The ID of the extension.
|
||||
* @param callable|string $extenders A callable returning an array of extenders, or an invokable class string.
|
||||
* @return self
|
||||
*/
|
||||
public function whenExtensionDisabled(string $extensionId, callable|string $extenders): self
|
||||
{
|
||||
@@ -67,7 +65,6 @@ class Conditional implements ExtenderInterface
|
||||
* @param bool|callable $condition A boolean or callable that should return a boolean.
|
||||
* If this evaluates to true, the extenders will be applied.
|
||||
* @param callable|string $extenders A callable returning an array of extenders, or an invokable class string.
|
||||
* @return self
|
||||
*/
|
||||
public function when(callable|bool $condition, callable|string $extenders): self
|
||||
{
|
||||
@@ -81,10 +78,6 @@ class Conditional implements ExtenderInterface
|
||||
|
||||
/**
|
||||
* Iterates over the conditions and applies the associated extenders if the conditions are met.
|
||||
*
|
||||
* @param Container $container
|
||||
* @param Extension|null $extension
|
||||
* @return void
|
||||
*/
|
||||
public function extend(Container $container, ?Extension $extension = null): void
|
||||
{
|
||||
|
@@ -23,7 +23,6 @@ class Console implements ExtenderInterface
|
||||
* Add a command to the console.
|
||||
*
|
||||
* @param class-string<AbstractCommand|\Illuminate\Console\Command> $command: ::class attribute of command class, which must extend \Flarum\Console\AbstractCommand.
|
||||
* @return self
|
||||
*/
|
||||
public function command(string $command): self
|
||||
{
|
||||
@@ -48,7 +47,6 @@ class Console implements ExtenderInterface
|
||||
* for more information on available methods and what they do.
|
||||
*
|
||||
* @param array $args An array of args to call the command with.
|
||||
* @return self
|
||||
*/
|
||||
public function schedule(string $command, callable|string $callback, array $args = []): self
|
||||
{
|
||||
|
@@ -18,9 +18,6 @@ class Csrf implements ExtenderInterface
|
||||
|
||||
/**
|
||||
* Exempt a named route from CSRF checks.
|
||||
*
|
||||
* @param string $routeName
|
||||
* @return self
|
||||
*/
|
||||
public function exemptRoute(string $routeName): self
|
||||
{
|
||||
|
@@ -32,7 +32,6 @@ class ErrorHandling implements ExtenderInterface
|
||||
*
|
||||
* @param string $errorType: Type of the error.
|
||||
* @param int $httpStatus: The status code for this error.
|
||||
* @return self
|
||||
*/
|
||||
public function status(string $errorType, int $httpStatus): self
|
||||
{
|
||||
@@ -52,7 +51,6 @@ class ErrorHandling implements ExtenderInterface
|
||||
*
|
||||
* @param string $exceptionClass: The ::class attribute of the exception class.
|
||||
* @param string $errorType: Type of the error.
|
||||
* @return self
|
||||
*/
|
||||
public function type(string $exceptionClass, string $errorType): self
|
||||
{
|
||||
@@ -77,7 +75,6 @@ class ErrorHandling implements ExtenderInterface
|
||||
*
|
||||
* @param string $exceptionClass: The ::class attribute of the exception class.
|
||||
* @param string $handlerClass: The ::class attribute of the handler class.
|
||||
* @return self
|
||||
*/
|
||||
public function handler(string $exceptionClass, string $handlerClass): self
|
||||
{
|
||||
@@ -99,7 +96,6 @@ class ErrorHandling implements ExtenderInterface
|
||||
* {@see Reporter} interface.
|
||||
*
|
||||
* @param class-string<Reporter> $reporterClass: The ::class attribute of the reporter class.
|
||||
* @return self
|
||||
*/
|
||||
public function reporter(string $reporterClass): self
|
||||
{
|
||||
|
@@ -29,8 +29,6 @@ class Event implements ExtenderInterface
|
||||
* - The ::class attribute of a class with a public `handle` method, which accepts an instance of the event as a parameter.
|
||||
* - An array, where the first argument is an object or class name, and the second argument is the method on the
|
||||
* first argument that should be executed as the listener.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function listen(string $event, callable|string $listener): self
|
||||
{
|
||||
@@ -47,7 +45,6 @@ class Event implements ExtenderInterface
|
||||
* @see https://laravel.com/docs/11.x/events#writing-event-subscribers
|
||||
*
|
||||
* @param string $subscriber: The ::class attribute of the subscriber class.
|
||||
* @return self
|
||||
*/
|
||||
public function subscribe(string $subscriber): self
|
||||
{
|
||||
|
@@ -52,8 +52,6 @@ class Filesystem implements ExtenderInterface
|
||||
* ```
|
||||
*
|
||||
* @see https://laravel.com/docs/11.x/filesystem#configuration
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function disk(string $name, callable|string $callback): self
|
||||
{
|
||||
@@ -68,7 +66,6 @@ class Filesystem implements ExtenderInterface
|
||||
* @param string $name: The name of the driver.
|
||||
* @param string $driverClass: The ::class attribute of the driver.
|
||||
* Driver must implement `\Flarum\Filesystem\DriverInterface`.
|
||||
* @return self
|
||||
*/
|
||||
public function driver(string $name, string $driverClass): self
|
||||
{
|
||||
|
@@ -36,8 +36,6 @@ class Formatter implements ExtenderInterface, LifecycleInterface
|
||||
* - \s9e\TextFormatter\Configurator $configurator
|
||||
*
|
||||
* The callable should return void.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function configure(callable|string $callback): self
|
||||
{
|
||||
@@ -60,8 +58,6 @@ class Formatter implements ExtenderInterface, LifecycleInterface
|
||||
*
|
||||
* The callback should return:
|
||||
* - string $text: The text to be parsed.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function parse(callable|string $callback): self
|
||||
{
|
||||
@@ -82,8 +78,6 @@ class Formatter implements ExtenderInterface, LifecycleInterface
|
||||
*
|
||||
* The callback should return:
|
||||
* - string $xml: The text to be unparsed.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function unparse(callable|string $callback): self
|
||||
{
|
||||
@@ -106,8 +100,6 @@ class Formatter implements ExtenderInterface, LifecycleInterface
|
||||
*
|
||||
* The callback should return:
|
||||
* - string $xml: The xml to be rendered.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function render(callable|string $callback): self
|
||||
{
|
||||
|
@@ -50,7 +50,6 @@ class Frontend implements ExtenderInterface
|
||||
* Add a CSS file to load in the frontend.
|
||||
*
|
||||
* @param string $path: The path to the CSS file.
|
||||
* @return self
|
||||
*/
|
||||
public function css(string $path): self
|
||||
{
|
||||
@@ -63,7 +62,6 @@ class Frontend implements ExtenderInterface
|
||||
* Add a JavaScript file to load in the frontend.
|
||||
*
|
||||
* @param string $path: The path to the JavaScript file.
|
||||
* @return self
|
||||
*/
|
||||
public function js(string $path): self
|
||||
{
|
||||
@@ -98,8 +96,6 @@ class Frontend implements ExtenderInterface
|
||||
* - \Psr\Http\Message\ServerRequestInterface $request
|
||||
*
|
||||
* The callable should return void.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function route(string $path, string $name, callable|string|null $content = null): self
|
||||
{
|
||||
@@ -113,7 +109,6 @@ class Frontend implements ExtenderInterface
|
||||
* This is necessary before overriding a route.
|
||||
*
|
||||
* @param string $name: The name of the route.
|
||||
* @return self
|
||||
*/
|
||||
public function removeRoute(string $name): self
|
||||
{
|
||||
@@ -133,8 +128,6 @@ class Frontend implements ExtenderInterface
|
||||
*
|
||||
* The callable should return void.
|
||||
* @param int $priority: The priority of the content. Higher priorities are executed first.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function content(callable|string|null $callback, int $priority = 0): self
|
||||
{
|
||||
@@ -165,9 +158,6 @@ class Frontend implements ExtenderInterface
|
||||
* ]
|
||||
* ]);
|
||||
* ```
|
||||
*
|
||||
* @param callable|array $preloads
|
||||
* @return self
|
||||
*/
|
||||
public function preloads(callable|array $preloads): self
|
||||
{
|
||||
|
@@ -19,8 +19,8 @@ use s9e\TextFormatter\Utils;
|
||||
|
||||
class Link implements ExtenderInterface
|
||||
{
|
||||
protected Closure|null $setRel = null;
|
||||
protected Closure|null $setTarget = null;
|
||||
protected ?Closure $setRel = null;
|
||||
protected ?Closure $setTarget = null;
|
||||
|
||||
public function setRel(Closure $callable): self
|
||||
{
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user