1
0
mirror of https://github.com/flarum/core.git synced 2025-08-18 14:22:02 +02:00

Compare commits

..

17 Commits

Author SHA1 Message Date
flarum-bot
a46ce07255 Bundled output for commit 8e404e4415
Includes transpiled JS/TS, and Typescript declaration files (typings).

[skip ci]
2025-06-01 07:48:18 +00:00
David Sevilla Martin
8e404e4415 fix: prevent <button>'s from becoming form submit when they shouldn't (#4221)
* fix: prevent <button>'s from becoming form submit when they shouldn't

Adds `type=button` to most `<button>` usage (except the AdminPage#submitButton). The first button of type submit (which is the default in HTML if undeclared) becomes what the enter keybind presses in a form. This makes the behavior with these components make more sense.

* Prettier on framework
2025-06-01 08:45:32 +01:00
Sami Mazouz
4e95d06190 chore: change webpack config version 2025-04-25 11:36:25 +01:00
Sami Mazouz
e1a91dcd77 chore: prepare 2.0.0-beta.3 2025-04-25 10:19:54 +01:00
Sami Mazouz
fbe7be69ef chore: audit-fix 2025-04-25 09:52:11 +01:00
Márk Magyar
7f946ca0dd chore: code cleanup (#4161) 2025-04-25 09:36:49 +01:00
flarum-bot
cc05a6dd3b Bundled output for commit 649be7cb03
Includes transpiled JS/TS, and Typescript declaration files (typings).

[skip ci]
2025-04-25 08:20:15 +00:00
Sami Mazouz
649be7cb03 chore(a11y): misc a11y improvements (#4211) 2025-04-25 09:17:36 +01:00
Simon
f19007f424 Merge commit from fork 2025-04-18 09:45:41 +01:00
Robert Korulczyk
15112c2f40 Sanitize page in Tag (#4170) 2025-04-18 09:45:21 +01:00
flarum-bot
00ef0dd9d0 Bundled output for commit cae706a638
Includes transpiled JS/TS, and Typescript declaration files (typings).

[skip ci]
2025-04-18 08:40:38 +00:00
Sami Mazouz
cae706a638 feat: advanced admin registry extenders (#4209)
* feat: advanced admin registry extenders
* fix: admin extender execution order
2025-04-18 09:38:00 +01:00
flarum-bot
2be1932e54 Bundled output for commit 2339c23aae
Includes transpiled JS/TS, and Typescript declaration files (typings).

[skip ci]
2025-04-18 08:14:42 +00:00
Davide Iadeluca
2339c23aae refactor(core): improve extensibility of AppearancePage (#4200) 2025-04-18 09:12:10 +01:00
StyleCI Bot
2b08c30a22 Apply fixes from StyleCI 2025-04-11 09:07:58 +00:00
Sami Mazouz
fa88731fe1 chore: increase composer job timeout 2025-04-11 10:07:17 +01:00
Daniël Klabbers
65d8c16580 fix: issue with smtp non-tls connections (#4203) 2025-02-27 18:34:31 +01:00
254 changed files with 1069 additions and 1157 deletions

View File

@@ -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]

View File

@@ -19,7 +19,7 @@
}
],
"require": {
"flarum/core": "^2.0.0-beta.2",
"flarum/core": "^2.0.0-beta.3",
"flarum/approval": "^2.0"
},
"autoload": {

View File

@@ -19,7 +19,7 @@
}
],
"require": {
"flarum/core": "^2.0.0-beta.2",
"flarum/core": "^2.0.0-beta.3",
"flarum/flags": "^2.0"
},
"autoload": {

View File

@@ -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

File diff suppressed because one or more lines are too long

View File

@@ -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"
/>
);
}
});

View File

@@ -19,7 +19,7 @@
}
],
"require": {
"flarum/core": "^2.0.0-beta.2"
"flarum/core": "^2.0.0-beta.3"
},
"autoload": {
"psr-4": {

View File

@@ -19,7 +19,7 @@
}
],
"require": {
"flarum/core": "^2.0.0-beta.2"
"flarum/core": "^2.0.0-beta.3"
},
"autoload": {
"psr-4": {

View File

@@ -19,7 +19,7 @@
}
],
"require": {
"flarum/core": "^2.0.0-beta.2"
"flarum/core": "^2.0.0-beta.3"
},
"extra": {
"branch-alias": {

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -70,6 +70,7 @@ export default function addComposerAutocomplete() {
return (
<Tooltip text={name}>
<button
type="button"
key={emoji}
onclick={() => applySuggestion(emoji)}
onmouseenter={function () {

View File

@@ -19,7 +19,7 @@
}
],
"require": {
"flarum/core": "^2.0.0-beta.2"
"flarum/core": "^2.0.0-beta.3"
},
"autoload": {
"psr-4": {

View File

@@ -7,7 +7,7 @@
],
"license": "MIT",
"require": {
"flarum/core": "^2.0.0-beta.2"
"flarum/core": "^2.0.0-beta.3"
},
"extra": {
"branch-alias": {

View File

@@ -19,7 +19,7 @@
}
],
"require": {
"flarum/core": "^2.0.0-beta.2"
"flarum/core": "^2.0.0-beta.3"
},
"autoload": {
"psr-4": {

View File

@@ -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
View File

@@ -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

File diff suppressed because one or more lines are too long

View File

@@ -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" />
);
}
});
}

View File

@@ -19,7 +19,7 @@
}
],
"require": {
"flarum/core": "^2.0.0-beta.2"
"flarum/core": "^2.0.0-beta.3"
},
"extra": {
"branch-alias": {

View File

@@ -19,7 +19,7 @@
}
],
"require": {
"flarum/core": "^2.0.0-beta.2"
"flarum/core": "^2.0.0-beta.3"
},
"autoload": {
"psr-4": {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -17,7 +17,7 @@ export default class MentionsDropdownItem<CustomAttrs extends IMentionsDropdownI
const className = classList('MentionsDropdownItem', 'PostPreview', `MentionsDropdown-${mentionable.type()}`);
return (
<button className={className} {...attrs}>
<button className={className} type="button" {...attrs}>
<span className="PostPreview-content">{vnode.children}</span>
</button>
);

View File

@@ -15,6 +15,7 @@ export default class PostQuoteButton extends Fragment {
return (
<button
className="Button PostQuoteButton"
type="button"
onclick={() => {
reply(this.post, this.content);
}}

View File

@@ -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": [
{

View File

@@ -19,7 +19,7 @@
}
],
"require": {
"flarum/core": "^2.0.0-beta.2"
"flarum/core": "^2.0.0-beta.3"
},
"autoload": {
"psr-4": {

View File

@@ -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": {

View File

@@ -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,

View File

@@ -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

View File

@@ -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": {

View File

@@ -19,7 +19,7 @@
}
],
"require": {
"flarum/core": "^2.0.0-beta.2"
"flarum/core": "^2.0.0-beta.3"
},
"autoload": {
"psr-4": {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -279,6 +279,7 @@ export default class StatisticsWidget extends DashboardWidget {
return (
<button
className={classList('Button--ua-reset StatisticsWidget-entity', { active: this.selectedEntity === entity })}
type="button"
onclick={this.changeEntity.bind(this, entity)}
>
<h3 className="StatisticsWidget-heading">{app.translator.trans('flarum-statistics.admin.statistics.' + entity + '_heading')}</h3>

View File

@@ -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
View File

@@ -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

File diff suppressed because one or more lines are too long

View File

@@ -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
);
}

View File

@@ -19,7 +19,7 @@
}
],
"require": {
"flarum/core": "^2.0.0-beta.2"
"flarum/core": "^2.0.0-beta.3"
},
"autoload": {
"psr-4": {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -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;
}

View File

@@ -19,7 +19,7 @@
}
],
"require": {
"flarum/core": "^2.0.0-beta.2"
"flarum/core": "^2.0.0-beta.3"
},
"autoload": {
"psr-4": {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -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
);
}

View File

@@ -19,7 +19,7 @@
}
],
"require": {
"flarum/core": "^2.0.0-beta.2"
"flarum/core": "^2.0.0-beta.3"
},
"autoload": {
"psr-4": {

View File

@@ -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();

View File

@@ -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
*/

View File

@@ -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;

View File

@@ -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

View File

@@ -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;

View File

@@ -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.
*/

View 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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
framework/core/js/dist/forum.js generated vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -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();
}
/**

View File

@@ -67,7 +67,13 @@ export default abstract class AdminPage<CustomAttrs extends IPageAttrs = IPageAt
*/
submitButton(): Mithril.Children {
return (
<Button onclick={this.saveSettings.bind(this)} className="Button Button--primary" loading={this.loading} disabled={!this.isChanged()}>
<Button
type="submit"
onclick={this.saveSettings.bind(this)}
className="Button Button--primary"
loading={this.loading}
disabled={!this.isChanged()}
>
{app.translator.trans('core.admin.settings.submit_button')}
</Button>
);

View File

@@ -22,8 +22,14 @@ export default class AppearancePage extends AdminPage {
}
content() {
return (
<>
return this.contentItems().toArray();
}
contentItems(): ItemList<Mithril.Children> {
const items = new ItemList<Mithril.Children>();
items.add(
'colors',
<Form>
<FieldSet
className="AppearancePage-colors"
@@ -32,47 +38,75 @@ export default class AppearancePage extends AdminPage {
>
{this.colorItems().toArray()}
</FieldSet>
</Form>
</Form>,
100
);
<Form>
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>
</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>
</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>
</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>
</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>
</Form>
</>
</div>,
60
);
return items;
}
colorItems() {

View File

@@ -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(

View File

@@ -25,12 +25,12 @@ export default class PermissionsPage extends AdminPage {
.all<Group>('groups')
.filter((group) => [Group.GUEST_ID, Group.MEMBER_ID].indexOf(group.id()!) === -1)
.map((group) => (
<button className="Button Group" onclick={() => app.modal.show(EditGroupModal, { group })}>
<button className="Button Group" type="button" onclick={() => app.modal.show(EditGroupModal, { group })}>
<GroupBadge group={group} className="Group-icon" label={null} />
<span className="Group-name">{group.namePlural()}</span>
</button>
))}
<button className="Button Group Group--add" onclick={() => app.modal.show(EditGroupModal)}>
<button className="Button Group Group--add" type="button" onclick={() => app.modal.show(EditGroupModal)}>
<Icon name="fas fa-plus" className="Group-icon" />
<span className="Group-name">{app.translator.trans('core.admin.permissions.new_group_button')}</span>
</button>

View File

@@ -352,6 +352,7 @@ export default class UserListPage extends AdminPage {
<button
onclick={toggleEmailVisibility}
className="Button Button--text UserList-emailIconBtn"
type="button"
title={app.translator.trans('core.admin.users.grid.columns.email.visibility_show')}
>
<Icon name="far fa-eye-slash fa-fw" className="icon" />

View File

@@ -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

View File

@@ -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 {

View File

@@ -21,7 +21,7 @@ export default class DetailedDropdownItem<
> extends Component<CustomAttrs> {
view() {
return (
<button className="DetailedDropdownItem hasIcon" onclick={this.attrs.onclick}>
<button type="button" className="DetailedDropdownItem hasIcon" onclick={this.attrs.onclick}>
<Icon name={this.attrs.active ? 'fas fa-check' : 'fas'} className="Button-icon" />
<span className="DetailedDropdownItem-content">
<Icon name={this.attrs.icon} className="Button-icon" />

View File

@@ -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.
}
/**
@@ -131,6 +139,7 @@ export default class Dropdown<CustomAttrs extends IDropdownAttrs = IDropdownAttr
getButton(children: Mithril.ChildArray): Mithril.Vnode<any, any> {
let button = (
<button
type="button"
className={'Dropdown-toggle ' + this.attrs.buttonClassName}
aria-haspopup="menu"
aria-label={this.attrs.accessibleToggleLabel}

View File

@@ -45,6 +45,7 @@ export default class SplitDropdown<CustomAttrs extends ISplitDropdownAttrs = ISp
<>
{button}
<button
type="button"
className={'Dropdown-toggle Button Button--icon ' + this.attrs.buttonClassName}
aria-haspopup="menu"
aria-label={this.attrs.accessibleToggleLabel}

View File

@@ -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,7 +124,8 @@ 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!;
@@ -67,6 +135,18 @@ export default class Admin implements IExtender<AdminApplication> {
}
});
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();
@@ -75,6 +155,18 @@ export default class Admin implements IExtender<AdminApplication> {
}
});
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);
}
@@ -90,5 +182,6 @@ export default class Admin implements IExtender<AdminApplication> {
app.generalIndex.add(key, callback());
}
});
});
}
}

View File

@@ -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;

View 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';
}
});
});
}

View File

@@ -42,9 +42,11 @@ 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
type="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 +62,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>
);

View File

@@ -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}
/>
);

View File

@@ -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);

View File

@@ -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>
);
}
}

View File

@@ -40,6 +40,7 @@ export default class PostMeta<CustomAttrs extends IPostMetaAttrs = IPostMetaAttr
items.add(
'time',
<button
type="button"
className={classList({
'Button Button--text': true,
'Dropdown-toggle Button--link': !!permalink,

View File

@@ -58,7 +58,7 @@ export default class PostStreamScrubber extends Component {
return (
<div className={classNames.join(' ')}>
<button className="Button Dropdown-toggle" data-toggle="dropdown">
<button type="button" className="Button Dropdown-toggle" data-toggle="dropdown">
{viewing} <Icon name={'fas fa-sort'} />
</button>

View File

@@ -54,7 +54,7 @@ export default class ReplyPlaceholder<CustomAttrs extends IReplyPlaceholderAttrs
});
return (
<button className="Post ReplyPlaceholder" onclick={reply}>
<button type="button" className="Post ReplyPlaceholder" onclick={reply}>
<div className="Post-container">
<div className="Post-side">
<Avatar user={app.session.user} className="Post-avatar" />

View File

@@ -20,10 +20,6 @@
border-radius: 100%;
vertical-align: top;
}
span& {
cursor: default;
}
}
.Avatar--size(@size) {

View File

@@ -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;
}

View File

@@ -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})";

View File

@@ -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 {

View File

@@ -306,7 +306,7 @@
display: none;
}
&:hover {
&:hover, &:focus {
._checkmark {
display: block;
}
@@ -317,3 +317,9 @@
}
}
}
@media @small-phone {
.DiscussionListItem-info {
max-width: 160px;
}
}

View File

@@ -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

View File

@@ -63,7 +63,6 @@ class AdminServiceProvider extends AbstractServiceProvider
HttpMiddleware\ReferrerPolicyHeader::class,
HttpMiddleware\ContentTypeOptionsHeader::class,
Middleware\DisableBrowserCache::class,
Middleware\GatherDebugInformation::class,
];
});

View File

@@ -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();

View File

@@ -1,66 +0,0 @@
<?php
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\Admin\Middleware;
use Flarum\Settings\SettingsRepositoryInterface;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\MiddlewareInterface as Middleware;
use Psr\Http\Server\RequestHandlerInterface as Handler;
class GatherDebugInformation implements Middleware
{
public function __construct(protected SettingsRepositoryInterface $settings)
{
}
public function process(Request $request, Handler $handler): Response
{
$this->upsertWebUser();
$this->upsertOpCacheStatus();
return $handler->handle($request);
}
/**
* Read current web user, so we can compare that against CLI executions,
* these often cause file permission issues.
*/
public function upsertWebUser(): void
{
$user = $this->settings->get('core.debug.web_user');
$currentUser = get_current_user();
if ($user !== $currentUser) {
$this->settings->set(
'core.debug.web_user',
$currentUser
);
}
}
/**
* Read the opcache situation, this is only visible in web.
* Cli has opcache disabled by default.
*/
public function upsertOpCacheStatus(): void
{
$opcache = $this->settings->get('core.debug.opcache');
$opcacheStatus = function_exists('opcache_get_configuration') && opcache_get_configuration() !== false ? 'on' : 'off';
if ($opcache !== $opcacheStatus) {
$this->settings->set(
'core.debug.opcache',
$opcacheStatus
);
}
}
}

View File

@@ -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));
}

View File

@@ -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;
}

View File

@@ -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),
];
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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)));
}
}

View File

@@ -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)

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