mirror of
https://github.com/flarum/core.git
synced 2025-08-18 14:22:02 +02:00
Compare commits
65 Commits
ck/code-sp
...
as/export-
Author | SHA1 | Date | |
---|---|---|---|
|
3ef541a152 | ||
|
81894d7cc2 | ||
|
97aa569bfa | ||
|
7d80b88d5c | ||
|
2cd1c2964a | ||
|
8a451e0bfc | ||
|
0b9ad5425c | ||
|
da5db714c2 | ||
|
d4a2357a32 | ||
|
588a9f952f | ||
|
66233ce818 | ||
|
7d4bd8a845 | ||
|
3a6b8847f1 | ||
|
2ffec2ee71 | ||
|
7eea2476ca | ||
|
9711af42ae | ||
|
d12d52918b | ||
|
ad92d11cf9 | ||
|
3ca035f9aa | ||
|
bbff3a2748 | ||
|
f5cd5f202f | ||
|
a78cbf644c | ||
|
2de47a8656 | ||
|
b45519974a | ||
|
edaf45d133 | ||
|
6b9e991082 | ||
|
8a431dc3cc | ||
|
91b1d9029e | ||
|
e337c10bb8 | ||
|
e0258d2708 | ||
|
fcb5778705 | ||
|
40b47de9e1 | ||
|
deadd67691 | ||
|
c119731e65 | ||
|
2b7e7f3ff4 | ||
|
f4acb2c5db | ||
|
f9779284e4 | ||
|
43d6b3104d | ||
|
33bd99d376 | ||
|
eb4b18a979 | ||
|
b62debf031 | ||
|
1f2411e15e | ||
|
d99df936b1 | ||
|
9716a15c31 | ||
|
5e2340bf10 | ||
|
c84939b19c | ||
|
4974c91481 | ||
|
f67149bb06 | ||
|
a2d77d7b81 | ||
|
da4264c8a3 | ||
|
0f9526ba9f | ||
|
e77365f32f | ||
|
c7c456cb3e | ||
|
fb51fb4e6d | ||
|
5b7d364b87 | ||
|
39a6106854 | ||
|
9e3699ea47 | ||
|
b6f0b01307 | ||
|
548f1321f1 | ||
|
e376cf2079 | ||
|
286027ff27 | ||
|
e52b769ceb | ||
|
b1f166d82a | ||
|
63675c81d6 | ||
|
f76524a5de |
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -11,5 +11,6 @@ phpunit.xml export-ignore
|
|||||||
tests export-ignore
|
tests export-ignore
|
||||||
|
|
||||||
js/dist/* -diff
|
js/dist/* -diff
|
||||||
|
js/dist/* linguist-generated
|
||||||
|
|
||||||
* text=auto eol=lf
|
* text=auto eol=lf
|
||||||
|
6
.github/workflows/pr_size_change.yml
vendored
6
.github/workflows/pr_size_change.yml
vendored
@@ -25,8 +25,12 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: "14"
|
node-version: "14"
|
||||||
|
|
||||||
|
- name: Use npm v7
|
||||||
|
run: sudo npm install -g npm@7.x.x
|
||||||
|
|
||||||
- name: Install JS dependencies
|
- name: Install JS dependencies
|
||||||
run: npm ci
|
# We need to use `npm install` here. If we don't, the workflow will fail.
|
||||||
|
run: npm install
|
||||||
working-directory: ./js
|
working-directory: ./js
|
||||||
|
|
||||||
- name: Build production assets
|
- name: Build production assets
|
||||||
|
@@ -63,13 +63,14 @@
|
|||||||
"symfony/console": "^5.2.2",
|
"symfony/console": "^5.2.2",
|
||||||
"symfony/event-dispatcher": "^5.2.2",
|
"symfony/event-dispatcher": "^5.2.2",
|
||||||
"symfony/mime": "^5.2.0",
|
"symfony/mime": "^5.2.0",
|
||||||
|
"symfony/polyfill-intl-messageformatter": "^1.22.0",
|
||||||
"symfony/translation": "^5.1.5",
|
"symfony/translation": "^5.1.5",
|
||||||
"symfony/yaml": "^5.2.2",
|
"symfony/yaml": "^5.2.2",
|
||||||
"tobscure/json-api": "^0.3.0",
|
"tobscure/json-api": "^0.3.0",
|
||||||
"wikimedia/less.php": "^3.0"
|
"wikimedia/less.php": "^3.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"flarum/testing": "^0.1.0-beta.16"
|
"flarum/testing": "dev-main#81e25f034e2b6dceaea753ad7579b5c61d641993"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
|
@@ -1,40 +0,0 @@
|
|||||||
// Hi early reviewer! I'm a temporary file and
|
|
||||||
// will be moved to the Flarum webpack config soon!
|
|
||||||
|
|
||||||
const loaderUtils = require('loader-utils');
|
|
||||||
|
|
||||||
// Custom loader logic
|
|
||||||
module.exports = function (source) {
|
|
||||||
|
|
||||||
// Get the type of the module to be exported
|
|
||||||
const location = loaderUtils.interpolateName(this, '[folder]/', {
|
|
||||||
context: this.rootContext || this.context,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get the name of module to be exported
|
|
||||||
const moduleName = loaderUtils.interpolateName(this, '[name]', {
|
|
||||||
context: this.rootContext || this.context,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Don't export low level files
|
|
||||||
if (/.*\/(admin|forum)$/.test(location) || /(index|app|compat|FlarumRegistry)$/.test(moduleName)) {
|
|
||||||
return source;
|
|
||||||
}
|
|
||||||
|
|
||||||
let addition = "";
|
|
||||||
|
|
||||||
// Find the export names
|
|
||||||
const matches = [...source.matchAll(/export\s+?(?:default\s?|function|abstract\s?|class)+?\s([^(\s<;]*)/gm)];
|
|
||||||
matches.map(match => {
|
|
||||||
let name = match[1]
|
|
||||||
|
|
||||||
if (!name || name === 'interface') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add code at the end of the file to add the file to registry
|
|
||||||
addition += `\nwindow.flreg.add('${location}${name}', ${name})`
|
|
||||||
});
|
|
||||||
|
|
||||||
return source + addition;
|
|
||||||
}
|
|
@@ -8,4 +8,4 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export * from './src/common';
|
export * from './src/common';
|
||||||
export * from './src/admin';
|
export * from './src/admin';
|
26
js/dist/admin.js
generated
vendored
Normal file
26
js/dist/admin.js
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
1
js/dist/admin.js.map
generated
vendored
Normal file
1
js/dist/admin.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
2
js/dist/admin/flarum-core.js
vendored
2
js/dist/admin/flarum-core.js
vendored
File diff suppressed because one or more lines are too long
1
js/dist/admin/flarum-core.js.map
vendored
1
js/dist/admin/flarum-core.js.map
vendored
File diff suppressed because one or more lines are too long
2
js/dist/admin/runtime.js
vendored
2
js/dist/admin/runtime.js
vendored
@@ -1,2 +0,0 @@
|
|||||||
(()=>{var e,r={},o={};function t(e){var n=o[e];if(void 0!==n)return n.exports;var l=o[e]={exports:{}};return r[e].call(l.exports,l,l.exports,t),l.exports}t.m=r,e=[],t.O=(r,o,n,l)=>{if(!o){var a=1/0;for(f=0;f<e.length;f++){for(var[o,n,l]=e[f],i=!0,u=0;u<o.length;u++)(!1&l||a>=l)&&Object.keys(t.O).every((e=>t.O[e](o[u])))?o.splice(u--,1):(i=!1,l<a&&(a=l));i&&(e.splice(f--,1),r=n())}return r}l=l||0;for(var f=e.length;f>0&&e[f-1][2]>l;f--)e[f]=e[f-1];e[f]=[o,n,l]},t.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return t.d(r,{a:r}),r},t.d=(e,r)=>{for(var o in r)t.o(r,o)&&!t.o(e,o)&&Object.defineProperty(e,o,{enumerable:!0,get:r[o]})},t.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),t.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),t.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{var e={93:0};t.O.j=r=>0===e[r];var r=(r,o)=>{var n,l,[a,i,u]=o,f=0;for(n in i)t.o(i,n)&&(t.m[n]=i[n]);for(u&&u(t),r&&r(o);f<a.length;f++)l=a[f],t.o(e,l)&&e[l]&&e[l][0](),e[a[f]]=0;t.O()},o=self.webpackChunk_flarum_core=self.webpackChunk_flarum_core||[];o.forEach(r.bind(null,0)),o.push=r.bind(null,o.push.bind(o))})(),t.O()})();
|
|
||||||
//# sourceMappingURL=runtime.js.map
|
|
1
js/dist/admin/runtime.js.map
vendored
1
js/dist/admin/runtime.js.map
vendored
File diff suppressed because one or more lines are too long
2
js/dist/common/common.js
vendored
2
js/dist/common/common.js
vendored
File diff suppressed because one or more lines are too long
1
js/dist/common/common.js.map
vendored
1
js/dist/common/common.js.map
vendored
File diff suppressed because one or more lines are too long
2
js/dist/common/components/TextEditor.js
vendored
2
js/dist/common/components/TextEditor.js
vendored
File diff suppressed because one or more lines are too long
1
js/dist/common/components/TextEditor.js.map
vendored
1
js/dist/common/components/TextEditor.js.map
vendored
File diff suppressed because one or more lines are too long
53
js/dist/forum.js
generated
vendored
Normal file
53
js/dist/forum.js
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
1
js/dist/forum.js.map
generated
vendored
Normal file
1
js/dist/forum.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
2
js/dist/forum/components/ChangeEmailModal.js
vendored
2
js/dist/forum/components/ChangeEmailModal.js
vendored
@@ -1,2 +0,0 @@
|
|||||||
(self.webpackChunk_flarum_core=self.webpackChunk_flarum_core||[]).push([[372],{4146:(a,s,t)=>{t.r(s),t.d(s,{default:()=>n});var e=t(1788),o=t(5556),r=t(2587),i=t(1024),n=function(a){function s(){return a.apply(this,arguments)||this}(0,e.Z)(s,a);var t=s.prototype;return t.oninit=function(s){a.prototype.oninit.call(this,s),this.success=!1,this.email=(0,i.Z)(app.session.user.email()),this.password=(0,i.Z)("")},t.className=function(){return"ChangeEmailModal Modal--small"},t.title=function(){return app.translator.trans("core.forum.change_email.title")},t.content=function(){return this.success?m("div",{className:"Modal-body"},m("div",{className:"Form Form--centered"},m("p",{className:"helpText"},app.translator.trans("core.forum.change_email.confirmation_message",{email:m("strong",null,this.email())})),m("div",{className:"Form-group"},m(r.Z,{className:"Button Button--primary Button--block",onclick:this.hide.bind(this)},app.translator.trans("core.forum.change_email.dismiss_button"))))):m("div",{className:"Modal-body"},m("div",{className:"Form Form--centered"},m("div",{className:"Form-group"},m("input",{type:"email",name:"email",className:"FormControl",placeholder:app.session.user.email(),bidi:this.email,disabled:this.loading})),m("div",{className:"Form-group"},m("input",{type:"password",name:"password",className:"FormControl",placeholder:app.translator.trans("core.forum.change_email.confirm_password_placeholder"),bidi:this.password,disabled:this.loading})),m("div",{className:"Form-group"},r.Z.component({className:"Button Button--primary Button--block",type:"submit",loading:this.loading},app.translator.trans("core.forum.change_email.submit_button")))))},t.onsubmit=function(a){var s=this;a.preventDefault(),this.email()!==app.session.user.email()?(this.loading=!0,this.alertAttrs=null,app.session.user.save({email:this.email()},{errorHandler:this.onerror.bind(this),meta:{password:this.password()}}).then((function(){s.success=!0})).catch((function(){})).then(this.loaded.bind(this))):this.hide()},t.onerror=function(s){401===s.status&&(s.alert.content=app.translator.trans("core.forum.change_email.incorrect_password_message")),a.prototype.onerror.call(this,s)},s}(o.Z);window.flreg.add("components/ChangeEmailModal",n)}}]);
|
|
||||||
//# sourceMappingURL=ChangeEmailModal.js.map
|
|
File diff suppressed because one or more lines are too long
@@ -1,2 +0,0 @@
|
|||||||
(self.webpackChunk_flarum_core=self.webpackChunk_flarum_core||[]).push([[503],{22:(t,a,e)=>{e.r(a),e.d(a,{default:()=>s});var o=e(1788),r=e(5556),n=e(2587),s=function(t){function a(){return t.apply(this,arguments)||this}(0,o.Z)(a,t);var e=a.prototype;return e.className=function(){return"ChangePasswordModal Modal--small"},e.title=function(){return app.translator.trans("core.forum.change_password.title")},e.content=function(){return m("div",{className:"Modal-body"},m("div",{className:"Form Form--centered"},m("p",{className:"helpText"},app.translator.trans("core.forum.change_password.text")),m("div",{className:"Form-group"},n.Z.component({className:"Button Button--primary Button--block",type:"submit",loading:this.loading},app.translator.trans("core.forum.change_password.send_button")))))},e.onsubmit=function(t){t.preventDefault(),this.loading=!0,app.request({method:"POST",url:app.forum.attribute("apiUrl")+"/forgot",body:{email:app.session.user.email()}}).then(this.hide.bind(this),this.loaded.bind(this))},a}(r.Z);window.flreg.add("components/ChangePasswordModal",s)}}]);
|
|
||||||
//# sourceMappingURL=ChangePasswordModal.js.map
|
|
@@ -1 +0,0 @@
|
|||||||
{"version":3,"sources":["webpack://@flarum/core/./src/forum/components/ChangePasswordModal.js"],"names":["ChangePasswordModal","className","title","app","translator","trans","content","Button","type","loading","this","onsubmit","e","preventDefault","request","method","url","forum","attribute","body","email","session","user","then","hide","bind","loaded","Modal","window","flreg","add"],"mappings":"4JAOqBA,E,sGACnBC,UAAA,WACE,MAAO,oC,EAGTC,MAAA,WACE,OAAOC,IAAIC,WAAWC,MAAM,qC,EAG9BC,QAAA,WACE,OACE,SAAKL,UAAU,cACb,SAAKA,UAAU,uBACb,OAAGA,UAAU,YAAYE,IAAIC,WAAWC,MAAM,oCAC9C,SAAKJ,UAAU,cACZM,cACC,CACEN,UAAW,uCACXO,KAAM,SACNC,QAASC,KAAKD,SAEhBN,IAAIC,WAAWC,MAAM,+C,EAQjCM,SAAA,SAASC,GACPA,EAAEC,iBAEFH,KAAKD,SAAU,EAEfN,IACGW,QAAQ,CACPC,OAAQ,OACRC,IAAKb,IAAIc,MAAMC,UAAU,UAAY,UACrCC,KAAM,CAAEC,MAAOjB,IAAIkB,QAAQC,KAAKF,WAEjCG,KAAKb,KAAKc,KAAKC,KAAKf,MAAOA,KAAKgB,OAAOD,KAAKf,Q,GAxCFiB,KA4CjDC,OAAOC,MAAMC,IAAI,iCAAkC9B","file":"forum/components/ChangePasswordModal.js","sourcesContent":["import Modal from '../../common/components/Modal';\nimport Button from '../../common/components/Button';\n\n/**\n * The `ChangePasswordModal` component shows a modal dialog which allows the\n * user to send themself a password reset email.\n */\nexport default class ChangePasswordModal extends Modal {\n className() {\n return 'ChangePasswordModal Modal--small';\n }\n\n title() {\n return app.translator.trans('core.forum.change_password.title');\n }\n\n content() {\n return (\n <div className=\"Modal-body\">\n <div className=\"Form Form--centered\">\n <p className=\"helpText\">{app.translator.trans('core.forum.change_password.text')}</p>\n <div className=\"Form-group\">\n {Button.component(\n {\n className: 'Button Button--primary Button--block',\n type: 'submit',\n loading: this.loading,\n },\n app.translator.trans('core.forum.change_password.send_button')\n )}\n </div>\n </div>\n </div>\n );\n }\n\n onsubmit(e) {\n e.preventDefault();\n\n this.loading = true;\n\n app\n .request({\n method: 'POST',\n url: app.forum.attribute('apiUrl') + '/forgot',\n body: { email: app.session.user.email() },\n })\n .then(this.hide.bind(this), this.loaded.bind(this));\n }\n}\n\nwindow.flreg.add('components/ChangePasswordModal', ChangePasswordModal)"],"sourceRoot":""}
|
|
@@ -1,2 +0,0 @@
|
|||||||
(self.webpackChunk_flarum_core=self.webpackChunk_flarum_core||[]).push([[437],{3406:(t,s,o)=>{o.r(s),o.d(s,{default:()=>a});var e=o(1788),i=o(4810),r=o(5731),n=o(1024),a=function(t){function s(){return t.apply(this,arguments)||this}(0,e.Z)(s,t),s.initAttrs=function(s){t.initAttrs.call(this,s),s.placeholder=s.placeholder||(0,r.Z)(app.translator.trans("core.forum.composer_discussion.body_placeholder")),s.submitLabel=s.submitLabel||app.translator.trans("core.forum.composer_discussion.submit_button"),s.confirmExit=s.confirmExit||(0,r.Z)(app.translator.trans("core.forum.composer_discussion.discard_confirmation")),s.titlePlaceholder=s.titlePlaceholder||(0,r.Z)(app.translator.trans("core.forum.composer_discussion.title_placeholder")),s.className="ComposerBody--discussion"};var o=s.prototype;return o.oninit=function(s){t.prototype.oninit.call(this,s),this.composer.fields.title=this.composer.fields.title||(0,n.Z)(""),this.title=this.composer.fields.title},o.headerItems=function(){var s=t.prototype.headerItems.call(this);return s.add("title",m("h3",null,app.translator.trans("core.forum.composer_discussion.title")),100),s.add("discussionTitle",m("h3",null,m("input",{className:"FormControl",bidi:this.title,placeholder:this.attrs.titlePlaceholder,disabled:!!this.attrs.disabled,onkeydown:this.onkeydown.bind(this)}))),s},o.onkeydown=function(t){13===t.which&&(t.preventDefault(),this.composer.editor.moveCursorTo(0)),t.redraw=!1},o.hasChanges=function(){return this.title()||this.composer.fields.content()},o.data=function(){return{title:this.title(),content:this.composer.fields.content()}},o.onsubmit=function(){var t=this;this.loading=!0;var s=this.data();app.store.createRecord("discussions").save(s).then((function(s){t.composer.hide(),app.discussions.refresh({deferClear:!0}),m.route.set(app.route.discussion(s))}),this.loaded.bind(this))},s}(i.Z);window.flreg.add("components/DiscussionComposer",a)}}]);
|
|
||||||
//# sourceMappingURL=DiscussionComposer.js.map
|
|
File diff suppressed because one or more lines are too long
@@ -1,2 +0,0 @@
|
|||||||
(self.webpackChunk_flarum_core=self.webpackChunk_flarum_core||[]).push([[799],{5813:(t,e,s)=>{s.a(t,(async t=>{s.r(e),s.d(e,{default:()=>r});var n=s(1788),a=s(486),o=s(2060),r=function(t){function e(){return t.apply(this,arguments)||this}(0,n.Z)(e,t);var s=e.prototype;return s.oninit=function(e){t.prototype.oninit.call(this,e),this.loadUser(m.route.param("username"))},s.show=function(e){t.prototype.show.call(this,e),this.state=new o.Z({q:"author:"+e.username(),sort:"newest"}),this.state.refresh()},s.content=function(){return m("div",{className:"DiscussionsUserPage"},a.Z.component({state:this.state}))},e}((await s.e(251).then(s.bind(s,6068))).default);window.flreg.add("components/DiscussionsUserPage",r),t()}),1)}}]);
|
|
||||||
//# sourceMappingURL=DiscussionsUserPage.js.map
|
|
@@ -1 +0,0 @@
|
|||||||
{"version":3,"sources":["webpack://@flarum/core/./src/forum/components/DiscussionsUserPage.js"],"names":["DiscussionsUserPage","oninit","vnode","this","loadUser","m","route","param","show","user","state","DiscussionListState","q","username","sort","refresh","content","className","DiscussionList","window","flreg","add"],"mappings":"8KASqBA,E,sGACnBC,OAAA,SAAOC,GACL,YAAMD,OAAN,UAAaC,GAEbC,KAAKC,SAASC,EAAEC,MAAMC,MAAM,c,EAG9BC,KAAA,SAAKC,GACH,YAAMD,KAAN,UAAWC,GAEXN,KAAKO,MAAQ,IAAIC,IAAoB,CACnCC,EAAG,UAAYH,EAAKI,WACpBC,KAAM,WAGRX,KAAKO,MAAMK,W,EAGbC,QAAA,WACE,OAAO,SAAKC,UAAU,uBAAuBC,cAAyB,CAAER,MAAOP,KAAKO,U,UAzBhE,+BAAP,SA6BjBS,OAAOC,MAAMC,IAAI,iCAAkCrB,G","file":"forum/components/DiscussionsUserPage.js","sourcesContent":["import DiscussionList from './DiscussionList';\nimport DiscussionListState from '../states/DiscussionListState';\n\nconst UserPage = (await import(/* webpackChunkName: \"forum/components/UserPage\" */ './UserPage')).default;\n\n/**\n * The `DiscussionsUserPage` component shows a discussion list inside of a user\n * page.\n */\nexport default class DiscussionsUserPage extends UserPage {\n oninit(vnode) {\n super.oninit(vnode);\n\n this.loadUser(m.route.param('username'));\n }\n\n show(user) {\n super.show(user);\n\n this.state = new DiscussionListState({\n q: 'author:' + user.username(),\n sort: 'newest',\n });\n\n this.state.refresh();\n }\n\n content() {\n return <div className=\"DiscussionsUserPage\">{DiscussionList.component({ state: this.state })}</div>;\n }\n}\n\nwindow.flreg.add('components/DiscussionsUserPage', DiscussionsUserPage)"],"sourceRoot":""}
|
|
2
js/dist/forum/components/EditPostComposer.js
vendored
2
js/dist/forum/components/EditPostComposer.js
vendored
@@ -1,2 +0,0 @@
|
|||||||
(self.webpackChunk_flarum_core=self.webpackChunk_flarum_core||[]).push([[293],{763:(t,o,s)=>{s.r(o),s.d(o,{default:()=>u});var e=s(1788),r=s(4810),n=s(2587),i=s(8046),a=s(9100);function p(t){app.composer.isFullScreen()&&(app.composer.minimize(),t.stopPropagation())}var u=function(t){function o(){return t.apply(this,arguments)||this}(0,e.Z)(o,t),o.initAttrs=function(o){t.initAttrs.call(this,o),o.submitLabel=o.submitLabel||app.translator.trans("core.forum.composer_edit.submit_button"),o.confirmExit=o.confirmExit||app.translator.trans("core.forum.composer_edit.discard_confirmation"),o.originalContent=o.originalContent||o.post.content(),o.user=o.user||o.post.user(),o.post.editedContent=o.originalContent};var s=o.prototype;return s.headerItems=function(){var o=t.prototype.headerItems.call(this),s=this.attrs.post;return o.add("title",m("h3",null,(0,a.Z)("fas fa-pencil-alt")," ",m(i.Z,{href:app.route.discussion(s.discussion(),s.number()),onclick:p},app.translator.trans("core.forum.composer_edit.post_link",{number:s.number(),discussion:s.discussion().title()})))),o},s.jumpToPreview=function(t){p(t),m.route.set(app.route.post(this.attrs.post))},s.data=function(){return{content:this.composer.fields.content()}},s.onsubmit=function(){var t=this,o=this.attrs.post.discussion();this.loading=!0;var s=this.data();this.attrs.post.save(s).then((function(s){if(app.viewingDiscussion(o))app.current.get("stream").goToNumber(s.number());else{var e,r=n.Z.component({className:"Button Button--link",onclick:function(){m.route.set(app.route.post(s)),app.alerts.dismiss(e)}},app.translator.trans("core.forum.composer_edit.view_button"));e=app.alerts.show({type:"success",controls:[r]},app.translator.trans("core.forum.composer_edit.edited_message"))}t.composer.hide()}),this.loaded.bind(this))},o}(r.Z);window.flreg.add("components/EditPostComposer",u)}}]);
|
|
||||||
//# sourceMappingURL=EditPostComposer.js.map
|
|
File diff suppressed because one or more lines are too long
2
js/dist/forum/components/EditUserModal.js
vendored
2
js/dist/forum/components/EditUserModal.js
vendored
@@ -1,2 +0,0 @@
|
|||||||
(self.webpackChunk_flarum_core=self.webpackChunk_flarum_core||[]).push([[352],{3089:(s,r,t)=>{t.r(r),t.d(r,{default:()=>p});var e=t(1788),a=t(5556),i=t(2587),n=t(7880),o=t(4032),d=t(5731),u=t(4594),l=t(1024),p=function(s){function r(){return s.apply(this,arguments)||this}(0,e.Z)(r,s);var t=r.prototype;return t.oninit=function(r){var t=this;s.prototype.oninit.call(this,r);var e=this.attrs.user;this.username=(0,l.Z)(e.username()||""),this.email=(0,l.Z)(e.email()||""),this.isEmailConfirmed=(0,l.Z)(e.isEmailConfirmed()||!1),this.setPassword=(0,l.Z)(!1),this.password=(0,l.Z)(e.password()||""),this.groups={},app.store.all("groups").filter((function(s){return-1===[o.Z.GUEST_ID,o.Z.MEMBER_ID].indexOf(s.id())})).forEach((function(s){return t.groups[s.id()]=(0,l.Z)(-1!==e.groups().indexOf(s))}))},t.className=function(){return"EditUserModal Modal--small"},t.title=function(){return app.translator.trans("core.forum.edit_user.title")},t.content=function(){var s=this.fields().toArray();return m("div",{className:"Modal-body"},s.length>1?m("div",{className:"Form"},this.fields().toArray()):app.translator.trans("core.forum.edit_user.nothing_available"))},t.fields=function(){var s=this,r=new u.Z;return app.session.user.canEditCredentials()&&(r.add("username",m("div",{className:"Form-group"},m("label",null,app.translator.trans("core.forum.edit_user.username_heading")),m("input",{className:"FormControl",placeholder:(0,d.Z)(app.translator.trans("core.forum.edit_user.username_label")),bidi:this.username,disabled:this.nonAdminEditingAdmin()})),40),app.session.user!==this.attrs.user&&(r.add("email",m("div",{className:"Form-group"},m("label",null,app.translator.trans("core.forum.edit_user.email_heading")),m("div",null,m("input",{className:"FormControl",placeholder:(0,d.Z)(app.translator.trans("core.forum.edit_user.email_label")),bidi:this.email,disabled:this.nonAdminEditingAdmin()})),!this.isEmailConfirmed()&&this.userIsAdmin(app.session.user)?m("div",null,i.Z.component({className:"Button Button--block",loading:this.loading,onclick:this.activate.bind(this)},app.translator.trans("core.forum.edit_user.activate_button"))):""),30),r.add("password",m("div",{className:"Form-group"},m("label",null,app.translator.trans("core.forum.edit_user.password_heading")),m("div",null,m("label",{className:"checkbox"},m("input",{type:"checkbox",onchange:function(r){s.setPassword(r.target.checked),m.redraw.sync(),r.target.checked&&s.$("[name=password]").select(),r.redraw=!1},disabled:this.nonAdminEditingAdmin()}),app.translator.trans("core.forum.edit_user.set_password_label")),this.setPassword()?m("input",{className:"FormControl",type:"password",name:"password",placeholder:(0,d.Z)(app.translator.trans("core.forum.edit_user.password_label")),bidi:this.password,disabled:this.nonAdminEditingAdmin()}):"")),20))),app.session.user.canEditGroups()&&r.add("groups",m("div",{className:"Form-group EditUserModal-groups"},m("label",null,app.translator.trans("core.forum.edit_user.groups_heading")),m("div",null,Object.keys(this.groups).map((function(s){return app.store.getById("groups",s)})).map((function(r){return m("label",{className:"checkbox"},m("input",{type:"checkbox",bidi:s.groups[r.id()],disabled:r.id()===o.Z.ADMINISTRATOR_ID&&(s.attrs.user===app.session.user||!s.userIsAdmin(app.session.user))}),n.Z.component({group:r,label:""})," ",r.nameSingular())})))),10),r.add("submit",m("div",{className:"Form-group"},i.Z.component({className:"Button Button--primary",type:"submit",loading:this.loading},app.translator.trans("core.forum.edit_user.submit_button"))),-10),r},t.activate=function(){var s=this;this.loading=!0;var r={username:this.username(),isEmailConfirmed:!0};this.attrs.user.save(r,{errorHandler:this.onerror.bind(this)}).then((function(){s.isEmailConfirmed(!0),s.loading=!1,m.redraw()})).catch((function(){s.loading=!1,m.redraw()}))},t.data=function(){var s=this,r={relationships:{}};return this.attrs.user.canEditCredentials()&&!this.nonAdminEditingAdmin()&&(r.username=this.username(),app.session.user!==this.attrs.user&&(r.email=this.email()),this.setPassword()&&(r.password=this.password())),this.attrs.user.canEditGroups()&&(r.relationships.groups=Object.keys(this.groups).filter((function(r){return s.groups[r]()})).map((function(s){return app.store.getById("groups",s)}))),r},t.onsubmit=function(s){var r=this;s.preventDefault(),this.loading=!0,this.attrs.user.save(this.data(),{errorHandler:this.onerror.bind(this)}).then(this.hide.bind(this)).catch((function(){r.loading=!1,m.redraw()}))},t.nonAdminEditingAdmin=function(){return this.userIsAdmin(this.attrs.user)&&!this.userIsAdmin(app.session.user)},t.userIsAdmin=function(s){return s.groups().some((function(s){return s.id()===o.Z.ADMINISTRATOR_ID}))},r}(a.Z);window.flreg.add("components/EditUserModal",p)}}]);
|
|
||||||
//# sourceMappingURL=EditUserModal.js.map
|
|
File diff suppressed because one or more lines are too long
@@ -1,2 +0,0 @@
|
|||||||
(self.webpackChunk_flarum_core=self.webpackChunk_flarum_core||[]).push([[502],{5833:(t,o,r)=>{r.r(o),r.d(o,{default:()=>l});var a=r(1788),s=r(5556),e=r(2587),n=r(5731),i=r(1024),l=function(t){function o(){return t.apply(this,arguments)||this}(0,a.Z)(o,t);var r=o.prototype;return r.oninit=function(o){t.prototype.oninit.call(this,o),this.email=(0,i.Z)(this.attrs.email||""),this.success=!1},r.className=function(){return"ForgotPasswordModal Modal--small"},r.title=function(){return app.translator.trans("core.forum.forgot_password.title")},r.content=function(){return this.success?m("div",{className:"Modal-body"},m("div",{className:"Form Form--centered"},m("p",{className:"helpText"},app.translator.trans("core.forum.forgot_password.email_sent_message")),m("div",{className:"Form-group"},m(e.Z,{className:"Button Button--primary Button--block",onclick:this.hide.bind(this)},app.translator.trans("core.forum.forgot_password.dismiss_button"))))):m("div",{className:"Modal-body"},m("div",{className:"Form Form--centered"},m("p",{className:"helpText"},app.translator.trans("core.forum.forgot_password.text")),m("div",{className:"Form-group"},m("input",{className:"FormControl",name:"email",type:"email",placeholder:(0,n.Z)(app.translator.trans("core.forum.forgot_password.email_placeholder")),bidi:this.email,disabled:this.loading})),m("div",{className:"Form-group"},e.Z.component({className:"Button Button--primary Button--block",type:"submit",loading:this.loading},app.translator.trans("core.forum.forgot_password.submit_button")))))},r.onsubmit=function(t){var o=this;t.preventDefault(),this.loading=!0,app.request({method:"POST",url:app.forum.attribute("apiUrl")+"/forgot",body:{email:this.email()},errorHandler:this.onerror.bind(this)}).then((function(){o.success=!0,o.alert=null})).catch((function(){})).then(this.loaded.bind(this))},r.onerror=function(o){404===o.status&&(o.alert.content=app.translator.trans("core.forum.forgot_password.not_found_message")),t.prototype.onerror.call(this,o)},o}(s.Z);window.flreg.add("components/ForgotPasswordModal",l)}}]);
|
|
||||||
//# sourceMappingURL=ForgotPasswordModal.js.map
|
|
File diff suppressed because one or more lines are too long
2
js/dist/forum/components/IndexPage.js
vendored
2
js/dist/forum/components/IndexPage.js
vendored
File diff suppressed because one or more lines are too long
1
js/dist/forum/components/IndexPage.js.map
vendored
1
js/dist/forum/components/IndexPage.js.map
vendored
File diff suppressed because one or more lines are too long
2
js/dist/forum/components/LogInModal.js
vendored
2
js/dist/forum/components/LogInModal.js
vendored
@@ -1,2 +0,0 @@
|
|||||||
(self.webpackChunk_flarum_core=self.webpackChunk_flarum_core||[]).push([[460],{7527:(t,o,i)=>{i.d(o,{Z:()=>e});var n=i(1788),r=i(8931),a=i(4594),e=function(t){function o(){return t.apply(this,arguments)||this}(0,n.Z)(o,t);var i=o.prototype;return i.view=function(){return m("div",{className:"LogInButtons"},this.items().toArray())},i.items=function(){return new a.Z},o}(r.Z);window.flreg.add("components/LogInButtons",e)},1498:(t,o,i)=>{i.r(o),i.d(o,{default:()=>c});var n=i(1788),r=i(5556),a=i(2587),e=i(7527),s=i(5731),l=i(4594),d=i(1024),c=function(t){function o(){return t.apply(this,arguments)||this}(0,n.Z)(o,t);var r=o.prototype;return r.oninit=function(o){t.prototype.oninit.call(this,o),this.identification=(0,d.Z)(this.attrs.identification||""),this.password=(0,d.Z)(this.attrs.password||""),this.remember=(0,d.Z)(!!this.attrs.remember)},r.className=function(){return"LogInModal Modal--small"},r.title=function(){return app.translator.trans("core.forum.log_in.title")},r.content=function(){return[m("div",{className:"Modal-body"},this.body()),m("div",{className:"Modal-footer"},this.footer())]},r.body=function(){return[m(e.Z,null),m("div",{className:"Form Form--centered"},this.fields().toArray())]},r.fields=function(){var t=new l.Z;return t.add("identification",m("div",{className:"Form-group"},m("input",{className:"FormControl",name:"identification",type:"text",placeholder:(0,s.Z)(app.translator.trans("core.forum.log_in.username_or_email_placeholder")),bidi:this.identification,disabled:this.loading})),30),t.add("password",m("div",{className:"Form-group"},m("input",{className:"FormControl",name:"password",type:"password",placeholder:(0,s.Z)(app.translator.trans("core.forum.log_in.password_placeholder")),bidi:this.password,disabled:this.loading})),20),t.add("remember",m("div",{className:"Form-group"},m("div",null,m("label",{className:"checkbox"},m("input",{type:"checkbox",bidi:this.remember,disabled:this.loading}),app.translator.trans("core.forum.log_in.remember_me_label")))),10),t.add("submit",m("div",{className:"Form-group"},a.Z.component({className:"Button Button--primary Button--block",type:"submit",loading:this.loading},app.translator.trans("core.forum.log_in.submit_button"))),-10),t},r.footer=function(){return[m("p",{className:"LogInModal-forgotPassword"},m("a",{onclick:this.forgotPassword.bind(this)},app.translator.trans("core.forum.log_in.forgot_password_link"))),app.forum.attribute("allowSignUp")?m("p",{className:"LogInModal-signUp"},app.translator.trans("core.forum.log_in.sign_up_text",{a:m("a",{onclick:this.signUp.bind(this)})})):""]},r.forgotPassword=function(){var t=this.identification(),o=-1!==t.indexOf("@")?{email:t}:void 0;app.modal.show((function(){return i.e(502).then(i.bind(i,5833))}),o)},r.signUp=function(){var t={password:this.password()},o=this.identification();t[-1!==o.indexOf("@")?"email":"username"]=o,app.modal.show((function(){return i.e(395).then(i.bind(i,3235))}),t)},r.onready=function(){this.$("[name="+(this.identification()?"password":"identification")+"]").select()},r.onsubmit=function(t){t.preventDefault(),this.loading=!0;var o=this.identification(),i=this.password(),n=this.remember();app.session.login({identification:o,password:i,remember:n},{errorHandler:this.onerror.bind(this)}).then((function(){return window.location.reload()}),this.loaded.bind(this))},r.onerror=function(o){401===o.status&&(o.alert.content=app.translator.trans("core.forum.log_in.invalid_login_message")),t.prototype.onerror.call(this,o)},o}(r.Z);window.flreg.add("components/LogInModal",c)}}]);
|
|
||||||
//# sourceMappingURL=LogInModal.js.map
|
|
1
js/dist/forum/components/LogInModal.js.map
vendored
1
js/dist/forum/components/LogInModal.js.map
vendored
File diff suppressed because one or more lines are too long
2
js/dist/forum/components/NotificationList.js
vendored
2
js/dist/forum/components/NotificationList.js
vendored
@@ -1,2 +0,0 @@
|
|||||||
(self.webpackChunk_flarum_core=self.webpackChunk_flarum_core||[]).push([[77],{3490:(t,i,n)=>{n.r(i),n.d(i,{default:()=>N});var o=n(1788),a=n(8931),s=n(7473),e=n(2587),c=n(8046),r=n(8726),l=n(8197),u=n(9908),f=n(9100),p=n(6961),d=function(t){function i(){return t.apply(this,arguments)||this}(0,o.Z)(i,t);var n=i.prototype;return n.view=function(){var t=this,i=this.attrs.notification,n=this.href();return m(c.Z,{className:"Notification Notification--"+i.contentType()+" "+(i.isRead()?"":"unread"),href:n,external:n.includes("://"),onclick:this.markAsRead.bind(this)},!i.isRead()&&e.Z.component({className:"Notification-action Button Button--icon Button--link",icon:"fas fa-check",title:app.translator.trans("core.forum.notifications.mark_as_read_tooltip"),onclick:function(i){i.preventDefault(),i.stopPropagation(),t.markAsRead()}}),(0,u.Z)(i.fromUser()),(0,f.Z)(this.icon(),{className:"Notification-icon"}),m("span",{className:"Notification-content"},this.content()),(0,p.Z)(i.createdAt()),m("div",{className:"Notification-excerpt"},this.excerpt()))},n.icon=function(){},n.href=function(){},n.content=function(){},n.excerpt=function(){},n.markAsRead=function(){this.attrs.notification.isRead()||(app.session.user.pushAttributes({unreadNotificationCount:app.session.user.unreadNotificationCount()-1}),this.attrs.notification.save({isRead:!0}))},i}(a.Z);window.flreg.add("components/Notification",d);var h=function(t){function i(){return t.apply(this,arguments)||this}(0,o.Z)(i,t);var n=i.prototype;return n.icon=function(){return"fas fa-pencil-alt"},n.href=function(){var t=this.attrs.notification;return app.route.discussion(t.subject(),t.content().postNumber)},n.content=function(){return app.translator.trans("core.forum.notifications.discussion_renamed_text",{user:this.attrs.notification.fromUser()})},i}(d);window.flreg.add("components/DiscussionRenamedNotification",h);var N=function(t){function i(){return t.apply(this,arguments)||this}(0,o.Z)(i,t);var n=i.prototype;return n.view=function(){var t=this,i=this.attrs.state,n=i.getNotificationPages();return m("div",{className:"NotificationList"},m("div",{className:"NotificationList-header"},m("div",{className:"App-primaryControl"},e.Z.component({className:"Button Button--icon Button--link",icon:"fas fa-check",title:app.translator.trans("core.forum.notifications.mark_all_as_read_tooltip"),onclick:i.markAllAsRead.bind(i)})),m("h4",{className:"App-titleControl App-titleControl--text"},app.translator.trans("core.forum.notifications.title"))),m("div",{className:"NotificationList-content"},n.length?n.map((function(i){var n=[],o={};return i.forEach((function(t){var i=t.subject();if(void 0!==i){var a=!1;i instanceof l.Z?a=i:i&&i.discussion&&(a=i.discussion());var s=a?a.id():0;o[s]=o[s]||{discussion:a,notifications:[]},o[s].notifications.push(t),-1===n.indexOf(o[s])&&n.push(o[s])}})),n.map((function(i){var n=i.discussion&&i.discussion.badges().toArray();return m("div",{className:"NotificationGroup"},i.discussion?m(c.Z,{className:"NotificationGroup-header",href:app.route.discussion(i.discussion)},n&&n.length?m("ul",{className:"NotificationGroup-badges badges"},(0,s.Z)(n)):"",i.discussion.title()):m("div",{className:"NotificationGroup-header"},app.forum.attribute("title")),m("ul",{className:"NotificationGroup-content"},i.notifications.map((function(i){var n=t.notificationComponents()[i.contentType()];return n?m("li",null,n.component({notification:i})):""}))))}))})):"",i.isLoading()?m(r.Z,{className:"LoadingIndicator--block"}):n.length?"":m("div",{className:"NotificationList-empty"},app.translator.trans("core.forum.notifications.empty_text"))))},n.notificationComponents=function(){return{discussionRenamed:h}},n.oncreate=function(i){t.prototype.oncreate.call(this,i),this.$notifications=this.$(".NotificationList-content"),this.$scrollParent=this.inPanel()?this.$notifications:$(window),this.boundScrollHandler=this.scrollHandler.bind(this),this.$scrollParent.on("scroll",this.boundScrollHandler)},n.onremove=function(){this.$scrollParent.off("scroll",this.boundScrollHandler)},n.scrollHandler=function(){var t=this.attrs.state,i=this.inPanel()?this.$scrollParent[0]:document.documentElement,n=Math.abs(i.scrollHeight-i.scrollTop-i.clientHeight)<=1;t.hasMoreResults()&&!t.isLoading()&&n&&t.loadMore()},n.inPanel=function(){return"auto"===this.$notifications.css("overflow")},i}(a.Z);window.flreg.add("components/NotificationList",N)}}]);
|
|
||||||
//# sourceMappingURL=NotificationList.js.map
|
|
File diff suppressed because one or more lines are too long
@@ -1,2 +0,0 @@
|
|||||||
(self.webpackChunk_flarum_core=self.webpackChunk_flarum_core||[]).push([[765],{9440:(t,n,o)=>{o.r(n),o.d(n,{default:()=>s});var a=o(1788),e=o(9657),i=o(9100),s=function(t){function n(){return t.apply(this,arguments)||this}(0,a.Z)(n,t),n.initAttrs=function(n){n.className=n.className||"NotificationsDropdown",n.buttonClassName=n.buttonClassName||"Button Button--flat",n.menuClassName=n.menuClassName||"Dropdown-menu--right",n.label=n.label||app.translator.trans("core.forum.notifications.tooltip"),n.icon=n.icon||"fas fa-bell",n.accessibleToggleLabel=n.accessibleToggleLabel||app.translator.trans("core.forum.notifications.toggle_dropdown_accessible_label"),t.initAttrs.call(this,n)};var e=n.prototype;return e.getButton=function(){var n=this.getNewCount(),o=t.prototype.getButton.call(this);return o.attrs.title=this.attrs.label,o.attrs.className+=n?" new":"",o.attrs.onclick=this.onclick.bind(this),o},e.getButtonContent=function(){var t=this.getUnreadCount();return[(0,i.Z)(this.attrs.icon,{className:"Button-icon"}),t?m("span",{className:"NotificationsDropdown-unread"},t):"",m("span",{className:"Button-label"},this.attrs.label)]},e.getMenu=function(){var t=this.NotificationList;return m("div",{className:"Dropdown-menu "+this.attrs.menuClassName,onclick:this.menuClick.bind(this)},this.showing&&t?m(t,{state:this.attrs.state}):"")},e.onclick=function(){var t=this;o.e(77).then(o.bind(o,3490)).then((function(n){t.NotificationList=n.default,m.redraw()})),app.drawer.isOpen()?this.goToRoute():this.attrs.state.load()},e.goToRoute=function(){m.route.set(app.route("notifications"))},e.getUnreadCount=function(){return app.session.user.unreadNotificationCount()},e.getNewCount=function(){return app.session.user.newNotificationCount()},e.menuClick=function(t){(t.shiftKey||t.metaKey||t.ctrlKey||2===t.which)&&t.stopPropagation()},n}(e.Z);window.flreg.add("components/NotificationsDropdown",s)}}]);
|
|
||||||
//# sourceMappingURL=NotificationsDropdown.js.map
|
|
@@ -1 +0,0 @@
|
|||||||
{"version":3,"sources":["webpack://@flarum/core/./src/forum/components/NotificationsDropdown.js"],"names":["NotificationsDropdown","initAttrs","attrs","className","buttonClassName","menuClassName","label","app","translator","trans","icon","accessibleToggleLabel","getButton","newNotifications","this","getNewCount","vdom","title","onclick","bind","getButtonContent","unread","getUnreadCount","getMenu","NotificationList","menuClick","showing","state","then","m","redraw","drawer","isOpen","goToRoute","load","route","set","session","user","unreadNotificationCount","newNotificationCount","e","shiftKey","metaKey","ctrlKey","which","stopPropagation","Dropdown","window","flreg","add"],"mappings":"8JAGqBA,E,6EACZC,UAAP,SAAiBC,GACfA,EAAMC,UAAYD,EAAMC,WAAa,wBACrCD,EAAME,gBAAkBF,EAAME,iBAAmB,sBACjDF,EAAMG,cAAgBH,EAAMG,eAAiB,uBAC7CH,EAAMI,MAAQJ,EAAMI,OAASC,IAAIC,WAAWC,MAAM,oCAClDP,EAAMQ,KAAOR,EAAMQ,MAAQ,cAE3BR,EAAMS,sBAAwBT,EAAMS,uBAAyBJ,IAAIC,WAAWC,MAAM,6DAElF,EAAMR,UAAN,UAAgBC,I,2BAGlBU,UAAA,WACE,IAAMC,EAAmBC,KAAKC,cACxBC,EAAO,EAAH,UAASJ,UAAT,WAOV,OALAI,EAAKd,MAAMe,MAAQH,KAAKZ,MAAMI,MAE9BU,EAAKd,MAAMC,WAAaU,EAAmB,OAAS,GACpDG,EAAKd,MAAMgB,QAAUJ,KAAKI,QAAQC,KAAKL,MAEhCE,G,EAGTI,iBAAA,WACE,IAAMC,EAASP,KAAKQ,iBAEpB,MAAO,EACLZ,OAAKI,KAAKZ,MAAMQ,KAAM,CAAEP,UAAW,gBACnCkB,EAAS,UAAMlB,UAAU,gCAAgCkB,GAAiB,GAC1E,UAAMlB,UAAU,gBAAgBW,KAAKZ,MAAMI,S,EAI/CiB,QAAA,WACE,IAAMC,EAAmBV,KAAKU,iBAE9B,OACE,SAAKrB,UAAW,iBAAmBW,KAAKZ,MAAMG,cAAea,QAASJ,KAAKW,UAAUN,KAAKL,OACvFA,KAAKY,SAAWF,EAAmB,EAACA,EAAD,CAAkBG,MAAOb,KAAKZ,MAAMyB,QAAY,K,EAK1FT,QAAA,WAAU,WACR,6BAAyFU,MAAK,SAACJ,GAC7F,EAAKA,iBAAmBA,EAAgB,QACxCK,EAAEC,YAGAvB,IAAIwB,OAAOC,SACblB,KAAKmB,YAELnB,KAAKZ,MAAMyB,MAAMO,Q,EAIrBD,UAAA,WACEJ,EAAEM,MAAMC,IAAI7B,IAAI4B,MAAM,mB,EAGxBb,eAAA,WACE,OAAOf,IAAI8B,QAAQC,KAAKC,2B,EAG1BxB,YAAA,WACE,OAAOR,IAAI8B,QAAQC,KAAKE,wB,EAG1Bf,UAAA,SAAUgB,IAGJA,EAAEC,UAAYD,EAAEE,SAAWF,EAAEG,SAAuB,IAAZH,EAAEI,QAAaJ,EAAEK,mB,GAzEdC,KA6EnDC,OAAOC,MAAMC,IAAI,mCAAoClD","file":"forum/components/NotificationsDropdown.js","sourcesContent":["import Dropdown from '../../common/components/Dropdown';\nimport icon from '../../common/helpers/icon';\n\nexport default class NotificationsDropdown extends Dropdown {\n static initAttrs(attrs) {\n attrs.className = attrs.className || 'NotificationsDropdown';\n attrs.buttonClassName = attrs.buttonClassName || 'Button Button--flat';\n attrs.menuClassName = attrs.menuClassName || 'Dropdown-menu--right';\n attrs.label = attrs.label || app.translator.trans('core.forum.notifications.tooltip');\n attrs.icon = attrs.icon || 'fas fa-bell';\n // For best a11y support, both `title` and `aria-label` should be used\n attrs.accessibleToggleLabel = attrs.accessibleToggleLabel || app.translator.trans('core.forum.notifications.toggle_dropdown_accessible_label');\n\n super.initAttrs(attrs);\n }\n\n getButton() {\n const newNotifications = this.getNewCount();\n const vdom = super.getButton();\n\n vdom.attrs.title = this.attrs.label;\n\n vdom.attrs.className += newNotifications ? ' new' : '';\n vdom.attrs.onclick = this.onclick.bind(this);\n\n return vdom;\n }\n\n getButtonContent() {\n const unread = this.getUnreadCount();\n\n return [\n icon(this.attrs.icon, { className: 'Button-icon' }),\n unread ? <span className=\"NotificationsDropdown-unread\">{unread}</span> : '',\n <span className=\"Button-label\">{this.attrs.label}</span>,\n ];\n }\n\n getMenu() {\n const NotificationList = this.NotificationList;\n\n return (\n <div className={'Dropdown-menu ' + this.attrs.menuClassName} onclick={this.menuClick.bind(this)}>\n {this.showing && NotificationList ? <NotificationList state={this.attrs.state} /> : ''}\n </div>\n );\n }\n\n onclick() {\n import(/* webpackChunkName: \"forum/components/NotificationList\" */ './NotificationList').then((NotificationList) => {\n this.NotificationList = NotificationList.default;\n m.redraw();\n });\n\n if (app.drawer.isOpen()) {\n this.goToRoute();\n } else {\n this.attrs.state.load();\n }\n }\n\n goToRoute() {\n m.route.set(app.route('notifications'));\n }\n\n getUnreadCount() {\n return app.session.user.unreadNotificationCount();\n }\n\n getNewCount() {\n return app.session.user.newNotificationCount();\n }\n\n menuClick(e) {\n // Don't close the notifications dropdown if the user is opening a link in a\n // new tab or window.\n if (e.shiftKey || e.metaKey || e.ctrlKey || e.which === 2) e.stopPropagation();\n }\n}\n\nwindow.flreg.add('components/NotificationsDropdown', NotificationsDropdown)"],"sourceRoot":""}
|
|
2
js/dist/forum/components/Post.js
vendored
2
js/dist/forum/components/Post.js
vendored
File diff suppressed because one or more lines are too long
1
js/dist/forum/components/Post.js.map
vendored
1
js/dist/forum/components/Post.js.map
vendored
File diff suppressed because one or more lines are too long
2
js/dist/forum/components/PostStream.js
vendored
2
js/dist/forum/components/PostStream.js
vendored
File diff suppressed because one or more lines are too long
1
js/dist/forum/components/PostStream.js.map
vendored
1
js/dist/forum/components/PostStream.js.map
vendored
File diff suppressed because one or more lines are too long
2
js/dist/forum/components/PostUser.js
vendored
2
js/dist/forum/components/PostUser.js
vendored
File diff suppressed because one or more lines are too long
1
js/dist/forum/components/PostUser.js.map
vendored
1
js/dist/forum/components/PostUser.js.map
vendored
File diff suppressed because one or more lines are too long
2
js/dist/forum/components/PostsUserPage.js
vendored
2
js/dist/forum/components/PostsUserPage.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,2 +0,0 @@
|
|||||||
(self.webpackChunk_flarum_core=self.webpackChunk_flarum_core||[]).push([[564],{7214:(t,i,n)=>{n.r(i),n.d(i,{default:()=>a});var s=n(1788),e=n(5556),r=n(2587),o=n(1024),a=function(t){function i(){return t.apply(this,arguments)||this}(0,s.Z)(i,t);var n=i.prototype;return n.oninit=function(i){t.prototype.oninit.call(this,i),this.discussion=this.attrs.discussion,this.currentTitle=this.attrs.currentTitle,this.newTitle=(0,o.Z)(this.currentTitle)},n.className=function(){return"RenameDiscussionModal Modal--small"},n.title=function(){return app.translator.trans("core.forum.rename_discussion.title")},n.content=function(){return m("div",{className:"Modal-body"},m("div",{className:"Form Form--centered"},m("div",{className:"Form-group"},m("input",{className:"FormControl",bidi:this.newTitle,type:"text"})),m("div",{className:"Form-group"},r.Z.component({className:"Button Button--primary Button--block",type:"submit",loading:this.loading},app.translator.trans("core.forum.rename_discussion.submit_button")))))},n.onsubmit=function(t){var i=this;t.preventDefault(),this.loading=!0;var n=this.newTitle(),s=this.currentTitle;if(n&&n!==s)return this.discussion.save({title:n}).then((function(){app.viewingDiscussion(i.discussion)&&app.current.get("stream").update(),m.redraw(),i.hide()})).catch((function(){i.loading=!1,m.redraw()}));this.hide()},i}(e.Z);window.flreg.add("components/RenameDiscussionModal",a)}}]);
|
|
||||||
//# sourceMappingURL=RenameDiscussionModal.js.map
|
|
@@ -1 +0,0 @@
|
|||||||
{"version":3,"sources":["webpack://@flarum/core/./src/forum/components/RenameDiscussionModal.js"],"names":["RenameDiscussionModal","oninit","vnode","this","discussion","attrs","currentTitle","newTitle","Stream","className","title","app","translator","trans","content","bidi","type","Button","loading","onsubmit","e","preventDefault","save","then","viewingDiscussion","current","get","update","m","redraw","hide","Modal","window","flreg","add"],"mappings":"wKAOqBA,E,sGACnBC,OAAA,SAAOC,GACL,YAAMD,OAAN,UAAaC,GAEbC,KAAKC,WAAaD,KAAKE,MAAMD,WAC7BD,KAAKG,aAAeH,KAAKE,MAAMC,aAC/BH,KAAKI,UAAWC,OAAOL,KAAKG,e,EAG9BG,UAAA,WACE,MAAO,sC,EAGTC,MAAA,WACE,OAAOC,IAAIC,WAAWC,MAAM,uC,EAG9BC,QAAA,WACE,OACE,SAAKL,UAAU,cACb,SAAKA,UAAU,uBACb,SAAKA,UAAU,cACb,WAAOA,UAAU,cAAcM,KAAMZ,KAAKI,SAAUS,KAAK,UAE3D,SAAKP,UAAU,cACZQ,cACC,CACER,UAAW,uCACXO,KAAM,SACNE,QAASf,KAAKe,SAEhBP,IAAIC,WAAWC,MAAM,mD,EAQjCM,SAAA,SAASC,GAAG,WACVA,EAAEC,iBAEFlB,KAAKe,SAAU,EAEf,IAAMR,EAAQP,KAAKI,WACbD,EAAeH,KAAKG,aAK1B,GAAII,GAASA,IAAUJ,EACrB,OAAOH,KAAKC,WACTkB,KAAK,CAAEZ,UACPa,MAAK,WACAZ,IAAIa,kBAAkB,EAAKpB,aAC7BO,IAAIc,QAAQC,IAAI,UAAUC,SAE5BC,EAAEC,SACF,EAAKC,UAPF,OASE,WACL,EAAKZ,SAAU,EACfU,EAAEC,YAGN1B,KAAK2B,Q,GAjEwCC,KAsEnDC,OAAOC,MAAMC,IAAI,mCAAoClC","file":"forum/components/RenameDiscussionModal.js","sourcesContent":["import Modal from '../../common/components/Modal';\nimport Button from '../../common/components/Button';\nimport Stream from '../../common/utils/Stream';\n\n/**\n * The 'RenameDiscussionModal' displays a modal dialog with an input to rename a discussion\n */\nexport default class RenameDiscussionModal extends Modal {\n oninit(vnode) {\n super.oninit(vnode);\n\n this.discussion = this.attrs.discussion;\n this.currentTitle = this.attrs.currentTitle;\n this.newTitle = Stream(this.currentTitle);\n }\n\n className() {\n return 'RenameDiscussionModal Modal--small';\n }\n\n title() {\n return app.translator.trans('core.forum.rename_discussion.title');\n }\n\n content() {\n return (\n <div className=\"Modal-body\">\n <div className=\"Form Form--centered\">\n <div className=\"Form-group\">\n <input className=\"FormControl\" bidi={this.newTitle} type=\"text\" />\n </div>\n <div className=\"Form-group\">\n {Button.component(\n {\n className: 'Button Button--primary Button--block',\n type: 'submit',\n loading: this.loading,\n },\n app.translator.trans('core.forum.rename_discussion.submit_button')\n )}\n </div>\n </div>\n </div>\n );\n }\n\n onsubmit(e) {\n e.preventDefault();\n\n this.loading = true;\n\n const title = this.newTitle();\n const currentTitle = this.currentTitle;\n\n // If the title is different to what it was before, then save it. After the\n // save has completed, update the post stream as there will be a new post\n // indicating that the discussion was renamed.\n if (title && title !== currentTitle) {\n return this.discussion\n .save({ title })\n .then(() => {\n if (app.viewingDiscussion(this.discussion)) {\n app.current.get('stream').update();\n }\n m.redraw();\n this.hide();\n })\n .catch(() => {\n this.loading = false;\n m.redraw();\n });\n } else {\n this.hide();\n }\n }\n}\n\nwindow.flreg.add('components/RenameDiscussionModal', RenameDiscussionModal)"],"sourceRoot":""}
|
|
2
js/dist/forum/components/SessionDropdown.js
vendored
2
js/dist/forum/components/SessionDropdown.js
vendored
@@ -1,2 +0,0 @@
|
|||||||
(self.webpackChunk_flarum_core=self.webpackChunk_flarum_core||[]).push([[557],{2304:(t,n,o)=>{o.r(n),o.d(n,{default:()=>f});var a=o(2122),e=o(1788),r=o(9908),s=o(507),i=o(9657),p=o(2558),u=o(2587),l=o(4594),c=o(5128),f=function(t){function n(){return t.apply(this,arguments)||this}(0,e.Z)(n,t),n.initAttrs=function(n){t.initAttrs.call(this,n),n.className="SessionDropdown",n.buttonClassName="Button Button--user Button--flat",n.menuClassName="Dropdown-menu--right",n.accessibleToggleLabel=app.translator.trans("core.forum.header.session_dropdown_accessible_label")};var o=n.prototype;return o.view=function(n){return t.prototype.view.call(this,(0,a.Z)({},n,{children:this.items().toArray()}))},o.getButtonContent=function(){var t=app.session.user;return[(0,r.Z)(t)," ",m("span",{className:"Button-label"},(0,s.Z)(t))]},o.items=function(){var t=new l.Z,n=app.session.user;return t.add("profile",p.Z.component({icon:"fas fa-user",href:app.route.user(n)},app.translator.trans("core.forum.header.profile_button")),100),t.add("settings",p.Z.component({icon:"fas fa-cog",href:app.route("settings")},app.translator.trans("core.forum.header.settings_button")),50),app.forum.attribute("adminUrl")&&t.add("administration",p.Z.component({icon:"fas fa-wrench",href:app.forum.attribute("adminUrl"),target:"_blank"},app.translator.trans("core.forum.header.admin_button")),0),t.add("separator",c.Z.component(),-90),t.add("logOut",u.Z.component({icon:"fas fa-sign-out-alt",onclick:app.session.logout.bind(app.session)},app.translator.trans("core.forum.header.log_out_button")),-100),t},n}(i.Z);window.flreg.add("components/SessionDropdown",f)}}]);
|
|
||||||
//# sourceMappingURL=SessionDropdown.js.map
|
|
@@ -1 +0,0 @@
|
|||||||
{"version":3,"sources":["webpack://@flarum/core/./src/forum/components/SessionDropdown.js"],"names":["SessionDropdown","initAttrs","attrs","className","buttonClassName","menuClassName","accessibleToggleLabel","app","translator","trans","view","vnode","children","this","items","toArray","getButtonContent","user","session","avatar","username","ItemList","add","LinkButton","icon","href","route","forum","attribute","target","Separator","Button","onclick","logout","bind","Dropdown","window","flreg"],"mappings":"yNAYqBA,E,6EACZC,UAAP,SAAiBC,GACf,EAAMD,UAAN,UAAgBC,GAEhBA,EAAMC,UAAY,kBAClBD,EAAME,gBAAkB,mCACxBF,EAAMG,cAAgB,uBAEtBH,EAAMI,sBAAwBC,IAAIC,WAAWC,MAAM,wD,2BAGrDC,KAAA,SAAKC,GACH,mBAAaD,KAAb,qBAAuBC,EAAvB,CAA8BC,SAAUC,KAAKC,QAAQC,c,EAGvDC,iBAAA,WACE,IAAMC,EAAOV,IAAIW,QAAQD,KAEzB,MAAO,EAACE,OAAOF,GAAO,IAAK,UAAMd,UAAU,iBAAgBiB,OAASH,M,EAQtEH,MAAA,WACE,IAAMA,EAAQ,IAAIO,IACZJ,EAAOV,IAAIW,QAAQD,KAuDzB,OArDAH,EAAMQ,IACJ,UACAC,cACE,CACEC,KAAM,cACNC,KAAMlB,IAAImB,MAAMT,KAAKA,IAEvBV,IAAIC,WAAWC,MAAM,qCAEvB,KAGFK,EAAMQ,IACJ,WACAC,cACE,CACEC,KAAM,aACNC,KAAMlB,IAAImB,MAAM,aAElBnB,IAAIC,WAAWC,MAAM,sCAEvB,IAGEF,IAAIoB,MAAMC,UAAU,aACtBd,EAAMQ,IACJ,iBACAC,cACE,CACEC,KAAM,gBACNC,KAAMlB,IAAIoB,MAAMC,UAAU,YAC1BC,OAAQ,UAEVtB,IAAIC,WAAWC,MAAM,mCAEvB,GAIJK,EAAMQ,IAAI,YAAaQ,iBAAwB,IAE/ChB,EAAMQ,IACJ,SACAS,cACE,CACEP,KAAM,sBACNQ,QAASzB,IAAIW,QAAQe,OAAOC,KAAK3B,IAAIW,UAEvCX,IAAIC,WAAWC,MAAM,sCAEtB,KAGIK,G,GAnFkCqB,KAuF7CC,OAAOC,MAAMf,IAAI,6BAA8BtB","file":"forum/components/SessionDropdown.js","sourcesContent":["import avatar from '../../common/helpers/avatar';\nimport username from '../../common/helpers/username';\nimport Dropdown from '../../common/components/Dropdown';\nimport LinkButton from '../../common/components/LinkButton';\nimport Button from '../../common/components/Button';\nimport ItemList from '../../common/utils/ItemList';\nimport Separator from '../../common/components/Separator';\n\n/**\n * The `SessionDropdown` component shows a button with the current user's\n * avatar/name, with a dropdown of session controls.\n */\nexport default class SessionDropdown extends Dropdown {\n static initAttrs(attrs) {\n super.initAttrs(attrs);\n\n attrs.className = 'SessionDropdown';\n attrs.buttonClassName = 'Button Button--user Button--flat';\n attrs.menuClassName = 'Dropdown-menu--right';\n\n attrs.accessibleToggleLabel = app.translator.trans('core.forum.header.session_dropdown_accessible_label');\n }\n\n view(vnode) {\n return super.view({ ...vnode, children: this.items().toArray() });\n }\n\n getButtonContent() {\n const user = app.session.user;\n\n return [avatar(user), ' ', <span className=\"Button-label\">{username(user)}</span>];\n }\n\n /**\n * Build an item list for the contents of the dropdown menu.\n *\n * @return {ItemList}\n */\n items() {\n const items = new ItemList();\n const user = app.session.user;\n\n items.add(\n 'profile',\n LinkButton.component(\n {\n icon: 'fas fa-user',\n href: app.route.user(user),\n },\n app.translator.trans('core.forum.header.profile_button')\n ),\n 100\n );\n\n items.add(\n 'settings',\n LinkButton.component(\n {\n icon: 'fas fa-cog',\n href: app.route('settings'),\n },\n app.translator.trans('core.forum.header.settings_button')\n ),\n 50\n );\n\n if (app.forum.attribute('adminUrl')) {\n items.add(\n 'administration',\n LinkButton.component(\n {\n icon: 'fas fa-wrench',\n href: app.forum.attribute('adminUrl'),\n target: '_blank',\n },\n app.translator.trans('core.forum.header.admin_button')\n ),\n 0\n );\n }\n\n items.add('separator', Separator.component(), -90);\n\n items.add(\n 'logOut',\n Button.component(\n {\n icon: 'fas fa-sign-out-alt',\n onclick: app.session.logout.bind(app.session),\n },\n app.translator.trans('core.forum.header.log_out_button')\n ),\n -100\n );\n\n return items;\n }\n}\n\nwindow.flreg.add('components/SessionDropdown', SessionDropdown)"],"sourceRoot":""}
|
|
2
js/dist/forum/components/SettingsPage.js
vendored
2
js/dist/forum/components/SettingsPage.js
vendored
@@ -1,2 +0,0 @@
|
|||||||
(self.webpackChunk_flarum_core=self.webpackChunk_flarum_core||[]).push([[826],{6991:(t,n,e)=>{e.d(n,{Z:()=>c});var i=e(1788),a=e(8931),o=e(5971),r=e(9100),s=e(4594),c=function(t){function n(){return t.apply(this,arguments)||this}(0,i.Z)(n,t);var e=n.prototype;return e.oninit=function(n){t.prototype.oninit.call(this,n),this.methods=this.notificationMethods().toArray(),this.loading={},this.types=this.notificationTypes().toArray()},e.view=function(){var t=this,n=this.attrs.user.preferences();return m("table",{className:"NotificationGrid"},m("thead",null,m("tr",null,m("td",null),this.methods.map((function(n){return m("th",{className:"NotificationGrid-groupToggle",onclick:t.toggleMethod.bind(t,n.name)},(0,r.Z)(n.icon)," ",n.label)})))),m("tbody",null,this.types.map((function(e){return m("tr",null,m("td",{className:"NotificationGrid-groupToggle",onclick:t.toggleType.bind(t,e.name)},(0,r.Z)(e.icon)," ",e.label),t.methods.map((function(i){var a=t.preferenceKey(e.name,i.name);return m("td",{className:"NotificationGrid-checkbox"},m(o.Z,{state:!!n[a],loading:t.loading[a],disabled:!(a in n),onchange:t.toggle.bind(t,[a])}))})))}))))},e.oncreate=function(n){t.prototype.oncreate.call(this,n),this.$("thead .NotificationGrid-groupToggle").bind("mouseenter mouseleave",(function(t){var n=parseInt($(this).index(),10)+1;$(this).parents("table").find("td:nth-child("+n+")").toggleClass("highlighted","mouseenter"===t.type)})),this.$("tbody .NotificationGrid-groupToggle").bind("mouseenter mouseleave",(function(t){$(this).parent().find("td").toggleClass("highlighted","mouseenter"===t.type)}))},e.toggle=function(t){var n=this,e=this.attrs.user,i=e.preferences(),a=!i[t[0]];t.forEach((function(t){n.loading[t]=!0,i[t]=a})),m.redraw(),e.save({preferences:i}).then((function(){t.forEach((function(t){return n.loading[t]=!1})),m.redraw()}))},e.toggleMethod=function(t){var n=this,e=this.types.map((function(e){return n.preferenceKey(e.name,t)})).filter((function(t){return t in n.attrs.user.preferences()}));this.toggle(e)},e.toggleType=function(t){var n=this,e=this.methods.map((function(e){return n.preferenceKey(t,e.name)})).filter((function(t){return t in n.attrs.user.preferences()}));this.toggle(e)},e.preferenceKey=function(t,n){return"notify_"+t+"_"+n},e.notificationMethods=function(){var t=new s.Z;return t.add("alert",{name:"alert",icon:"fas fa-bell",label:app.translator.trans("core.forum.settings.notify_by_web_heading")}),t.add("email",{name:"email",icon:"far fa-envelope",label:app.translator.trans("core.forum.settings.notify_by_email_heading")}),t},e.notificationTypes=function(){var t=new s.Z;return t.add("discussionRenamed",{name:"discussionRenamed",icon:"fas fa-pencil-alt",label:app.translator.trans("core.forum.settings.notify_discussion_renamed_label")}),t},n}(a.Z);window.flreg.add("components/NotificationGrid",c)},5792:(t,n,e)=>{e.a(t,(async t=>{e.r(n),e.d(n,{default:()=>u});var i=e(1788),a=e(4594),o=e(833),r=e(2587),s=e(4926),c=e(6991),l=e(7473),u=function(t){function n(){return t.apply(this,arguments)||this}(0,i.Z)(n,t);var u=n.prototype;return u.oninit=function(n){t.prototype.oninit.call(this,n),this.show(app.session.user),app.setTitle(app.translator.trans("core.forum.settings.title"))},u.content=function(){return m("div",{className:"SettingsPage"},m("ul",null,(0,l.Z)(this.settingsItems().toArray())))},u.settingsItems=function(){var t=this,n=new a.Z;return["account","notifications","privacy"].forEach((function(e){n.add(e,m(s.Z,{className:"Settings-"+e,label:app.translator.trans("core.forum.settings."+e+"_heading")},t[e+"Items"]().toArray()))})),n},u.accountItems=function(){var t=new a.Z,n=function(){return e.e(372).then(e.bind(e,4146))},i=function(){return e.e(503).then(e.bind(e,22))};return t.add("changePassword",m(r.Z,{className:"Button",onclick:function(){return app.modal.show(i)}},app.translator.trans("core.forum.settings.change_password_button"))),t.add("changeEmail",m(r.Z,{className:"Button",onclick:function(){return app.modal.show(n)}},app.translator.trans("core.forum.settings.change_email_button"))),t},u.notificationsItems=function(){var t=new a.Z;return t.add("notificationGrid",m(c.Z,{user:this.user})),t},u.privacyItems=function(){var t=this,n=new a.Z;return n.add("discloseOnline",m(o.Z,{state:this.user.preferences().discloseOnline,onchange:function(n){t.discloseOnlineLoading=!0,t.user.savePreferences({discloseOnline:n}).then((function(){t.discloseOnlineLoading=!1,m.redraw()}))},loading:this.discloseOnlineLoading},app.translator.trans("core.forum.settings.privacy_disclose_online_label"))),n},n}((await e.e(251).then(e.bind(e,6068))).default);window.flreg.add("components/SettingsPage",u),t()}),1)}}]);
|
|
||||||
//# sourceMappingURL=SettingsPage.js.map
|
|
1
js/dist/forum/components/SettingsPage.js.map
vendored
1
js/dist/forum/components/SettingsPage.js.map
vendored
File diff suppressed because one or more lines are too long
2
js/dist/forum/components/SignUpModal.js
vendored
2
js/dist/forum/components/SignUpModal.js
vendored
@@ -1,2 +0,0 @@
|
|||||||
(self.webpackChunk_flarum_core=self.webpackChunk_flarum_core||[]).push([[395],{7527:(t,a,s)=>{s.d(a,{Z:()=>n});var r=s(1788),e=s(8931),i=s(4594),n=function(t){function a(){return t.apply(this,arguments)||this}(0,r.Z)(a,t);var s=a.prototype;return s.view=function(){return m("div",{className:"LogInButtons"},this.items().toArray())},s.items=function(){return new i.Z},a}(e.Z);window.flreg.add("components/LogInButtons",n)},3235:(t,a,s)=>{s.r(a),s.d(a,{default:()=>u});var r=s(1788),e=s(5556),i=s(2587),n=s(7527),o=s(5731),l=s(4594),d=s(1024),u=function(t){function a(){return t.apply(this,arguments)||this}(0,r.Z)(a,t);var e=a.prototype;return e.oninit=function(a){t.prototype.oninit.call(this,a),this.username=(0,d.Z)(this.attrs.username||""),this.email=(0,d.Z)(this.attrs.email||""),this.password=(0,d.Z)(this.attrs.password||"")},e.className=function(){return"Modal--small SignUpModal"},e.title=function(){return app.translator.trans("core.forum.sign_up.title")},e.content=function(){return[m("div",{className:"Modal-body"},this.body()),m("div",{className:"Modal-footer"},this.footer())]},e.isProvided=function(t){return this.attrs.provided&&-1!==this.attrs.provided.indexOf(t)},e.body=function(){return[this.attrs.token?"":m(n.Z,null),m("div",{className:"Form Form--centered"},this.fields().toArray())]},e.fields=function(){var t=new l.Z;return t.add("username",m("div",{className:"Form-group"},m("input",{className:"FormControl",name:"username",type:"text",placeholder:(0,o.Z)(app.translator.trans("core.forum.sign_up.username_placeholder")),bidi:this.username,disabled:this.loading||this.isProvided("username")})),30),t.add("email",m("div",{className:"Form-group"},m("input",{className:"FormControl",name:"email",type:"email",placeholder:(0,o.Z)(app.translator.trans("core.forum.sign_up.email_placeholder")),bidi:this.email,disabled:this.loading||this.isProvided("email")})),20),this.attrs.token||t.add("password",m("div",{className:"Form-group"},m("input",{className:"FormControl",name:"password",type:"password",placeholder:(0,o.Z)(app.translator.trans("core.forum.sign_up.password_placeholder")),bidi:this.password,disabled:this.loading})),10),t.add("submit",m("div",{className:"Form-group"},m(i.Z,{className:"Button Button--primary Button--block",type:"submit",loading:this.loading},app.translator.trans("core.forum.sign_up.submit_button"))),-10),t},e.footer=function(){return[m("p",{className:"SignUpModal-logIn"},app.translator.trans("core.forum.sign_up.log_in_text",{a:m("a",{onclick:this.logIn.bind(this)})}))]},e.logIn=function(){var t={identification:this.email()||this.username(),password:this.password()};app.modal.show((function(){return s.e(460).then(s.bind(s,1498))}),t)},e.onready=function(){this.attrs.username&&!this.attrs.email?this.$("[name=email]").select():this.$("[name=username]").select()},e.onsubmit=function(t){t.preventDefault(),this.loading=!0;var a=this.submitData();app.request({url:app.forum.attribute("baseUrl")+"/register",method:"POST",body:a,errorHandler:this.onerror.bind(this)}).then((function(){return window.location.reload()}),this.loaded.bind(this))},e.submitData=function(){var t={username:this.username(),email:this.email()};return this.attrs.token?t.token=this.attrs.token:t.password=this.password(),t},a}(e.Z);window.flreg.add("components/SignUpModal",u)}}]);
|
|
||||||
//# sourceMappingURL=SignUpModal.js.map
|
|
1
js/dist/forum/components/SignUpModal.js.map
vendored
1
js/dist/forum/components/SignUpModal.js.map
vendored
File diff suppressed because one or more lines are too long
2
js/dist/forum/components/UserPage.js
vendored
2
js/dist/forum/components/UserPage.js
vendored
File diff suppressed because one or more lines are too long
1
js/dist/forum/components/UserPage.js.map
vendored
1
js/dist/forum/components/UserPage.js.map
vendored
File diff suppressed because one or more lines are too long
3
js/dist/forum/flarum-core.js
vendored
3
js/dist/forum/flarum-core.js
vendored
File diff suppressed because one or more lines are too long
28
js/dist/forum/flarum-core.js.LICENSE.txt
vendored
28
js/dist/forum/flarum-core.js.LICENSE.txt
vendored
@@ -1,28 +0,0 @@
|
|||||||
/*!
|
|
||||||
* Block below copied from Protovis: http://mbostock.github.com/protovis/
|
|
||||||
* Copyright 2010 Stanford Visualization Group
|
|
||||||
* Licensed under the BSD License: http://www.opensource.org/licenses/bsd-license.php
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* Color Thief v2.0
|
|
||||||
* by Lokesh Dhakar - http://www.lokeshdhakar.com
|
|
||||||
*
|
|
||||||
* Thanks
|
|
||||||
* ------
|
|
||||||
* Nick Rabinowitz - For creating quantize.js.
|
|
||||||
* John Schulz - For clean up and optimization. @JFSIII
|
|
||||||
* Nathan Spady - For adding drag and drop support to the demo page.
|
|
||||||
*
|
|
||||||
* License
|
|
||||||
* -------
|
|
||||||
* Copyright 2011, 2015 Lokesh Dhakar
|
|
||||||
* Released under the MIT license
|
|
||||||
* https://raw.githubusercontent.com/lokesh/color-thief/master/LICENSE
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* quantize.js Copyright 2008 Nick Rabinowitz.
|
|
||||||
* Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
|
|
||||||
*/
|
|
1
js/dist/forum/flarum-core.js.map
vendored
1
js/dist/forum/flarum-core.js.map
vendored
File diff suppressed because one or more lines are too long
2
js/dist/forum/runtime.js
vendored
2
js/dist/forum/runtime.js
vendored
@@ -1,2 +0,0 @@
|
|||||||
(()=>{var e,o,r,t,n,a,s,i,u={},c={};function m(e){var o=c[e];if(void 0!==o)return o.exports;var r=c[e]={exports:{}};return u[e].call(r.exports,r,r.exports,m),r.exports}m.m=u,e="function"==typeof Symbol?Symbol("webpack then"):"__webpack_then__",o="function"==typeof Symbol?Symbol("webpack exports"):"__webpack_exports__",r=e=>{e&&(e.forEach((e=>e.r--)),e.forEach((e=>e.r--?e.r++:e())))},t=e=>!--e.r&&e(),n=(e,o)=>e?e.push(o):t(o),m.a=(a,s,i)=>{var u,c,m,p=i&&[],f=a.exports,l=!0,d=!1,h=(o,r,t)=>{d||(d=!0,r.r+=o.length,o.map(((o,n)=>o[e](r,t))),d=!1)},b=new Promise(((e,o)=>{m=o,c=()=>(e(f),r(p),p=0)}));b[o]=f,b[e]=(e,o)=>{if(l)return t(e);u&&h(u,e,o),n(p,e),b.catch(o)},a.exports=b,s((a=>{if(!a)return c();var s,i;u=(a=>a.map((a=>{if(null!==a&&"object"==typeof a){if(a[e])return a;if(a.then){var s=[];a.then((e=>{i[o]=e,r(s),s=0}));var i={[e]:(e,o)=>(n(s,e),a.catch(o))};return i}}return{[e]:e=>t(e),[o]:a}})))(a);var m=new Promise(((e,r)=>{(s=()=>e(i=u.map((e=>e[o])))).r=0,h(u,s,r)}));return s.r?m:i})).then(c,m),l=!1},a=[],m.O=(e,o,r,t)=>{if(!o){var n=1/0;for(u=0;u<a.length;u++){for(var[o,r,t]=a[u],s=!0,i=0;i<o.length;i++)(!1&t||n>=t)&&Object.keys(m.O).every((e=>m.O[e](o[i])))?o.splice(i--,1):(s=!1,t<n&&(n=t));s&&(a.splice(u--,1),e=r())}return e}t=t||0;for(var u=a.length;u>0&&a[u-1][2]>t;u--)a[u]=a[u-1];a[u]=[o,r,t]},m.n=e=>{var o=e&&e.__esModule?()=>e.default:()=>e;return m.d(o,{a:o}),o},m.d=(e,o)=>{for(var r in o)m.o(o,r)&&!m.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:o[r]})},m.f={},m.e=e=>Promise.all(Object.keys(m.f).reduce(((o,r)=>(m.f[r](e,o),o)),[])),m.u=e=>({28:"forum/components/PostsUserPage",77:"forum/components/NotificationList",192:"forum/components/Post",247:"common/components/TextEditor",251:"forum/components/UserPage",293:"forum/components/EditPostComposer",352:"forum/components/EditUserModal",372:"forum/components/ChangeEmailModal",395:"forum/components/SignUpModal",437:"forum/components/DiscussionComposer",460:"forum/components/LogInModal",502:"forum/components/ForgotPasswordModal",503:"forum/components/ChangePasswordModal",557:"forum/components/SessionDropdown",563:"forum/components/PostStream",564:"forum/components/RenameDiscussionModal",765:"forum/components/NotificationsDropdown",779:"forum/components/PostUser",799:"forum/components/DiscussionsUserPage",826:"forum/components/SettingsPage",847:"forum/components/IndexPage"}[e]+".js"),m.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),m.o=(e,o)=>Object.prototype.hasOwnProperty.call(e,o),s={},i="@flarum/core:",m.l=(e,o,r,t)=>{if(s[e])s[e].push(o);else{var n,a;if(void 0!==r)for(var u=document.getElementsByTagName("script"),c=0;c<u.length;c++){var p=u[c];if(p.getAttribute("src")==e||p.getAttribute("data-webpack")==i+r){n=p;break}}n||(a=!0,(n=document.createElement("script")).charset="utf-8",n.timeout=120,m.nc&&n.setAttribute("nonce",m.nc),n.setAttribute("data-webpack",i+r),n.src=e),s[e]=[o];var f=(o,r)=>{n.onerror=n.onload=null,clearTimeout(l);var t=s[e];if(delete s[e],n.parentNode&&n.parentNode.removeChild(n),t&&t.forEach((e=>e(r))),o)return o(r)},l=setTimeout(f.bind(null,void 0,{type:"timeout",target:n}),12e4);n.onerror=f.bind(null,n.onerror),n.onload=f.bind(null,n.onload),a&&document.head.appendChild(n)}},m.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},m.p="/assets/",(()=>{var e={406:0};m.f.j=(o,r)=>{var t=m.o(e,o)?e[o]:void 0;if(0!==t)if(t)r.push(t[2]);else if(406!=o){var n=new Promise(((r,n)=>t=e[o]=[r,n]));r.push(t[2]=n);var a=m.p+m.u(o),s=new Error;m.l(a,(r=>{if(m.o(e,o)&&(0!==(t=e[o])&&(e[o]=void 0),t)){var n=r&&("load"===r.type?"missing":r.type),a=r&&r.target&&r.target.src;s.message="Loading chunk "+o+" failed.\n("+n+": "+a+")",s.name="ChunkLoadError",s.type=n,s.request=a,t[1](s)}}),"chunk-"+o,o)}else e[o]=0},m.O.j=o=>0===e[o];var o=(o,r)=>{var t,n,[a,s,i]=r,u=0;for(t in s)m.o(s,t)&&(m.m[t]=s[t]);for(i&&i(m),o&&o(r);u<a.length;u++)n=a[u],m.o(e,n)&&e[n]&&e[n][0](),e[a[u]]=0;m.O()},r=self.webpackChunk_flarum_core=self.webpackChunk_flarum_core||[];r.forEach(o.bind(null,0)),r.push=o.bind(null,r.push.bind(r))})(),m.O()})();
|
|
||||||
//# sourceMappingURL=runtime.js.map
|
|
1
js/dist/forum/runtime.js.map
vendored
1
js/dist/forum/runtime.js.map
vendored
File diff suppressed because one or more lines are too long
3
js/dist/forum/vendor.js
vendored
3
js/dist/forum/vendor.js
vendored
File diff suppressed because one or more lines are too long
24
js/dist/forum/vendor.js.LICENSE.txt
vendored
24
js/dist/forum/vendor.js.LICENSE.txt
vendored
@@ -1,24 +0,0 @@
|
|||||||
/*!
|
|
||||||
* Sizzle CSS Selector Engine v2.3.6
|
|
||||||
* https://sizzlejs.com/
|
|
||||||
*
|
|
||||||
* Copyright JS Foundation and other contributors
|
|
||||||
* Released under the MIT license
|
|
||||||
* https://js.foundation/
|
|
||||||
*
|
|
||||||
* Date: 2021-02-16
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* jQuery JavaScript Library v3.6.0
|
|
||||||
* https://jquery.com/
|
|
||||||
*
|
|
||||||
* Includes Sizzle.js
|
|
||||||
* https://sizzlejs.com/
|
|
||||||
*
|
|
||||||
* Copyright OpenJS Foundation and other contributors
|
|
||||||
* Released under the MIT license
|
|
||||||
* https://jquery.org/license
|
|
||||||
*
|
|
||||||
* Date: 2021-03-02T17:08Z
|
|
||||||
*/
|
|
1
js/dist/forum/vendor.js.map
vendored
1
js/dist/forum/vendor.js.map
vendored
File diff suppressed because one or more lines are too long
@@ -8,4 +8,4 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export * from './src/common';
|
export * from './src/common';
|
||||||
export * from './src/forum';
|
export * from './src/forum';
|
11339
js/package-lock.json
generated
11339
js/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -2,45 +2,41 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"name": "@flarum/core",
|
"name": "@flarum/core",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@askvortsov/rich-icu-message-formatter": "^0.1.0",
|
||||||
|
"@ultraq/icu-message-formatter": "^0.10.0",
|
||||||
"bootstrap": "^3.4.1",
|
"bootstrap": "^3.4.1",
|
||||||
"clsx": "^1.1.1",
|
"clsx": "^1.1.1",
|
||||||
"color-thief-browser": "^2.0.2",
|
"color-thief-browser": "^2.0.2",
|
||||||
"dayjs": "^1.10.4",
|
"dayjs": "^1.10.4",
|
||||||
|
"expose-loader": "^2.0.0",
|
||||||
"jquery": "^3.6.0",
|
"jquery": "^3.6.0",
|
||||||
"jquery.hotkeys": "^0.1.0",
|
"jquery.hotkeys": "^0.1.0",
|
||||||
"lodash-es": "^4.17.21",
|
|
||||||
"mithril": "^2.0.4",
|
"mithril": "^2.0.4",
|
||||||
"punycode": "^2.1.1",
|
"punycode": "^2.1.1",
|
||||||
"spin.js": "^3.1.0",
|
"spin.js": "^3.1.0",
|
||||||
"textarea-caret": "^3.1.0",
|
"textarea-caret": "^3.1.0",
|
||||||
"webpack-visualizer-plugin": "^0.1.11",
|
"throttle-debounce": "^3.0.1"
|
||||||
"zepto": "^1.2.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/plugin-proposal-export-default-from": "^7.12.13",
|
|
||||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
|
||||||
"@babel/plugin-syntax-top-level-await": "^7.12.13",
|
|
||||||
"@babel/preset-typescript": "^7.13.0",
|
"@babel/preset-typescript": "^7.13.0",
|
||||||
"@types/jquery": "^3.5.5",
|
"@types/jquery": "^3.5.5",
|
||||||
"@types/lodash-es": "^4.17.4",
|
|
||||||
"@types/mithril": "^2.0.7",
|
"@types/mithril": "^2.0.7",
|
||||||
"@types/punycode": "^2.1.0",
|
"@types/punycode": "^2.1.0",
|
||||||
"@types/textarea-caret": "^3.0.0",
|
"@types/textarea-caret": "^3.0.0",
|
||||||
"babel-plugin-add-module-exports": "^1.0.4",
|
|
||||||
"bundlewatch": "^0.3.2",
|
"bundlewatch": "^0.3.2",
|
||||||
"expose-loader": "^2.0.0",
|
"cross-env": "^7.0.3",
|
||||||
"flarum-webpack-config": "0.1.0-beta.10",
|
"flarum-webpack-config": "^1.0.0",
|
||||||
"husky": "^4.3.8",
|
"husky": "^4.3.8",
|
||||||
"loader-utils": "^1.4.0",
|
|
||||||
"prettier": "^2.2.1",
|
"prettier": "^2.2.1",
|
||||||
"webpack": "~5.33.2",
|
"webpack": "^5.0.0",
|
||||||
"webpack-bundle-analyzer": "^4.4.1",
|
"webpack-bundle-analyzer": "^4.4.1",
|
||||||
"webpack-cli": "~4.6.0",
|
"webpack-cli": "^4.0.0",
|
||||||
"webpack-merge": "~5.7.3"
|
"webpack-merge": "^4.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "webpack --mode development --watch",
|
"dev": "webpack --mode development --watch",
|
||||||
"build": "webpack --mode production",
|
"build": "webpack --mode production",
|
||||||
|
"analyze": "cross-env ANALYZER=true npm run build",
|
||||||
"format": "prettier --write src",
|
"format": "prettier --write src",
|
||||||
"format-check": "prettier --check src"
|
"format-check": "prettier --check src"
|
||||||
},
|
},
|
||||||
|
3
js/shims.d.ts
vendored
3
js/shims.d.ts
vendored
@@ -8,8 +8,6 @@ import * as _$ from 'jquery';
|
|||||||
// Globals from flarum/core
|
// Globals from flarum/core
|
||||||
import Application from './src/common/Application';
|
import Application from './src/common/Application';
|
||||||
|
|
||||||
import FlarumRegistry from './src/common/FlarumRegistry';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* flarum/core exposes several extensions globally:
|
* flarum/core exposes several extensions globally:
|
||||||
*
|
*
|
||||||
@@ -43,5 +41,4 @@ declare global {
|
|||||||
*/
|
*/
|
||||||
declare global {
|
declare global {
|
||||||
const app: Application;
|
const app: Application;
|
||||||
const flreg: FlarumRegistry;
|
|
||||||
}
|
}
|
||||||
|
@@ -24,6 +24,7 @@ import UploadImageButton from './components/UploadImageButton';
|
|||||||
import LoadingModal from './components/LoadingModal';
|
import LoadingModal from './components/LoadingModal';
|
||||||
import DashboardPage from './components/DashboardPage';
|
import DashboardPage from './components/DashboardPage';
|
||||||
import BasicsPage from './components/BasicsPage';
|
import BasicsPage from './components/BasicsPage';
|
||||||
|
import UserListPage from './components/UserListPage';
|
||||||
import EditCustomHeaderModal from './components/EditCustomHeaderModal';
|
import EditCustomHeaderModal from './components/EditCustomHeaderModal';
|
||||||
import PermissionsPage from './components/PermissionsPage';
|
import PermissionsPage from './components/PermissionsPage';
|
||||||
import PermissionDropdown from './components/PermissionDropdown';
|
import PermissionDropdown from './components/PermissionDropdown';
|
||||||
@@ -59,6 +60,7 @@ export default Object.assign(compat, {
|
|||||||
'components/LoadingModal': LoadingModal,
|
'components/LoadingModal': LoadingModal,
|
||||||
'components/DashboardPage': DashboardPage,
|
'components/DashboardPage': DashboardPage,
|
||||||
'components/BasicsPage': BasicsPage,
|
'components/BasicsPage': BasicsPage,
|
||||||
|
'components/UserListPage': UserListPage,
|
||||||
'components/EditCustomHeaderModal': EditCustomHeaderModal,
|
'components/EditCustomHeaderModal': EditCustomHeaderModal,
|
||||||
'components/PermissionsPage': PermissionsPage,
|
'components/PermissionsPage': PermissionsPage,
|
||||||
'components/PermissionDropdown': PermissionDropdown,
|
'components/PermissionDropdown': PermissionDropdown,
|
||||||
|
@@ -94,6 +94,13 @@ export default class AdminNav extends Component {
|
|||||||
</LinkButton>
|
</LinkButton>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
items.add(
|
||||||
|
'userList',
|
||||||
|
<LinkButton href={app.route('users')} icon="fas fa-users" title={app.translator.trans('core.admin.nav.userlist_title')}>
|
||||||
|
{app.translator.trans('core.admin.nav.userlist_button')}
|
||||||
|
</LinkButton>
|
||||||
|
);
|
||||||
|
|
||||||
items.add(
|
items.add(
|
||||||
'search',
|
'search',
|
||||||
<div className="Search-input">
|
<div className="Search-input">
|
||||||
|
@@ -100,8 +100,6 @@ export default class AdminPage extends Page {
|
|||||||
|
|
||||||
const { setting, help, ...componentAttrs } = entry;
|
const { setting, help, ...componentAttrs } = entry;
|
||||||
|
|
||||||
delete componentAttrs.help;
|
|
||||||
|
|
||||||
const value = this.setting([setting])();
|
const value = this.setting([setting])();
|
||||||
if (['bool', 'checkbox', 'switch', 'boolean'].includes(componentAttrs.type)) {
|
if (['bool', 'checkbox', 'switch', 'boolean'].includes(componentAttrs.type)) {
|
||||||
return (
|
return (
|
||||||
|
@@ -182,7 +182,7 @@ export default class PermissionGrid extends Component {
|
|||||||
|
|
||||||
return SettingDropdown.component({
|
return SettingDropdown.component({
|
||||||
defaultLabel: minutes
|
defaultLabel: minutes
|
||||||
? app.translator.transChoice('core.admin.permissions_controls.allow_some_minutes_button', minutes, { count: minutes })
|
? app.translator.trans('core.admin.permissions_controls.allow_some_minutes_button', { count: minutes })
|
||||||
: app.translator.trans('core.admin.permissions_controls.allow_indefinitely_button'),
|
: app.translator.trans('core.admin.permissions_controls.allow_indefinitely_button'),
|
||||||
key: 'allow_renaming',
|
key: 'allow_renaming',
|
||||||
options: [
|
options: [
|
||||||
@@ -224,7 +224,7 @@ export default class PermissionGrid extends Component {
|
|||||||
|
|
||||||
return SettingDropdown.component({
|
return SettingDropdown.component({
|
||||||
defaultLabel: minutes
|
defaultLabel: minutes
|
||||||
? app.translator.transChoice('core.admin.permissions_controls.allow_some_minutes_button', minutes, { count: minutes })
|
? app.translator.trans('core.admin.permissions_controls.allow_some_minutes_button', { count: minutes })
|
||||||
: app.translator.trans('core.admin.permissions_controls.allow_indefinitely_button'),
|
: app.translator.trans('core.admin.permissions_controls.allow_indefinitely_button'),
|
||||||
key: 'allow_post_editing',
|
key: 'allow_post_editing',
|
||||||
options: [
|
options: [
|
||||||
|
@@ -36,7 +36,7 @@ export default class UploadImageButton extends Button {
|
|||||||
$input
|
$input
|
||||||
.appendTo('body')
|
.appendTo('body')
|
||||||
.hide()
|
.hide()
|
||||||
.trigger('click')
|
.click()
|
||||||
.on('change', (e) => {
|
.on('change', (e) => {
|
||||||
const body = new FormData();
|
const body = new FormData();
|
||||||
body.append(this.attrs.name, $(e.target)[0].files[0]);
|
body.append(this.attrs.name, $(e.target)[0].files[0]);
|
||||||
|
384
js/src/admin/components/UserListPage.tsx
Normal file
384
js/src/admin/components/UserListPage.tsx
Normal file
@@ -0,0 +1,384 @@
|
|||||||
|
import EditUserModal from '../../common/components/EditUserModal';
|
||||||
|
import LoadingIndicator from '../../common/components/LoadingIndicator';
|
||||||
|
import Button from '../../common/components/Button';
|
||||||
|
|
||||||
|
import icon from '../../common/helpers/icon';
|
||||||
|
import listItems from '../../common/helpers/listItems';
|
||||||
|
|
||||||
|
import type User from '../../common/models/User';
|
||||||
|
|
||||||
|
import ItemList from '../../common/utils/ItemList';
|
||||||
|
import classList from '../../common/utils/classList';
|
||||||
|
import extractText from '../../common/utils/extractText';
|
||||||
|
|
||||||
|
import AdminPage from './AdminPage';
|
||||||
|
|
||||||
|
type ColumnData = {
|
||||||
|
/**
|
||||||
|
* Column title
|
||||||
|
*/
|
||||||
|
name: String;
|
||||||
|
/**
|
||||||
|
* Component(s) to show for this column.
|
||||||
|
*/
|
||||||
|
content: (user: User) => JSX.Element;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ApiPayload = {
|
||||||
|
data: Record<string, unknown>[];
|
||||||
|
included: Record<string, unknown>[];
|
||||||
|
links: {
|
||||||
|
first: string;
|
||||||
|
next?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type UsersApiResponse = User[] & { payload: ApiPayload };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Admin page which displays a paginated list of all users on the forum.
|
||||||
|
*/
|
||||||
|
export default class UserListPage extends AdminPage {
|
||||||
|
/**
|
||||||
|
* Number of users to load per page.
|
||||||
|
*/
|
||||||
|
private numPerPage: number = 50;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current page number. Zero-indexed.
|
||||||
|
*/
|
||||||
|
private pageNumber: number = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Total number of forum users.
|
||||||
|
*
|
||||||
|
* Fetched from the active `AdminApplication` (`app`), with
|
||||||
|
* data provided by `AdminPayload.php`, or `flarum/statistics`
|
||||||
|
* if installed.
|
||||||
|
*/
|
||||||
|
readonly userCount: number = app.data.modelStatistics.users.total;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get total number of user pages.
|
||||||
|
*/
|
||||||
|
private getTotalPageCount(): number {
|
||||||
|
if (this.userCount === -1) return 0;
|
||||||
|
|
||||||
|
return Math.ceil(this.userCount / this.numPerPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This page's array of users.
|
||||||
|
*
|
||||||
|
* `undefined` when page loads as no data has been fetched.
|
||||||
|
*/
|
||||||
|
private pageData: User[] | undefined = undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Are there more users available?
|
||||||
|
*/
|
||||||
|
private moreData: boolean = false;
|
||||||
|
|
||||||
|
private isLoadingPage: boolean = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component to render.
|
||||||
|
*/
|
||||||
|
content() {
|
||||||
|
if (typeof this.pageData === 'undefined') {
|
||||||
|
this.loadPage(0);
|
||||||
|
|
||||||
|
return [
|
||||||
|
<section class="UserListPage-grid UserListPage-grid--loading">
|
||||||
|
<LoadingIndicator containerClassName="LoadingIndicator--block" size="large" />
|
||||||
|
</section>,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns: (ColumnData & { itemName: string })[] = this.columns().toArray();
|
||||||
|
|
||||||
|
return [
|
||||||
|
<p class="UserListPage-totalUsers">{app.translator.trans('core.admin.users.total_users', { count: this.userCount })}</p>,
|
||||||
|
<section
|
||||||
|
class={classList(['UserListPage-grid', this.isLoadingPage ? 'UserListPage-grid--loadingPage' : 'UserListPage-grid--loaded'])}
|
||||||
|
style={{ '--columns': columns.length }}
|
||||||
|
role="table"
|
||||||
|
// +1 to account for header
|
||||||
|
aria-rowcount={this.pageData.length + 1}
|
||||||
|
aria-colcount={columns.length}
|
||||||
|
aria-live="polite"
|
||||||
|
aria-busy={this.isLoadingPage ? 'true' : 'false'}
|
||||||
|
>
|
||||||
|
{/* Render columns */}
|
||||||
|
{columns.map((column, colIndex) => (
|
||||||
|
<div class="UserListPage-grid-header" role="columnheader" aria-colindex={colIndex + 1} aria-rowindex={1}>
|
||||||
|
{column.name}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Render user data */}
|
||||||
|
{this.pageData.map((user, rowIndex) =>
|
||||||
|
columns.map((col, colIndex) => {
|
||||||
|
const columnContent = col.content && col.content(user);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
class={classList(['UserListPage-grid-rowItem', rowIndex % 2 > 0 && 'UserListPage-grid-rowItem--shaded'])}
|
||||||
|
data-user-id={user.id()}
|
||||||
|
data-column-name={col.itemName}
|
||||||
|
aria-colindex={colIndex + 1}
|
||||||
|
// +2 to account for 0-based index, and for the header row
|
||||||
|
aria-rowindex={rowIndex + 2}
|
||||||
|
role="cell"
|
||||||
|
>
|
||||||
|
{columnContent || app.translator.trans('core.admin.users.grid.invalid_column_content')}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Loading spinner that shows when a new page is being loaded */}
|
||||||
|
{this.isLoadingPage && <LoadingIndicator size="large" />}
|
||||||
|
</section>,
|
||||||
|
<nav class="UserListPage-gridPagination">
|
||||||
|
<Button
|
||||||
|
disabled={this.pageNumber === 0}
|
||||||
|
title={app.translator.trans('core.admin.users.pagination.back_button')}
|
||||||
|
onclick={this.previousPage.bind(this)}
|
||||||
|
icon="fas fa-chevron-left"
|
||||||
|
className="Button Button--icon UserListPage-backBtn"
|
||||||
|
/>
|
||||||
|
<span class="UserListPage-pageNumber">
|
||||||
|
{app.translator.trans('core.admin.users.pagination.page_counter', {
|
||||||
|
current: this.pageNumber + 1,
|
||||||
|
total: this.getTotalPageCount(),
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
<Button
|
||||||
|
disabled={!this.moreData}
|
||||||
|
title={app.translator.trans('core.admin.users.pagination.next_button')}
|
||||||
|
onclick={this.nextPage.bind(this)}
|
||||||
|
icon="fas fa-chevron-right"
|
||||||
|
className="Button Button--icon UserListPage-nextBtn"
|
||||||
|
/>
|
||||||
|
</nav>,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build an item list of columns to show for each user.
|
||||||
|
*
|
||||||
|
* Each column in the list should be an object with keys `name` and `content`.
|
||||||
|
*
|
||||||
|
* `name` is a string that will be used as the column name.
|
||||||
|
* `content` is a function with the User model passed as the first and only argument.
|
||||||
|
*
|
||||||
|
* See `UserListPage.tsx` for examples.
|
||||||
|
*/
|
||||||
|
columns(): ItemList {
|
||||||
|
const columns = new ItemList();
|
||||||
|
|
||||||
|
columns.add(
|
||||||
|
'id',
|
||||||
|
{
|
||||||
|
name: app.translator.trans('core.admin.users.grid.columns.user_id.title'),
|
||||||
|
content: (user: User) => user.id(),
|
||||||
|
},
|
||||||
|
100
|
||||||
|
);
|
||||||
|
|
||||||
|
columns.add(
|
||||||
|
'username',
|
||||||
|
{
|
||||||
|
name: app.translator.trans('core.admin.users.grid.columns.username.title'),
|
||||||
|
content: (user: User) => {
|
||||||
|
const profileUrl = `${app.forum.attribute('baseUrl')}/u/${user.slug()}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href={profileUrl}
|
||||||
|
title={extractText(app.translator.trans('core.admin.users.grid.columns.username.profile_link_tooltip', { username: user.username() }))}
|
||||||
|
>
|
||||||
|
{user.username()}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
90
|
||||||
|
);
|
||||||
|
|
||||||
|
columns.add(
|
||||||
|
'joinDate',
|
||||||
|
{
|
||||||
|
name: app.translator.trans('core.admin.users.grid.columns.join_time.title'),
|
||||||
|
content: (user: User) => (
|
||||||
|
<span class="UserList-joinDate" title={user.joinTime()}>
|
||||||
|
{dayjs(user.joinTime()).format('LLL')}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
80
|
||||||
|
);
|
||||||
|
|
||||||
|
columns.add(
|
||||||
|
'groupBadges',
|
||||||
|
{
|
||||||
|
name: app.translator.trans('core.admin.users.grid.columns.group_badges.title'),
|
||||||
|
content: (user: User) => {
|
||||||
|
const badges = user.badges().toArray();
|
||||||
|
|
||||||
|
if (badges.length) {
|
||||||
|
return <ul className="DiscussionHero-badges badges">{listItems(badges)}</ul>;
|
||||||
|
} else {
|
||||||
|
return app.translator.trans('core.admin.users.grid.columns.group_badges.no_badges');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
70
|
||||||
|
);
|
||||||
|
|
||||||
|
columns.add(
|
||||||
|
'emailAddress',
|
||||||
|
{
|
||||||
|
name: app.translator.trans('core.admin.users.grid.columns.email.title'),
|
||||||
|
content: (user: User) => {
|
||||||
|
function setEmailVisibility(visible: boolean) {
|
||||||
|
// Get needed jQuery element refs
|
||||||
|
const emailContainer = $(`[data-column-name=emailAddress][data-user-id=${user.id()}] .UserList-email`);
|
||||||
|
const emailAddress = emailContainer.find('.UserList-emailAddress');
|
||||||
|
const emailToggleButton = emailContainer.find('.UserList-emailIconBtn');
|
||||||
|
const emailToggleButtonIcon = emailToggleButton.find('.icon');
|
||||||
|
|
||||||
|
emailToggleButton.attr(
|
||||||
|
'title',
|
||||||
|
extractText(
|
||||||
|
visible
|
||||||
|
? app.translator.trans('core.admin.users.grid.columns.email.visibility_hide')
|
||||||
|
: app.translator.trans('core.admin.users.grid.columns.email.visibility_show')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
emailAddress.attr('aria-hidden', visible ? 'false' : 'true');
|
||||||
|
|
||||||
|
if (visible) {
|
||||||
|
emailToggleButtonIcon.addClass('fa-eye');
|
||||||
|
emailToggleButtonIcon.removeClass('fa-eye-slash');
|
||||||
|
} else {
|
||||||
|
emailToggleButtonIcon.removeClass('fa-eye');
|
||||||
|
emailToggleButtonIcon.addClass('fa-eye-slash');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need the string interpolation to prevent TS error.
|
||||||
|
emailContainer.attr('data-email-shown', `${visible}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleEmailVisibility() {
|
||||||
|
const emailContainer = $(`[data-column-name=emailAddress][data-user-id=${user.id()}] .UserList-email`);
|
||||||
|
const emailShown = emailContainer.attr('data-email-shown') === 'true';
|
||||||
|
|
||||||
|
if (emailShown) {
|
||||||
|
setEmailVisibility(false);
|
||||||
|
} else {
|
||||||
|
setEmailVisibility(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="UserList-email" key={user.id()} data-email-shown="false">
|
||||||
|
<span class="UserList-emailAddress" aria-hidden onclick={() => setEmailVisibility(true)}>
|
||||||
|
{user.email()}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
onclick={toggleEmailVisibility}
|
||||||
|
class="Button Button--text UserList-emailIconBtn"
|
||||||
|
title={app.translator.trans('core.admin.users.grid.columns.email.visibility_show')}
|
||||||
|
>
|
||||||
|
{icon('far fa-eye-slash fa-fw', { className: 'icon' })}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
70
|
||||||
|
);
|
||||||
|
|
||||||
|
columns.add(
|
||||||
|
'editUser',
|
||||||
|
{
|
||||||
|
name: app.translator.trans('core.admin.users.grid.columns.edit_user.title'),
|
||||||
|
content: (user: User) => (
|
||||||
|
<Button
|
||||||
|
className="Button UserList-editModalBtn"
|
||||||
|
title={app.translator.trans('core.admin.users.grid.columns.edit_user.tooltip', { username: user.username() })}
|
||||||
|
onclick={() => app.modal.show(EditUserModal, { user })}
|
||||||
|
>
|
||||||
|
{app.translator.trans('core.admin.users.grid.columns.edit_user.button')}
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
-90
|
||||||
|
);
|
||||||
|
|
||||||
|
return columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
headerInfo() {
|
||||||
|
return {
|
||||||
|
className: 'UserListPage',
|
||||||
|
icon: 'fas fa-users',
|
||||||
|
title: app.translator.trans('core.admin.users.title'),
|
||||||
|
description: app.translator.trans('core.admin.users.description'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asynchronously fetch the next set of users to be rendered.
|
||||||
|
*
|
||||||
|
* Returns an array of Users, plus the raw API payload.
|
||||||
|
*
|
||||||
|
* Uses the `this.numPerPage` as the response limit, and automatically calculates the offset required from `pageNumber`.
|
||||||
|
*
|
||||||
|
* @param pageNumber The page number to load and display
|
||||||
|
*/
|
||||||
|
async loadPage(pageNumber: number) {
|
||||||
|
if (pageNumber < 0) pageNumber = 0;
|
||||||
|
|
||||||
|
app.store
|
||||||
|
.find('users', {
|
||||||
|
page: {
|
||||||
|
limit: this.numPerPage,
|
||||||
|
offset: pageNumber * this.numPerPage,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((apiData: UsersApiResponse) => {
|
||||||
|
// Next link won't be present if there's no more data
|
||||||
|
this.moreData = !!apiData.payload.links.next;
|
||||||
|
|
||||||
|
let data = apiData;
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
delete data.payload;
|
||||||
|
|
||||||
|
this.pageData = data;
|
||||||
|
this.pageNumber = pageNumber;
|
||||||
|
this.isLoadingPage = false;
|
||||||
|
|
||||||
|
m.redraw();
|
||||||
|
})
|
||||||
|
.catch((err: Error) => {
|
||||||
|
console.error(err);
|
||||||
|
this.pageData = [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
nextPage() {
|
||||||
|
this.isLoadingPage = true;
|
||||||
|
this.loadPage(this.pageNumber + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
previousPage() {
|
||||||
|
this.isLoadingPage = true;
|
||||||
|
this.loadPage(this.pageNumber - 1);
|
||||||
|
}
|
||||||
|
}
|
@@ -3,3 +3,11 @@ import app from './app';
|
|||||||
export { app };
|
export { app };
|
||||||
|
|
||||||
// Export public API
|
// Export public API
|
||||||
|
|
||||||
|
// Export compat API
|
||||||
|
import compatObj from './compat';
|
||||||
|
import proxifyCompat from '../common/utils/proxifyCompat';
|
||||||
|
|
||||||
|
compatObj.app = app;
|
||||||
|
|
||||||
|
export const compat = proxifyCompat(compatObj, 'admin');
|
||||||
|
@@ -3,6 +3,7 @@ import BasicsPage from './components/BasicsPage';
|
|||||||
import PermissionsPage from './components/PermissionsPage';
|
import PermissionsPage from './components/PermissionsPage';
|
||||||
import AppearancePage from './components/AppearancePage';
|
import AppearancePage from './components/AppearancePage';
|
||||||
import MailPage from './components/MailPage';
|
import MailPage from './components/MailPage';
|
||||||
|
import UserListPage from './components/UserListPage';
|
||||||
import ExtensionPage from './components/ExtensionPage';
|
import ExtensionPage from './components/ExtensionPage';
|
||||||
import ExtensionPageResolver from './resolvers/ExtensionPageResolver';
|
import ExtensionPageResolver from './resolvers/ExtensionPageResolver';
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ export default function (app) {
|
|||||||
permissions: { path: '/permissions', component: PermissionsPage },
|
permissions: { path: '/permissions', component: PermissionsPage },
|
||||||
appearance: { path: '/appearance', component: AppearancePage },
|
appearance: { path: '/appearance', component: AppearancePage },
|
||||||
mail: { path: '/mail', component: MailPage },
|
mail: { path: '/mail', component: MailPage },
|
||||||
|
users: { path: '/users', component: UserListPage },
|
||||||
extension: { path: '/extension/:id', component: ExtensionPage, resolverClass: ExtensionPageResolver },
|
extension: { path: '/extension/:id', component: ExtensionPage, resolverClass: ExtensionPageResolver },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -12,6 +12,7 @@ import mapRoutes from './utils/mapRoutes';
|
|||||||
import RequestError from './utils/RequestError';
|
import RequestError from './utils/RequestError';
|
||||||
import ScrollListener from './utils/ScrollListener';
|
import ScrollListener from './utils/ScrollListener';
|
||||||
import liveHumanTimes from './utils/liveHumanTimes';
|
import liveHumanTimes from './utils/liveHumanTimes';
|
||||||
|
import patchMithril from './utils/patchMithril';
|
||||||
import { extend } from './extend';
|
import { extend } from './extend';
|
||||||
|
|
||||||
import Forum from './models/Forum';
|
import Forum from './models/Forum';
|
||||||
@@ -20,7 +21,6 @@ import Discussion from './models/Discussion';
|
|||||||
import Post from './models/Post';
|
import Post from './models/Post';
|
||||||
import Group from './models/Group';
|
import Group from './models/Group';
|
||||||
import Notification from './models/Notification';
|
import Notification from './models/Notification';
|
||||||
import { flattenDeep } from 'lodash-es';
|
|
||||||
import PageState from './states/PageState';
|
import PageState from './states/PageState';
|
||||||
import ModalManagerState from './states/ModalManagerState';
|
import ModalManagerState from './states/ModalManagerState';
|
||||||
import AlertManagerState from './states/AlertManagerState';
|
import AlertManagerState from './states/AlertManagerState';
|
||||||
@@ -163,10 +163,12 @@ export default class Application {
|
|||||||
|
|
||||||
load(payload) {
|
load(payload) {
|
||||||
this.data = payload;
|
this.data = payload;
|
||||||
this.translator.locale = payload.locale;
|
this.translator.setLocale(payload.locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
boot() {
|
boot() {
|
||||||
|
patchMithril(window);
|
||||||
|
|
||||||
this.initializers.toArray().forEach((initializer) => initializer(this));
|
this.initializers.toArray().forEach((initializer) => initializer(this));
|
||||||
|
|
||||||
this.store.pushPayload({ data: this.data.resources });
|
this.store.pushPayload({ data: this.data.resources });
|
||||||
@@ -180,11 +182,15 @@ export default class Application {
|
|||||||
this.initialRoute = window.location.href;
|
this.initialRoute = window.location.href;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: This entire system needs a do-over for v2
|
||||||
bootExtensions(extensions) {
|
bootExtensions(extensions) {
|
||||||
Object.keys(extensions).forEach((name) => {
|
Object.keys(extensions).forEach((name) => {
|
||||||
const extension = extensions[name];
|
const extension = extensions[name];
|
||||||
|
|
||||||
const extenders = flattenDeep(extension.extend);
|
// If an extension doesn't define extenders, there's nothing more to do here.
|
||||||
|
if (!extension.extend) return;
|
||||||
|
|
||||||
|
const extenders = extension.extend.flat(Infinity);
|
||||||
|
|
||||||
for (const extender of extenders) {
|
for (const extender of extenders) {
|
||||||
extender.extend(this, { name, exports: extension });
|
extender.extend(this, { name, exports: extension });
|
||||||
|
@@ -7,49 +7,54 @@ interface ExportRegistry {
|
|||||||
* Add an instance to the registry.
|
* Add an instance to the registry.
|
||||||
* This serves as the equivalent of `flarum.core.compat[id] = object`
|
* This serves as the equivalent of `flarum.core.compat[id] = object`
|
||||||
*/
|
*/
|
||||||
add(id: string, object: any);
|
add(namespace: string, id: string, object: any): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a function to run when object of id "id" is added (or overriden).
|
* Add a function to run when object of id "id" is added (or overriden).
|
||||||
* If such an object is already registered, the handler will be applied immediately.
|
* If such an object is already registered, the handler will be applied immediately.
|
||||||
*/
|
*/
|
||||||
onLoad(id: string, handler: Function);
|
onLoad(namespace: string, id: string, handler: Function): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve an object of type `id` from the registry.
|
* Retrieve an object of type `id` from the registry.
|
||||||
*/
|
*/
|
||||||
get(id: string): any;
|
get(namespace: string, id: string): any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class FlarumRegistry implements ExportRegistry {
|
export default class FlarumRegistry implements ExportRegistry {
|
||||||
moduleExports = {};
|
moduleExports = new Map<string, any>();
|
||||||
|
onLoads = new Map<string, Function[]>();
|
||||||
|
|
||||||
onLoads = {};
|
protected genKey(namespace: string, id: string): string {
|
||||||
|
return `${namespace};${id}`;
|
||||||
add(id: string, object: any) {
|
|
||||||
const onLoads = this.onLoads[id];
|
|
||||||
if (onLoads) {
|
|
||||||
onLoads.map((onLoad) => {
|
|
||||||
object = onLoad(object);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.moduleExports[id] = object;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onLoad(id: string, handler: Function) {
|
add(namespace: string, id: string, object: any) {
|
||||||
const loadedObject = this.moduleExports[id];
|
const key = this.genKey(namespace, id);
|
||||||
|
|
||||||
|
const onLoads = this.onLoads.get(key);
|
||||||
|
if (onLoads) {
|
||||||
|
onLoads.reduce((acc, handler) => handler(acc), object);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.moduleExports.set(key, object);
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoad(namespace: string, id: string, handler: Function) {
|
||||||
|
const key = this.genKey(namespace, id);
|
||||||
|
|
||||||
|
const loadedObject = this.moduleExports.get(key);
|
||||||
if (loadedObject) {
|
if (loadedObject) {
|
||||||
this.moduleExports[id] = handler(loadedObject);
|
this.moduleExports[id] = handler(loadedObject);
|
||||||
} else {
|
} else {
|
||||||
this.onLoads[id] = this.onLoads[id] || [];
|
const currOnLoads = this.onLoads.get(key);
|
||||||
this.onLoads[id].push(handler);
|
this.onLoads.set(key, [...(currOnLoads || []), handler]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get(id: string): any {
|
get(namespace: string, id: string): any {
|
||||||
if (this.moduleExports[id]) {
|
const key = this.genKey(namespace, id);
|
||||||
return this.moduleExports[id];
|
|
||||||
}
|
return this.moduleExports.get(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,13 +1,8 @@
|
|||||||
|
import { RichMessageFormatter, mithrilRichHandler } from '@askvortsov/rich-icu-message-formatter';
|
||||||
|
import { pluralTypeHandler, selectTypeHandler } from '@ultraq/icu-message-formatter';
|
||||||
import username from './helpers/username';
|
import username from './helpers/username';
|
||||||
import extract from './utils/extract';
|
import extract from './utils/extract';
|
||||||
|
|
||||||
/**
|
|
||||||
* Translator with the same API as Symfony's.
|
|
||||||
*
|
|
||||||
* Derived from https://github.com/willdurand/BazingaJsTranslationBundle
|
|
||||||
* which is available under the MIT License.
|
|
||||||
* Copyright (c) William Durand <william.durand1@gmail.com>
|
|
||||||
*/
|
|
||||||
export default class Translator {
|
export default class Translator {
|
||||||
constructor() {
|
constructor() {
|
||||||
/**
|
/**
|
||||||
@@ -18,288 +13,53 @@ export default class Translator {
|
|||||||
*/
|
*/
|
||||||
this.translations = {};
|
this.translations = {};
|
||||||
|
|
||||||
this.locale = null;
|
this.formatter = new RichMessageFormatter(null, this.formatterTypeHandlers(), mithrilRichHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
formatterTypeHandlers() {
|
||||||
|
return {
|
||||||
|
plural: pluralTypeHandler,
|
||||||
|
select: selectTypeHandler,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
setLocale(locale) {
|
||||||
|
this.formatter.locale = locale;
|
||||||
}
|
}
|
||||||
|
|
||||||
addTranslations(translations) {
|
addTranslations(translations) {
|
||||||
Object.assign(this.translations, translations);
|
Object.assign(this.translations, translations);
|
||||||
}
|
}
|
||||||
|
|
||||||
trans(id, parameters) {
|
preprocessParameters(parameters) {
|
||||||
const translation = this.translations[id];
|
|
||||||
|
|
||||||
if (translation) {
|
|
||||||
return this.apply(translation, parameters || {});
|
|
||||||
}
|
|
||||||
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
transChoice(id, number, parameters) {
|
|
||||||
let translation = this.translations[id];
|
|
||||||
|
|
||||||
if (translation) {
|
|
||||||
number = parseInt(number, 10);
|
|
||||||
|
|
||||||
translation = this.pluralize(translation, number);
|
|
||||||
|
|
||||||
return this.apply(translation, parameters || {});
|
|
||||||
}
|
|
||||||
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
apply(translation, input) {
|
|
||||||
// If we've been given a user model as one of the input parameters, then
|
// If we've been given a user model as one of the input parameters, then
|
||||||
// we'll extract the username and use that for the translation. In the
|
// we'll extract the username and use that for the translation. In the
|
||||||
// future there should be a hook here to inspect the user and change the
|
// future there should be a hook here to inspect the user and change the
|
||||||
// translation key. This will allow a gender property to determine which
|
// translation key. This will allow a gender property to determine which
|
||||||
// translation key is used.
|
// translation key is used.
|
||||||
if ('user' in input) {
|
if ('user' in parameters) {
|
||||||
const user = extract(input, 'user');
|
const user = extract(parameters, 'user');
|
||||||
|
|
||||||
if (!input.username) input.username = username(user);
|
if (!parameters.username) parameters.username = username(user);
|
||||||
}
|
}
|
||||||
|
return parameters;
|
||||||
translation = translation.split(new RegExp('({[a-z0-9_]+}|</?[a-z0-9_]+>)', 'gi'));
|
|
||||||
|
|
||||||
const hydrated = [];
|
|
||||||
const open = [hydrated];
|
|
||||||
|
|
||||||
translation.forEach((part) => {
|
|
||||||
const match = part.match(new RegExp('{([a-z0-9_]+)}|<(/?)([a-z0-9_]+)>', 'i'));
|
|
||||||
|
|
||||||
if (match) {
|
|
||||||
// Either an opening or closing tag.
|
|
||||||
if (match[1]) {
|
|
||||||
open[0].push(input[match[1]]);
|
|
||||||
} else if (match[3]) {
|
|
||||||
if (match[2]) {
|
|
||||||
// Closing tag. We start by removing all raw children (generally in the form of strings) from the temporary
|
|
||||||
// holding array, then run them through m.fragment to convert them to vnodes. Usually this will just give us a
|
|
||||||
// text vnode, but using m.fragment as opposed to an explicit conversion should be more flexible. This is necessary because
|
|
||||||
// otherwise, our generated vnode will have raw strings as its children, and mithril expects vnodes.
|
|
||||||
// Finally, we add the now-processed vnodes back onto the holding array (which is the same object in memory as the
|
|
||||||
// children array of the vnode we are currently processing), and remove the reference to the holding array so that
|
|
||||||
// further text will be added to the full set of returned elements.
|
|
||||||
const rawChildren = open[0].splice(0, open[0].length);
|
|
||||||
open[0].push(...m.fragment(rawChildren).children);
|
|
||||||
open.shift();
|
|
||||||
} else {
|
|
||||||
// If a vnode with a matching tag was provided in the translator input, we use that. Otherwise, we create a new vnode
|
|
||||||
// with this tag, and an empty children array (since we're expecting to insert children, as that's the point of having this in translator)
|
|
||||||
let tag = input[match[3]] || { tag: match[3], children: [] };
|
|
||||||
open[0].push(tag);
|
|
||||||
// Insert the tag's children array as the first element of open, so that text in between the opening
|
|
||||||
// and closing tags will be added to the tag's children, not to the full set of returned elements.
|
|
||||||
open.unshift(tag.children || tag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Not an html tag, we add it to open[0], which is either the full set of returned elements (vnodes and text),
|
|
||||||
// or if an html tag is currently being processed, the children attribute of that html tag's vnode.
|
|
||||||
open[0].push(part);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return hydrated.filter((part) => part);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pluralize(translation, number) {
|
trans(id, parameters) {
|
||||||
const sPluralRegex = new RegExp(/^\w+\: +(.+)$/),
|
const translation = this.translations[id];
|
||||||
cPluralRegex = new RegExp(/^\s*((\{\s*(\-?\d+[\s*,\s*\-?\d+]*)\s*\})|([\[\]])\s*(-Inf|\-?\d+)\s*,\s*(\+?Inf|\-?\d+)\s*([\[\]]))\s?(.+?)$/),
|
|
||||||
iPluralRegex = new RegExp(/^\s*(\{\s*(\-?\d+[\s*,\s*\-?\d+]*)\s*\})|([\[\]])\s*(-Inf|\-?\d+)\s*,\s*(\+?Inf|\-?\d+)\s*([\[\]])/),
|
|
||||||
standardRules = [],
|
|
||||||
explicitRules = [];
|
|
||||||
|
|
||||||
translation.split('|').forEach((part) => {
|
if (translation) {
|
||||||
if (cPluralRegex.test(part)) {
|
parameters = this.preprocessParameters(parameters || {});
|
||||||
const matches = part.match(cPluralRegex);
|
return this.formatter.rich(translation, parameters);
|
||||||
explicitRules[matches[0]] = matches[matches.length - 1];
|
}
|
||||||
} else if (sPluralRegex.test(part)) {
|
|
||||||
const matches = part.match(sPluralRegex);
|
|
||||||
standardRules.push(matches[1]);
|
|
||||||
} else {
|
|
||||||
standardRules.push(part);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
explicitRules.forEach((rule, e) => {
|
return id;
|
||||||
if (iPluralRegex.test(e)) {
|
|
||||||
const matches = e.match(iPluralRegex);
|
|
||||||
|
|
||||||
if (matches[1]) {
|
|
||||||
const ns = matches[2].split(',');
|
|
||||||
|
|
||||||
for (let n in ns) {
|
|
||||||
if (number == ns[n]) {
|
|
||||||
return explicitRules[e];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var leftNumber = this.convertNumber(matches[4]);
|
|
||||||
var rightNumber = this.convertNumber(matches[5]);
|
|
||||||
|
|
||||||
if (
|
|
||||||
('[' === matches[3] ? number >= leftNumber : number > leftNumber) &&
|
|
||||||
(']' === matches[6] ? number <= rightNumber : number < rightNumber)
|
|
||||||
) {
|
|
||||||
return explicitRules[e];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return standardRules[this.pluralPosition(number, this.locale)] || standardRules[0] || undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
convertNumber(number) {
|
/**
|
||||||
if ('-Inf' === number) {
|
* @deprecated, remove before stable
|
||||||
return Number.NEGATIVE_INFINITY;
|
*/
|
||||||
} else if ('+Inf' === number || 'Inf' === number) {
|
transChoice(id, number, parameters) {
|
||||||
return Number.POSITIVE_INFINITY;
|
return this.trans(id, parameters);
|
||||||
}
|
|
||||||
|
|
||||||
return parseInt(number, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
pluralPosition(number, locale) {
|
|
||||||
if ('pt_BR' === locale) {
|
|
||||||
locale = 'xbr';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (locale.length > 3) {
|
|
||||||
locale = locale.split('_')[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (locale) {
|
|
||||||
case 'bo':
|
|
||||||
case 'dz':
|
|
||||||
case 'id':
|
|
||||||
case 'ja':
|
|
||||||
case 'jv':
|
|
||||||
case 'ka':
|
|
||||||
case 'km':
|
|
||||||
case 'kn':
|
|
||||||
case 'ko':
|
|
||||||
case 'ms':
|
|
||||||
case 'th':
|
|
||||||
case 'vi':
|
|
||||||
case 'zh':
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
case 'af':
|
|
||||||
case 'az':
|
|
||||||
case 'bn':
|
|
||||||
case 'bg':
|
|
||||||
case 'ca':
|
|
||||||
case 'da':
|
|
||||||
case 'de':
|
|
||||||
case 'el':
|
|
||||||
case 'en':
|
|
||||||
case 'eo':
|
|
||||||
case 'es':
|
|
||||||
case 'et':
|
|
||||||
case 'eu':
|
|
||||||
case 'fa':
|
|
||||||
case 'fi':
|
|
||||||
case 'fo':
|
|
||||||
case 'fur':
|
|
||||||
case 'fy':
|
|
||||||
case 'gl':
|
|
||||||
case 'gu':
|
|
||||||
case 'ha':
|
|
||||||
case 'he':
|
|
||||||
case 'hu':
|
|
||||||
case 'is':
|
|
||||||
case 'it':
|
|
||||||
case 'ku':
|
|
||||||
case 'lb':
|
|
||||||
case 'ml':
|
|
||||||
case 'mn':
|
|
||||||
case 'mr':
|
|
||||||
case 'nah':
|
|
||||||
case 'nb':
|
|
||||||
case 'ne':
|
|
||||||
case 'nl':
|
|
||||||
case 'nn':
|
|
||||||
case 'no':
|
|
||||||
case 'om':
|
|
||||||
case 'or':
|
|
||||||
case 'pa':
|
|
||||||
case 'pap':
|
|
||||||
case 'ps':
|
|
||||||
case 'pt':
|
|
||||||
case 'so':
|
|
||||||
case 'sq':
|
|
||||||
case 'sv':
|
|
||||||
case 'sw':
|
|
||||||
case 'ta':
|
|
||||||
case 'te':
|
|
||||||
case 'tk':
|
|
||||||
case 'tr':
|
|
||||||
case 'ur':
|
|
||||||
case 'zu':
|
|
||||||
return number == 1 ? 0 : 1;
|
|
||||||
|
|
||||||
case 'am':
|
|
||||||
case 'bh':
|
|
||||||
case 'fil':
|
|
||||||
case 'fr':
|
|
||||||
case 'gun':
|
|
||||||
case 'hi':
|
|
||||||
case 'ln':
|
|
||||||
case 'mg':
|
|
||||||
case 'nso':
|
|
||||||
case 'xbr':
|
|
||||||
case 'ti':
|
|
||||||
case 'wa':
|
|
||||||
return number === 0 || number == 1 ? 0 : 1;
|
|
||||||
|
|
||||||
case 'be':
|
|
||||||
case 'bs':
|
|
||||||
case 'hr':
|
|
||||||
case 'ru':
|
|
||||||
case 'sr':
|
|
||||||
case 'uk':
|
|
||||||
return number % 10 == 1 && number % 100 != 11 ? 0 : number % 10 >= 2 && number % 10 <= 4 && (number % 100 < 10 || number % 100 >= 20) ? 1 : 2;
|
|
||||||
|
|
||||||
case 'cs':
|
|
||||||
case 'sk':
|
|
||||||
return number == 1 ? 0 : number >= 2 && number <= 4 ? 1 : 2;
|
|
||||||
|
|
||||||
case 'ga':
|
|
||||||
return number == 1 ? 0 : number == 2 ? 1 : 2;
|
|
||||||
|
|
||||||
case 'lt':
|
|
||||||
return number % 10 == 1 && number % 100 != 11 ? 0 : number % 10 >= 2 && (number % 100 < 10 || number % 100 >= 20) ? 1 : 2;
|
|
||||||
|
|
||||||
case 'sl':
|
|
||||||
return number % 100 == 1 ? 0 : number % 100 == 2 ? 1 : number % 100 == 3 || number % 100 == 4 ? 2 : 3;
|
|
||||||
|
|
||||||
case 'mk':
|
|
||||||
return number % 10 == 1 ? 0 : 1;
|
|
||||||
|
|
||||||
case 'mt':
|
|
||||||
return number == 1 ? 0 : number === 0 || (number % 100 > 1 && number % 100 < 11) ? 1 : number % 100 > 10 && number % 100 < 20 ? 2 : 3;
|
|
||||||
|
|
||||||
case 'lv':
|
|
||||||
return number === 0 ? 0 : number % 10 == 1 && number % 100 != 11 ? 1 : 2;
|
|
||||||
|
|
||||||
case 'pl':
|
|
||||||
return number == 1 ? 0 : number % 10 >= 2 && number % 10 <= 4 && (number % 100 < 12 || number % 100 > 14) ? 1 : 2;
|
|
||||||
|
|
||||||
case 'cy':
|
|
||||||
return number == 1 ? 0 : number == 2 ? 1 : number == 8 || number == 11 ? 2 : 3;
|
|
||||||
|
|
||||||
case 'ro':
|
|
||||||
return number == 1 ? 0 : number === 0 || (number % 100 > 0 && number % 100 < 20) ? 1 : 2;
|
|
||||||
|
|
||||||
case 'ar':
|
|
||||||
return number === 0 ? 0 : number == 1 ? 1 : number == 2 ? 2 : number >= 3 && number <= 10 ? 3 : number >= 11 && number <= 99 ? 4 : 5;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
157
js/src/common/compat.js
Normal file
157
js/src/common/compat.js
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
import * as extend from './extend';
|
||||||
|
import Session from './Session';
|
||||||
|
import Store from './Store';
|
||||||
|
import BasicEditorDriver from './utils/BasicEditorDriver';
|
||||||
|
import evented from './utils/evented';
|
||||||
|
import liveHumanTimes from './utils/liveHumanTimes';
|
||||||
|
import ItemList from './utils/ItemList';
|
||||||
|
import mixin from './utils/mixin';
|
||||||
|
import humanTime from './utils/humanTime';
|
||||||
|
import computed from './utils/computed';
|
||||||
|
import Drawer from './utils/Drawer';
|
||||||
|
import anchorScroll from './utils/anchorScroll';
|
||||||
|
import RequestError from './utils/RequestError';
|
||||||
|
import abbreviateNumber from './utils/abbreviateNumber';
|
||||||
|
import escapeRegExp from './utils/escapeRegExp';
|
||||||
|
import * as string from './utils/string';
|
||||||
|
import * as ThrottleDebounce from './utils/throttleDebounce';
|
||||||
|
import Stream from './utils/Stream';
|
||||||
|
import SubtreeRetainer from './utils/SubtreeRetainer';
|
||||||
|
import setRouteWithForcedRefresh from './utils/setRouteWithForcedRefresh';
|
||||||
|
import extract from './utils/extract';
|
||||||
|
import ScrollListener from './utils/ScrollListener';
|
||||||
|
import stringToColor from './utils/stringToColor';
|
||||||
|
import subclassOf from './utils/subclassOf';
|
||||||
|
import patchMithril from './utils/patchMithril';
|
||||||
|
import proxifyCompat from './utils/proxifyCompat';
|
||||||
|
import classList from './utils/classList';
|
||||||
|
import extractText from './utils/extractText';
|
||||||
|
import formatNumber from './utils/formatNumber';
|
||||||
|
import mapRoutes from './utils/mapRoutes';
|
||||||
|
import withAttr from './utils/withAttr';
|
||||||
|
import Notification from './models/Notification';
|
||||||
|
import User from './models/User';
|
||||||
|
import Post from './models/Post';
|
||||||
|
import Discussion from './models/Discussion';
|
||||||
|
import Group from './models/Group';
|
||||||
|
import Forum from './models/Forum';
|
||||||
|
import Component from './Component';
|
||||||
|
import Translator from './Translator';
|
||||||
|
import AlertManager from './components/AlertManager';
|
||||||
|
import Page from './components/Page';
|
||||||
|
import Switch from './components/Switch';
|
||||||
|
import Badge from './components/Badge';
|
||||||
|
import LoadingIndicator from './components/LoadingIndicator';
|
||||||
|
import Placeholder from './components/Placeholder';
|
||||||
|
import Separator from './components/Separator';
|
||||||
|
import Dropdown from './components/Dropdown';
|
||||||
|
import SplitDropdown from './components/SplitDropdown';
|
||||||
|
import RequestErrorModal from './components/RequestErrorModal';
|
||||||
|
import FieldSet from './components/FieldSet';
|
||||||
|
import Select from './components/Select';
|
||||||
|
import Navigation from './components/Navigation';
|
||||||
|
import Alert from './components/Alert';
|
||||||
|
import Link from './components/Link';
|
||||||
|
import LinkButton from './components/LinkButton';
|
||||||
|
import Checkbox from './components/Checkbox';
|
||||||
|
import SelectDropdown from './components/SelectDropdown';
|
||||||
|
import ModalManager from './components/ModalManager';
|
||||||
|
import Button from './components/Button';
|
||||||
|
import Modal from './components/Modal';
|
||||||
|
import GroupBadge from './components/GroupBadge';
|
||||||
|
import TextEditor from './components/TextEditor';
|
||||||
|
import TextEditorButton from './components/TextEditorButton';
|
||||||
|
import EditUserModal from './components/EditUserModal';
|
||||||
|
import Model from './Model';
|
||||||
|
import Application from './Application';
|
||||||
|
import fullTime from './helpers/fullTime';
|
||||||
|
import avatar from './helpers/avatar';
|
||||||
|
import icon from './helpers/icon';
|
||||||
|
import humanTimeHelper from './helpers/humanTime';
|
||||||
|
import punctuateSeries from './helpers/punctuateSeries';
|
||||||
|
import highlight from './helpers/highlight';
|
||||||
|
import username from './helpers/username';
|
||||||
|
import userOnline from './helpers/userOnline';
|
||||||
|
import listItems from './helpers/listItems';
|
||||||
|
import Fragment from './Fragment';
|
||||||
|
import DefaultResolver from './resolvers/DefaultResolver';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
extend: extend,
|
||||||
|
Session: Session,
|
||||||
|
Store: Store,
|
||||||
|
'utils/BasicEditorDriver': BasicEditorDriver,
|
||||||
|
'utils/evented': evented,
|
||||||
|
'utils/liveHumanTimes': liveHumanTimes,
|
||||||
|
'utils/ItemList': ItemList,
|
||||||
|
'utils/mixin': mixin,
|
||||||
|
'utils/humanTime': humanTime,
|
||||||
|
'utils/computed': computed,
|
||||||
|
'utils/Drawer': Drawer,
|
||||||
|
'utils/anchorScroll': anchorScroll,
|
||||||
|
'utils/RequestError': RequestError,
|
||||||
|
'utils/abbreviateNumber': abbreviateNumber,
|
||||||
|
'utils/string': string,
|
||||||
|
'utils/SubtreeRetainer': SubtreeRetainer,
|
||||||
|
'utils/escapeRegExp': escapeRegExp,
|
||||||
|
'utils/extract': extract,
|
||||||
|
'utils/ScrollListener': ScrollListener,
|
||||||
|
'utils/stringToColor': stringToColor,
|
||||||
|
'utils/Stream': Stream,
|
||||||
|
'utils/subclassOf': subclassOf,
|
||||||
|
'utils/setRouteWithForcedRefresh': setRouteWithForcedRefresh,
|
||||||
|
'utils/patchMithril': patchMithril,
|
||||||
|
'utils/proxifyCompat': proxifyCompat,
|
||||||
|
'utils/classList': classList,
|
||||||
|
'utils/extractText': extractText,
|
||||||
|
'utils/formatNumber': formatNumber,
|
||||||
|
'utils/mapRoutes': mapRoutes,
|
||||||
|
'utils/withAttr': withAttr,
|
||||||
|
'utils/throttleDebounce': ThrottleDebounce,
|
||||||
|
'models/Notification': Notification,
|
||||||
|
'models/User': User,
|
||||||
|
'models/Post': Post,
|
||||||
|
'models/Discussion': Discussion,
|
||||||
|
'models/Group': Group,
|
||||||
|
'models/Forum': Forum,
|
||||||
|
Component: Component,
|
||||||
|
Fragment: Fragment,
|
||||||
|
Translator: Translator,
|
||||||
|
'components/AlertManager': AlertManager,
|
||||||
|
'components/Page': Page,
|
||||||
|
'components/Switch': Switch,
|
||||||
|
'components/Badge': Badge,
|
||||||
|
'components/LoadingIndicator': LoadingIndicator,
|
||||||
|
'components/Placeholder': Placeholder,
|
||||||
|
'components/Separator': Separator,
|
||||||
|
'components/Dropdown': Dropdown,
|
||||||
|
'components/SplitDropdown': SplitDropdown,
|
||||||
|
'components/RequestErrorModal': RequestErrorModal,
|
||||||
|
'components/FieldSet': FieldSet,
|
||||||
|
'components/Select': Select,
|
||||||
|
'components/Navigation': Navigation,
|
||||||
|
'components/Alert': Alert,
|
||||||
|
'components/Link': Link,
|
||||||
|
'components/LinkButton': LinkButton,
|
||||||
|
'components/Checkbox': Checkbox,
|
||||||
|
'components/SelectDropdown': SelectDropdown,
|
||||||
|
'components/ModalManager': ModalManager,
|
||||||
|
'components/Button': Button,
|
||||||
|
'components/Modal': Modal,
|
||||||
|
'components/GroupBadge': GroupBadge,
|
||||||
|
'components/TextEditor': TextEditor,
|
||||||
|
'components/TextEditorButton': TextEditorButton,
|
||||||
|
'components/EditUserModal': EditUserModal,
|
||||||
|
Model: Model,
|
||||||
|
Application: Application,
|
||||||
|
'helpers/fullTime': fullTime,
|
||||||
|
'helpers/avatar': avatar,
|
||||||
|
'helpers/icon': icon,
|
||||||
|
'helpers/humanTime': humanTimeHelper,
|
||||||
|
'helpers/punctuateSeries': punctuateSeries,
|
||||||
|
'helpers/highlight': highlight,
|
||||||
|
'helpers/username': username,
|
||||||
|
'helpers/userOnline': userOnline,
|
||||||
|
'helpers/listItems': listItems,
|
||||||
|
'resolvers/DefaultResolver': DefaultResolver,
|
||||||
|
};
|
@@ -69,7 +69,7 @@ export default class Button extends Component {
|
|||||||
return [
|
return [
|
||||||
iconName && iconName !== true ? icon(iconName, { className: 'Button-icon' }) : '',
|
iconName && iconName !== true ? icon(iconName, { className: 'Button-icon' }) : '',
|
||||||
children ? <span className="Button-label">{children}</span> : '',
|
children ? <span className="Button-label">{children}</span> : '',
|
||||||
this.attrs.loading ? <LoadingIndicator size="tiny" className="LoadingIndicator--inline" /> : '',
|
this.attrs.loading ? <LoadingIndicator size="small" display="inline" /> : '',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -46,7 +46,7 @@ export default class Checkbox extends Component {
|
|||||||
* @protected
|
* @protected
|
||||||
*/
|
*/
|
||||||
getDisplay() {
|
getDisplay() {
|
||||||
return this.attrs.loading ? <LoadingIndicator size="tiny" /> : icon(this.attrs.state ? 'fas fa-check' : 'fas fa-times');
|
return this.attrs.loading ? <LoadingIndicator display="unset" size="small" /> : icon(this.attrs.state ? 'fas fa-check' : 'fas fa-times');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
import Modal from '../../common/components/Modal';
|
import Modal from './Modal';
|
||||||
import Button from '../../common/components/Button';
|
import Button from './Button';
|
||||||
import GroupBadge from '../../common/components/GroupBadge';
|
import GroupBadge from './GroupBadge';
|
||||||
import Group from '../../common/models/Group';
|
import Group from '../models/Group';
|
||||||
import extractText from '../../common/utils/extractText';
|
import extractText from '../utils/extractText';
|
||||||
import ItemList from '../../common/utils/ItemList';
|
import ItemList from '../utils/ItemList';
|
||||||
import Stream from '../../common/utils/Stream';
|
import Stream from '../utils/Stream';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `EditUserModal` component displays a modal dialog with a login form.
|
* The `EditUserModal` component displays a modal dialog with a login form.
|
||||||
@@ -33,14 +33,14 @@ export default class EditUserModal extends Modal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
title() {
|
title() {
|
||||||
return app.translator.trans('core.forum.edit_user.title');
|
return app.translator.trans('core.lib.edit_user.title');
|
||||||
}
|
}
|
||||||
|
|
||||||
content() {
|
content() {
|
||||||
const fields = this.fields().toArray();
|
const fields = this.fields().toArray();
|
||||||
return (
|
return (
|
||||||
<div className="Modal-body">
|
<div className="Modal-body">
|
||||||
{fields.length > 1 ? <div className="Form">{this.fields().toArray()}</div> : app.translator.trans('core.forum.edit_user.nothing_available')}
|
{fields.length > 1 ? <div className="Form">{this.fields().toArray()}</div> : app.translator.trans('core.lib.edit_user.nothing_available')}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -52,10 +52,10 @@ export default class EditUserModal extends Modal {
|
|||||||
items.add(
|
items.add(
|
||||||
'username',
|
'username',
|
||||||
<div className="Form-group">
|
<div className="Form-group">
|
||||||
<label>{app.translator.trans('core.forum.edit_user.username_heading')}</label>
|
<label>{app.translator.trans('core.lib.edit_user.username_heading')}</label>
|
||||||
<input
|
<input
|
||||||
className="FormControl"
|
className="FormControl"
|
||||||
placeholder={extractText(app.translator.trans('core.forum.edit_user.username_label'))}
|
placeholder={extractText(app.translator.trans('core.lib.edit_user.username_label'))}
|
||||||
bidi={this.username}
|
bidi={this.username}
|
||||||
disabled={this.nonAdminEditingAdmin()}
|
disabled={this.nonAdminEditingAdmin()}
|
||||||
/>
|
/>
|
||||||
@@ -67,11 +67,11 @@ export default class EditUserModal extends Modal {
|
|||||||
items.add(
|
items.add(
|
||||||
'email',
|
'email',
|
||||||
<div className="Form-group">
|
<div className="Form-group">
|
||||||
<label>{app.translator.trans('core.forum.edit_user.email_heading')}</label>
|
<label>{app.translator.trans('core.lib.edit_user.email_heading')}</label>
|
||||||
<div>
|
<div>
|
||||||
<input
|
<input
|
||||||
className="FormControl"
|
className="FormControl"
|
||||||
placeholder={extractText(app.translator.trans('core.forum.edit_user.email_label'))}
|
placeholder={extractText(app.translator.trans('core.lib.edit_user.email_label'))}
|
||||||
bidi={this.email}
|
bidi={this.email}
|
||||||
disabled={this.nonAdminEditingAdmin()}
|
disabled={this.nonAdminEditingAdmin()}
|
||||||
/>
|
/>
|
||||||
@@ -84,7 +84,7 @@ export default class EditUserModal extends Modal {
|
|||||||
loading: this.loading,
|
loading: this.loading,
|
||||||
onclick: this.activate.bind(this),
|
onclick: this.activate.bind(this),
|
||||||
},
|
},
|
||||||
app.translator.trans('core.forum.edit_user.activate_button')
|
app.translator.trans('core.lib.edit_user.activate_button')
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -97,7 +97,7 @@ export default class EditUserModal extends Modal {
|
|||||||
items.add(
|
items.add(
|
||||||
'password',
|
'password',
|
||||||
<div className="Form-group">
|
<div className="Form-group">
|
||||||
<label>{app.translator.trans('core.forum.edit_user.password_heading')}</label>
|
<label>{app.translator.trans('core.lib.edit_user.password_heading')}</label>
|
||||||
<div>
|
<div>
|
||||||
<label className="checkbox">
|
<label className="checkbox">
|
||||||
<input
|
<input
|
||||||
@@ -110,14 +110,14 @@ export default class EditUserModal extends Modal {
|
|||||||
}}
|
}}
|
||||||
disabled={this.nonAdminEditingAdmin()}
|
disabled={this.nonAdminEditingAdmin()}
|
||||||
/>
|
/>
|
||||||
{app.translator.trans('core.forum.edit_user.set_password_label')}
|
{app.translator.trans('core.lib.edit_user.set_password_label')}
|
||||||
</label>
|
</label>
|
||||||
{this.setPassword() ? (
|
{this.setPassword() ? (
|
||||||
<input
|
<input
|
||||||
className="FormControl"
|
className="FormControl"
|
||||||
type="password"
|
type="password"
|
||||||
name="password"
|
name="password"
|
||||||
placeholder={extractText(app.translator.trans('core.forum.edit_user.password_label'))}
|
placeholder={extractText(app.translator.trans('core.lib.edit_user.password_label'))}
|
||||||
bidi={this.password}
|
bidi={this.password}
|
||||||
disabled={this.nonAdminEditingAdmin()}
|
disabled={this.nonAdminEditingAdmin()}
|
||||||
/>
|
/>
|
||||||
@@ -135,7 +135,7 @@ export default class EditUserModal extends Modal {
|
|||||||
items.add(
|
items.add(
|
||||||
'groups',
|
'groups',
|
||||||
<div className="Form-group EditUserModal-groups">
|
<div className="Form-group EditUserModal-groups">
|
||||||
<label>{app.translator.trans('core.forum.edit_user.groups_heading')}</label>
|
<label>{app.translator.trans('core.lib.edit_user.groups_heading')}</label>
|
||||||
<div>
|
<div>
|
||||||
{Object.keys(this.groups)
|
{Object.keys(this.groups)
|
||||||
.map((id) => app.store.getById('groups', id))
|
.map((id) => app.store.getById('groups', id))
|
||||||
@@ -164,7 +164,7 @@ export default class EditUserModal extends Modal {
|
|||||||
type: 'submit',
|
type: 'submit',
|
||||||
loading: this.loading,
|
loading: this.loading,
|
||||||
},
|
},
|
||||||
app.translator.trans('core.forum.edit_user.submit_button')
|
app.translator.trans('core.lib.edit_user.submit_button')
|
||||||
)}
|
)}
|
||||||
</div>,
|
</div>,
|
||||||
-10
|
-10
|
@@ -1,43 +0,0 @@
|
|||||||
import Component from '../Component';
|
|
||||||
import { Spinner } from 'spin.js';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The `LoadingIndicator` component displays a loading spinner with spin.js.
|
|
||||||
*
|
|
||||||
* ### Attrs
|
|
||||||
*
|
|
||||||
* - `size` The spin.js size preset to use. Defaults to 'small'.
|
|
||||||
*
|
|
||||||
* All other attrs will be assigned as attributes on the DOM element.
|
|
||||||
*/
|
|
||||||
export default class LoadingIndicator extends Component {
|
|
||||||
view() {
|
|
||||||
const attrs = Object.assign({}, this.attrs);
|
|
||||||
|
|
||||||
attrs.className = 'LoadingIndicator ' + (attrs.className || '');
|
|
||||||
delete attrs.size;
|
|
||||||
|
|
||||||
return <div {...attrs}>{m.trust(' ')}</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
oncreate(vnode) {
|
|
||||||
super.oncreate(vnode);
|
|
||||||
|
|
||||||
const options = { zIndex: 'auto', color: this.$().css('color') };
|
|
||||||
|
|
||||||
switch (this.attrs.size) {
|
|
||||||
case 'large':
|
|
||||||
Object.assign(options, { lines: 10, length: 8, width: 4, radius: 8 });
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'tiny':
|
|
||||||
Object.assign(options, { lines: 8, length: 2, width: 2, radius: 3 });
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
Object.assign(options, { lines: 8, length: 4, width: 3, radius: 5 });
|
|
||||||
}
|
|
||||||
|
|
||||||
new Spinner(options).spin(this.element);
|
|
||||||
}
|
|
||||||
}
|
|
80
js/src/common/components/LoadingIndicator.tsx
Normal file
80
js/src/common/components/LoadingIndicator.tsx
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import Component, { ComponentAttrs } from '../Component';
|
||||||
|
import classList from '../utils/classList';
|
||||||
|
|
||||||
|
export interface LoadingIndicatorAttrs extends ComponentAttrs {
|
||||||
|
/**
|
||||||
|
* Custom classes fro the loading indicator's container.
|
||||||
|
*/
|
||||||
|
className?: string;
|
||||||
|
/**
|
||||||
|
* Custom classes for the loading indicator's container.
|
||||||
|
*/
|
||||||
|
containerClassName?: string;
|
||||||
|
/**
|
||||||
|
* Optional size to specify for the loading indicator.
|
||||||
|
*/
|
||||||
|
size?: 'large' | 'medium' | 'small';
|
||||||
|
/**
|
||||||
|
* Optional attributes to apply to the loading indicator's container.
|
||||||
|
*/
|
||||||
|
containerAttrs?: Partial<ComponentAttrs>;
|
||||||
|
/**
|
||||||
|
* Display type of the spinner.
|
||||||
|
*
|
||||||
|
* @default 'block'
|
||||||
|
*/
|
||||||
|
display?: 'block' | 'inline' | 'unset';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `LoadingIndicator` component displays a simple CSS-based loading spinner.
|
||||||
|
*
|
||||||
|
* To set a custom color, use the CSS `color` property.
|
||||||
|
*
|
||||||
|
* To increase spacing around the spinner, use the CSS `height` property on the
|
||||||
|
* spinner's **container**. Setting the `display` attribute to `block` will set
|
||||||
|
* a height of `100px` by default.
|
||||||
|
*
|
||||||
|
* To apply a custom size to the loading indicator, set the `--size` and
|
||||||
|
* `--thickness` CSS custom properties on the loading indicator container.
|
||||||
|
*
|
||||||
|
* If you *really* want to change how this looks as part of your custom theme,
|
||||||
|
* you can override the `border-radius` and `border` then set either a
|
||||||
|
* background image, or use `content: "\<glyph>"` (e.g. `content: "\f1ce"`)
|
||||||
|
* and `font-family: 'Font Awesome 5 Free'` to set an FA icon if you'd rather.
|
||||||
|
*
|
||||||
|
* ### Attrs
|
||||||
|
*
|
||||||
|
* - `containerClassName` Class name(s) to apply to the indicator's parent
|
||||||
|
* - `className` Class name(s) to apply to the indicator itself
|
||||||
|
* - `display` Determines how the spinner should be displayed (`inline`, `block` (default) or `unset`)
|
||||||
|
* - `size` Size of the loading indicator (`small`, `medium` or `large`)
|
||||||
|
* - `containerAttrs` Optional attrs to be applied to the container DOM element
|
||||||
|
*
|
||||||
|
* All other attrs will be assigned as attributes on the DOM element.
|
||||||
|
*/
|
||||||
|
export default class LoadingIndicator extends Component<LoadingIndicatorAttrs> {
|
||||||
|
view() {
|
||||||
|
const { display = 'block', size = 'medium', containerClassName, className, ...attrs } = this.attrs;
|
||||||
|
|
||||||
|
const completeClassName = classList('LoadingIndicator', className);
|
||||||
|
const completeContainerClassName = classList(
|
||||||
|
'LoadingIndicator-container',
|
||||||
|
display !== 'unset' && `LoadingIndicator-container--${display}`,
|
||||||
|
size && `LoadingIndicator-container--${size}`,
|
||||||
|
containerClassName
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
aria-label={app.translator.trans('core.lib.loading_indicator.accessible_label')}
|
||||||
|
role="status"
|
||||||
|
{...attrs.containerAttrs}
|
||||||
|
data-size={size}
|
||||||
|
className={completeContainerClassName}
|
||||||
|
>
|
||||||
|
<div aria-hidden className={completeClassName} {...attrs} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -9,27 +9,36 @@
|
|||||||
* Care should be taken to extend the correct object – in most cases, a class'
|
* Care should be taken to extend the correct object – in most cases, a class'
|
||||||
* prototype will be the desired target of extension, not the class itself.
|
* prototype will be the desired target of extension, not the class itself.
|
||||||
*
|
*
|
||||||
* @example
|
* @example <caption>Example usage of extending one method.</caption>
|
||||||
* extend(Discussion.prototype, 'badges', function(badges) {
|
* extend(Discussion.prototype, 'badges', function(badges) {
|
||||||
* // do something with `badges`
|
* // do something with `badges`
|
||||||
* });
|
* });
|
||||||
*
|
*
|
||||||
* @param {Object} object The object that owns the method
|
* @example <caption>Example usage of extending multiple methods.</caption>
|
||||||
* @param {String} method The name of the method to extend
|
* extend(IndexPage.prototype, ['oncreate', 'onupdate'], function(vnode) {
|
||||||
|
* // something that needs to be run on creation and update
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* @param {object} object The object that owns the method
|
||||||
|
* @param {string|string[]} methods The name or names of the method(s) to extend
|
||||||
* @param {function} callback A callback which mutates the method's output
|
* @param {function} callback A callback which mutates the method's output
|
||||||
*/
|
*/
|
||||||
export function extend(object, method, callback) {
|
export function extend(object, methods, callback) {
|
||||||
const original = object[method];
|
const allMethods = Array.isArray(methods) ? methods : [methods];
|
||||||
|
|
||||||
object[method] = function (...args) {
|
allMethods.forEach((method) => {
|
||||||
const value = original ? original.apply(this, args) : undefined;
|
const original = object[method];
|
||||||
|
|
||||||
callback.apply(this, [value].concat(args));
|
object[method] = function (...args) {
|
||||||
|
const value = original ? original.apply(this, args) : undefined;
|
||||||
|
|
||||||
return value;
|
callback.apply(this, [value].concat(args));
|
||||||
};
|
|
||||||
|
|
||||||
Object.assign(object[method], original);
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.assign(object[method], original);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -37,29 +46,38 @@ export function extend(object, method, callback) {
|
|||||||
* new function will be run every time the object's method is called.
|
* new function will be run every time the object's method is called.
|
||||||
*
|
*
|
||||||
* The replacement function accepts the original method as its first argument,
|
* The replacement function accepts the original method as its first argument,
|
||||||
* which is like a call to 'super'. Any arguments passed to the original method
|
* which is like a call to `super`. Any arguments passed to the original method
|
||||||
* are also passed to the replacement.
|
* are also passed to the replacement.
|
||||||
*
|
*
|
||||||
* Care should be taken to extend the correct object – in most cases, a class'
|
* Care should be taken to extend the correct object – in most cases, a class'
|
||||||
* prototype will be the desired target of extension, not the class itself.
|
* prototype will be the desired target of extension, not the class itself.
|
||||||
*
|
*
|
||||||
* @example
|
* @example <caption>Example usage of overriding one method.</caption>
|
||||||
* override(Discussion.prototype, 'badges', function(original) {
|
* override(Discussion.prototype, 'badges', function(original) {
|
||||||
* const badges = original();
|
* const badges = original();
|
||||||
* // do something with badges
|
* // do something with badges
|
||||||
* return badges;
|
* return badges;
|
||||||
* });
|
* });
|
||||||
*
|
*
|
||||||
* @param {Object} object The object that owns the method
|
* @example <caption>Example usage of overriding multiple methods.</caption>
|
||||||
* @param {String} method The name of the method to override
|
* extend(Discussion.prototype, ['oncreate', 'onupdate'], function(original, vnode) {
|
||||||
|
* // something that needs to be run on creation and update
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* @param {object} object The object that owns the method
|
||||||
|
* @param {string|string[]} method The name or names of the method(s) to override
|
||||||
* @param {function} newMethod The method to replace it with
|
* @param {function} newMethod The method to replace it with
|
||||||
*/
|
*/
|
||||||
export function override(object, method, newMethod) {
|
export function override(object, methods, newMethod) {
|
||||||
const original = object[method];
|
const allMethods = Array.isArray(methods) ? methods : [methods];
|
||||||
|
|
||||||
object[method] = function (...args) {
|
allMethods.forEach((method) => {
|
||||||
return newMethod.apply(this, [original.bind(this)].concat(args));
|
const original = object[method];
|
||||||
};
|
|
||||||
|
|
||||||
Object.assign(object[method], original);
|
object[method] = function (...args) {
|
||||||
|
return newMethod.apply(this, [original.bind(this)].concat(args));
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.assign(object[method], original);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@@ -16,10 +16,12 @@ import localizedFormat from 'dayjs/plugin/localizedFormat';
|
|||||||
dayjs.extend(relativeTime);
|
dayjs.extend(relativeTime);
|
||||||
dayjs.extend(localizedFormat);
|
dayjs.extend(localizedFormat);
|
||||||
|
|
||||||
import patchMithril from './utils/patchMithril';
|
import FlarumRegistry from './FlarumRegistry';
|
||||||
|
|
||||||
patchMithril(window);
|
window.flreg = new FlarumRegistry();
|
||||||
|
|
||||||
import * as Extend from './extend/index';
|
import * as Extend from './extend/index';
|
||||||
|
|
||||||
export { Extend };
|
export { Extend };
|
||||||
|
|
||||||
|
import './utils/arrayFlatPolyfill';
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import Component from '../Component';
|
import Mithril from 'mithril';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a route resolver for a given component.
|
* Generates a route resolver for a given component.
|
||||||
@@ -7,7 +7,7 @@ import Component from '../Component';
|
|||||||
* - It sets a key on the component so a rerender will be triggered on route change.
|
* - It sets a key on the component so a rerender will be triggered on route change.
|
||||||
*/
|
*/
|
||||||
export default class DefaultResolver {
|
export default class DefaultResolver {
|
||||||
component: Component | Function;
|
component: Mithril.Component;
|
||||||
routeName: string;
|
routeName: string;
|
||||||
|
|
||||||
constructor(component, routeName) {
|
constructor(component, routeName) {
|
||||||
@@ -31,13 +31,8 @@ export default class DefaultResolver {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async onmatch(args, requestedPath, route) {
|
onmatch(args, requestedPath, route) {
|
||||||
if (typeof this.component.component === 'function') {
|
return this.component;
|
||||||
return this.component;
|
|
||||||
} else {
|
|
||||||
const component = await this.component();
|
|
||||||
return component.default;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render(vnode) {
|
render(vnode) {
|
||||||
|
@@ -10,17 +10,12 @@ export default class ModalManagerState {
|
|||||||
*
|
*
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
async show(componentClass, attrs) {
|
show(componentClass, attrs) {
|
||||||
// Breaking Change Compliance Warning, Remove in Beta 15.
|
// Breaking Change Compliance Warning, Remove in Beta 15.
|
||||||
if (!(componentClass.prototype instanceof Modal)) {
|
if (!(componentClass.prototype instanceof Modal)) {
|
||||||
if (typeof componentClass === 'function') {
|
// This is duplicated so that if the error is caught, an error message still shows up in the debug console.
|
||||||
const componentResolver = await componentClass();
|
console.error('The ModalManager can only show Modals');
|
||||||
componentClass = componentResolver.default;
|
throw new Error('The ModalManager can only show Modals');
|
||||||
} else {
|
|
||||||
// This is duplicated so that if the error is caught, an error message still shows up in the debug console.
|
|
||||||
console.error('The ModalManager can only show Modals');
|
|
||||||
throw new Error('The ModalManager can only show Modals');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (componentClass.init) {
|
if (componentClass.init) {
|
||||||
// This is duplicated so that if the error is caught, an error message still shows up in the debug console.
|
// This is duplicated so that if the error is caught, an error message still shows up in the debug console.
|
||||||
|
@@ -7,7 +7,7 @@ export default class Drawer {
|
|||||||
constructor() {
|
constructor() {
|
||||||
// Set up an event handler so that whenever the content area is tapped,
|
// Set up an event handler so that whenever the content area is tapped,
|
||||||
// the drawer will close.
|
// the drawer will close.
|
||||||
$('#content').on('click tap', e => {
|
$('#content').click((e) => {
|
||||||
if (this.isOpen()) {
|
if (this.isOpen()) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.hide();
|
this.hide();
|
||||||
|
@@ -1,162 +0,0 @@
|
|||||||
/* ========================================================================
|
|
||||||
* Bootstrap: affix.js v3.3.6
|
|
||||||
* http://getbootstrap.com/javascript/#affix
|
|
||||||
* ========================================================================
|
|
||||||
* Copyright 2011-2015 Twitter, Inc.
|
|
||||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
||||||
* ======================================================================== */
|
|
||||||
|
|
||||||
|
|
||||||
+function ($) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// AFFIX CLASS DEFINITION
|
|
||||||
// ======================
|
|
||||||
|
|
||||||
var Affix = function (element, options) {
|
|
||||||
this.options = $.extend({}, Affix.DEFAULTS, options)
|
|
||||||
|
|
||||||
this.$target = $(this.options.target)
|
|
||||||
.on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this))
|
|
||||||
.on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this))
|
|
||||||
|
|
||||||
this.$element = $(element)
|
|
||||||
this.affixed = null
|
|
||||||
this.unpin = null
|
|
||||||
this.pinnedOffset = null
|
|
||||||
|
|
||||||
this.checkPosition()
|
|
||||||
}
|
|
||||||
|
|
||||||
Affix.VERSION = '3.3.6'
|
|
||||||
|
|
||||||
Affix.RESET = 'affix affix-top affix-bottom'
|
|
||||||
|
|
||||||
Affix.DEFAULTS = {
|
|
||||||
offset: 0,
|
|
||||||
target: window
|
|
||||||
}
|
|
||||||
|
|
||||||
Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) {
|
|
||||||
var scrollTop = this.$target.scrollTop()
|
|
||||||
var position = this.$element.offset()
|
|
||||||
var targetHeight = this.$target.height()
|
|
||||||
|
|
||||||
if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false
|
|
||||||
|
|
||||||
if (this.affixed == 'bottom') {
|
|
||||||
if (offsetTop != null) return (scrollTop + this.unpin <= position.top) ? false : 'bottom'
|
|
||||||
return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom'
|
|
||||||
}
|
|
||||||
|
|
||||||
var initializing = this.affixed == null
|
|
||||||
var colliderTop = initializing ? scrollTop : position.top
|
|
||||||
var colliderHeight = initializing ? targetHeight : height
|
|
||||||
|
|
||||||
if (offsetTop != null && scrollTop <= offsetTop) return 'top'
|
|
||||||
if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom'
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
Affix.prototype.getPinnedOffset = function () {
|
|
||||||
if (this.pinnedOffset) return this.pinnedOffset
|
|
||||||
this.$element.removeClass(Affix.RESET).addClass('affix')
|
|
||||||
var scrollTop = this.$target.scrollTop()
|
|
||||||
var position = this.$element.offset()
|
|
||||||
return (this.pinnedOffset = position.top - scrollTop)
|
|
||||||
}
|
|
||||||
|
|
||||||
Affix.prototype.checkPositionWithEventLoop = function () {
|
|
||||||
setTimeout($.proxy(this.checkPosition, this), 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
Affix.prototype.checkPosition = function () {
|
|
||||||
if (!this.$element.is(':visible')) return
|
|
||||||
|
|
||||||
var height = this.$element.height()
|
|
||||||
var offset = this.options.offset
|
|
||||||
var offsetTop = offset.top
|
|
||||||
var offsetBottom = offset.bottom
|
|
||||||
var scrollHeight = Math.max($(document).height(), $(document.body).height())
|
|
||||||
|
|
||||||
if (typeof offset != 'object') offsetBottom = offsetTop = offset
|
|
||||||
if (typeof offsetTop == 'function') offsetTop = offset.top(this.$element)
|
|
||||||
if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element)
|
|
||||||
|
|
||||||
var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom)
|
|
||||||
|
|
||||||
if (this.affixed != affix) {
|
|
||||||
if (this.unpin != null) this.$element.css('top', '')
|
|
||||||
|
|
||||||
var affixType = 'affix' + (affix ? '-' + affix : '')
|
|
||||||
var e = $.Event(affixType + '.bs.affix')
|
|
||||||
|
|
||||||
this.$element.trigger(e)
|
|
||||||
|
|
||||||
if (e.isDefaultPrevented()) return
|
|
||||||
|
|
||||||
this.affixed = affix
|
|
||||||
this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null
|
|
||||||
|
|
||||||
this.$element
|
|
||||||
.removeClass(Affix.RESET)
|
|
||||||
.addClass(affixType)
|
|
||||||
.trigger(affixType.replace('affix', 'affixed') + '.bs.affix')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (affix == 'bottom') {
|
|
||||||
this.$element.offset({
|
|
||||||
top: scrollHeight - height - offsetBottom
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// AFFIX PLUGIN DEFINITION
|
|
||||||
// =======================
|
|
||||||
|
|
||||||
function Plugin(option) {
|
|
||||||
return this.each(function () {
|
|
||||||
var $this = $(this)
|
|
||||||
var data = $this.data('bs.affix')
|
|
||||||
var options = typeof option == 'object' && option
|
|
||||||
|
|
||||||
if (!data) $this.data('bs.affix', (data = new Affix(this, options)))
|
|
||||||
if (typeof option == 'string') data[option]()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var old = $.fn.affix
|
|
||||||
|
|
||||||
$.fn.affix = Plugin
|
|
||||||
$.fn.affix.Constructor = Affix
|
|
||||||
|
|
||||||
|
|
||||||
// AFFIX NO CONFLICT
|
|
||||||
// =================
|
|
||||||
|
|
||||||
$.fn.affix.noConflict = function () {
|
|
||||||
$.fn.affix = old
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// AFFIX DATA-API
|
|
||||||
// ==============
|
|
||||||
|
|
||||||
$(window).on('load', function () {
|
|
||||||
$('[data-spy="affix"]').each(function () {
|
|
||||||
var $spy = $(this)
|
|
||||||
var data = $spy.data()
|
|
||||||
|
|
||||||
data.offset = data.offset || {}
|
|
||||||
|
|
||||||
if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom
|
|
||||||
if (data.offsetTop != null) data.offset.top = data.offsetTop
|
|
||||||
|
|
||||||
Plugin.call($spy, data)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
}(jQuery);
|
|
14
js/src/common/utils/arrayFlatPolyfill.ts
Normal file
14
js/src/common/utils/arrayFlatPolyfill.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// Based off of the polyfill on MDN
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat#reduce_concat_isarray_recursivity
|
||||||
|
//
|
||||||
|
// Needed to provide support for Safari on iOS < 12
|
||||||
|
|
||||||
|
if (!Array.prototype['flat']) {
|
||||||
|
Array.prototype['flat'] = function flat(this: any[], depth: number = 1): any[] {
|
||||||
|
return depth > 0
|
||||||
|
? Array.prototype.reduce.call(this, (acc, val): any[] => acc.concat(Array.isArray(val) ? flat.call(val, depth - 1) : val), [])
|
||||||
|
: // If no depth is provided, or depth is 0, just return a copy of
|
||||||
|
// the array. Spread is supported in all major browsers (iOS 8+)
|
||||||
|
[...this];
|
||||||
|
};
|
||||||
|
}
|
@@ -1,5 +1,5 @@
|
|||||||
function bidi(node, prop) {
|
function bidi(node, prop) {
|
||||||
let type = node.tag === 'select' ? (node.attrs.multi ? 'multi' : 'select') : node.attrs.type;
|
var type = node.tag === 'select' ? (node.attrs.multi ? 'multi' : 'select') : node.attrs.type;
|
||||||
|
|
||||||
// Setup: bind listeners
|
// Setup: bind listeners
|
||||||
if (type === 'multi') {
|
if (type === 'multi') {
|
||||||
@@ -46,5 +46,5 @@ function bidi(node, prop) {
|
|||||||
bidi.view = function (ctrl, node, prop) {
|
bidi.view = function (ctrl, node, prop) {
|
||||||
return bidi(node, node.attrs.bidi);
|
return bidi(node, node.attrs.bidi);
|
||||||
};
|
};
|
||||||
window.flreg = new (require('../FlarumRegistry').default)();
|
|
||||||
export default bidi;
|
export default bidi;
|
||||||
|
10
js/src/common/utils/escapeRegExp.ts
Normal file
10
js/src/common/utils/escapeRegExp.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
const specialChars = /[.*+?^${}()|[\]\\]/g;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escapes the `RegExp` special characters in `input`.
|
||||||
|
*
|
||||||
|
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
|
||||||
|
*/
|
||||||
|
export default function escapeRegExp(input: string): string {
|
||||||
|
return input.replace(specialChars, '\\$&');
|
||||||
|
}
|
@@ -2,7 +2,7 @@
|
|||||||
* The `evented` mixin provides methods allowing an object to trigger events,
|
* The `evented` mixin provides methods allowing an object to trigger events,
|
||||||
* running externally registered event handlers.
|
* running externally registered event handlers.
|
||||||
*/
|
*/
|
||||||
export default {
|
const evented = {
|
||||||
/**
|
/**
|
||||||
* Arrays of registered event handlers, grouped by the event name.
|
* Arrays of registered event handlers, grouped by the event name.
|
||||||
*
|
*
|
||||||
@@ -79,3 +79,5 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default evented;
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
export default function proxifyCompat(compat: { [key: string]: any }, namespace: string) {
|
export default (compat: { [key: string]: any }, namespace: string) => {
|
||||||
// regex to replace common/ and NAMESPACE/ for core & core extensions
|
// regex to replace common/ and NAMESPACE/ for core & core extensions
|
||||||
// e.g. admin/utils/extract --> utils/extract
|
// e.g. admin/utils/extract --> utils/extract
|
||||||
// e.g. tags/common/utils/sortTags --> tags/utils/sortTags
|
// e.g. tags/common/utils/sortTags --> tags/utils/sortTags
|
||||||
@@ -7,4 +7,4 @@ export default function proxifyCompat(compat: { [key: string]: any }, namespace:
|
|||||||
return new Proxy(compat, {
|
return new Proxy(compat, {
|
||||||
get: (obj, prop: string) => obj[prop] || obj[prop.replace(regex, '$1')],
|
get: (obj, prop: string) => obj[prop] || obj[prop.replace(regex, '$1')],
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
3
js/src/common/utils/throttleDebounce.ts
Normal file
3
js/src/common/utils/throttleDebounce.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// Re-exports `throttle-debounce` to be used in `compat.js`.
|
||||||
|
|
||||||
|
export { throttle, debounce } from 'throttle-debounce';
|
@@ -9,8 +9,7 @@
|
|||||||
* Replaces m.withAttr for Mithril 2.0.
|
* Replaces m.withAttr for Mithril 2.0.
|
||||||
* @see https://mithril.js.org/archive/v0.2.5/mithril.withAttr.html
|
* @see https://mithril.js.org/archive/v0.2.5/mithril.withAttr.html
|
||||||
*/
|
*/
|
||||||
export default function withAttr(key: string, cb: Function) {
|
export default (key: string, cb: Function) =>
|
||||||
return function (this: Element) {
|
function (this: Element) {
|
||||||
cb(this.getAttribute(key) || this[key]);
|
cb(this.getAttribute(key) || this[key]);
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
@@ -1,9 +1,13 @@
|
|||||||
import History from './utils/History';
|
import History from './utils/History';
|
||||||
import Pane from './utils/Pane';
|
import Pane from './utils/Pane';
|
||||||
import DiscussionPage from './components/DiscussionPage';
|
import DiscussionPage from './components/DiscussionPage';
|
||||||
|
import SignUpModal from './components/SignUpModal';
|
||||||
import HeaderPrimary from './components/HeaderPrimary';
|
import HeaderPrimary from './components/HeaderPrimary';
|
||||||
import HeaderSecondary from './components/HeaderSecondary';
|
import HeaderSecondary from './components/HeaderSecondary';
|
||||||
import Composer from './components/Composer';
|
import Composer from './components/Composer';
|
||||||
|
import DiscussionRenamedNotification from './components/DiscussionRenamedNotification';
|
||||||
|
import CommentPost from './components/CommentPost';
|
||||||
|
import DiscussionRenamedPost from './components/DiscussionRenamedPost';
|
||||||
import routes from './routes';
|
import routes from './routes';
|
||||||
import alertEmailConfirmation from './utils/alertEmailConfirmation';
|
import alertEmailConfirmation from './utils/alertEmailConfirmation';
|
||||||
import Application from '../common/Application';
|
import Application from '../common/Application';
|
||||||
@@ -15,6 +19,24 @@ import ComposerState from './states/ComposerState';
|
|||||||
import isSafariMobile from './utils/isSafariMobile';
|
import isSafariMobile from './utils/isSafariMobile';
|
||||||
|
|
||||||
export default class ForumApplication extends Application {
|
export default class ForumApplication extends Application {
|
||||||
|
/**
|
||||||
|
* A map of notification types to their components.
|
||||||
|
*
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
notificationComponents = {
|
||||||
|
discussionRenamed: DiscussionRenamedNotification,
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* A map of post types to their components.
|
||||||
|
*
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
postComponents = {
|
||||||
|
comment: CommentPost,
|
||||||
|
discussionRenamed: DiscussionRenamedPost,
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An object which controls the state of the page's side pane.
|
* An object which controls the state of the page's side pane.
|
||||||
*
|
*
|
||||||
@@ -106,7 +128,7 @@ export default class ForumApplication extends Application {
|
|||||||
|
|
||||||
// Route the home link back home when clicked. We do not want it to register
|
// Route the home link back home when clicked. We do not want it to register
|
||||||
// if the user is opening it in a new tab, however.
|
// if the user is opening it in a new tab, however.
|
||||||
$('#home-link').on('click tap', e => {
|
$('#home-link').click((e) => {
|
||||||
if (e.ctrlKey || e.metaKey || e.which === 2) return;
|
if (e.ctrlKey || e.metaKey || e.which === 2) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
app.history.home();
|
app.history.home();
|
||||||
@@ -131,8 +153,8 @@ export default class ForumApplication extends Application {
|
|||||||
* @param {Discussion} discussion
|
* @param {Discussion} discussion
|
||||||
* @return {Boolean}
|
* @return {Boolean}
|
||||||
*/
|
*/
|
||||||
async viewingDiscussion(discussion) {
|
viewingDiscussion(discussion) {
|
||||||
return this.current.matches(DiscussionPage.default, { discussion });
|
return this.current.matches(DiscussionPage, { discussion });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -152,8 +174,6 @@ export default class ForumApplication extends Application {
|
|||||||
if (payload.loggedIn) {
|
if (payload.loggedIn) {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
} else {
|
} else {
|
||||||
const SignUpModal = () => import(/* webpackChunkName: "forum/components/SignUpModal" */ './components/SignUpModal');
|
|
||||||
|
|
||||||
this.modal.show(SignUpModal, payload);
|
this.modal.show(SignUpModal, payload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user