├── LICENSE.md ├── README.md ├── composer.json ├── extend.php ├── icon.svg ├── js ├── admin.js ├── dist-typings │ ├── admin │ │ └── index.d.ts │ └── forum │ │ ├── components │ │ └── TagsPage.d.ts │ │ └── index.d.ts ├── dist │ ├── admin.js │ ├── admin.js.map │ ├── forum.js │ └── forum.js.map ├── forum.js ├── package.json ├── src │ ├── admin │ │ └── index.js │ └── forum │ │ ├── components │ │ └── TagsPage.js │ │ └── index.js ├── tsconfig.json ├── webpack.config.js └── yarn.lock ├── less ├── admin.less ├── admin │ ├── AdminPage.less │ ├── Alert.less │ ├── App.less │ ├── ExtensionsPage.less │ ├── FormGroup.less │ ├── GroupPermissions.less │ ├── PermissionsPage.less │ ├── StatisticsWidget.less │ ├── StatusWidget.less │ ├── UserListPage.less │ ├── Widget.less │ ├── sideNav.less │ └── variables.less ├── common │ ├── App.less │ ├── Button.less │ ├── Dropdown.less │ ├── common.less │ ├── mixins │ │ ├── asirem-block.less │ │ ├── asirem-border-radius.less │ │ └── asirem-icon-block.less │ ├── scaffolding.less │ └── variables.less ├── forum.less └── forum │ ├── App.less │ ├── DiscussionList.less │ ├── Footer.less │ ├── Hero.less │ ├── Post.less │ ├── TagsPage.less │ └── sideNav.less ├── locale └── .gitkeep └── views └── frontend └── admin.blade.php /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Sami Mazouz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | asirem_logo 3 |

Asirem

4 |

5 | License Latest Stable Version Total Downloads donate 6 |

7 | The weird Flarum theme made with potatoes as fuel, by Afrux. 8 |

9 | asirem_theme_dashboard_screenshot_1asirem_theme_dashboard_screenshot_1 10 | asirem_theme_forum_screenshot_1 11 |

12 | 13 | ### Installation 14 | 15 | Install with composer: 16 | 17 | ```sh 18 | composer require afrux/asirem 19 | ``` 20 | 21 | The following are nice to install with the theme as well: 22 | 23 | ```sh 24 | composer require sycho/flarum-advanced-extension-categories 25 | ``` 26 | 27 | ### Updating 28 | 29 | ```sh 30 | composer update afrux/asirem --with-dependencies 31 | php flarum cache:clear 32 | ``` 33 | 34 | ### Links 35 | 36 | - [Packagist](https://packagist.org/packages/afrux/asirem) 37 | - [GitHub](https://github.com/afrux/asirem) 38 | - [Discuss](https://discuss.flarum.org/d/27939-asirem-theme) 39 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "afrux/asirem", 3 | "description": "The weird Flarum theme made with potatoes as fuel, by Afrux.", 4 | "keywords": [ 5 | "flarum" 6 | ], 7 | "type": "flarum-extension", 8 | "license": "MIT", 9 | "funding": [ 10 | { 11 | "type": "other", 12 | "url": "https://www.buymeacoffee.com/sycho" 13 | } 14 | ], 15 | "require": { 16 | "flarum/core": "^1.8.0", 17 | "afrux/flarum-theme-base": "^0.1.0" 18 | }, 19 | "require-dev": { 20 | "sycho/flarum-uikit": "0.1.x-dev", 21 | "afrux/flarum-theme-base": "0.1.x-dev" 22 | }, 23 | "authors": [ 24 | { 25 | "name": "Sami Mazouz", 26 | "email": "ilyasmazouz@gmail.com", 27 | "role": "Developer" 28 | } 29 | ], 30 | "support": { 31 | "issues": "https://github.com/afrux/asirem/issues", 32 | "source": "https://github.com/afrux/asirem", 33 | "forum": "https://discuss.flarum.org/d/27939" 34 | }, 35 | "autoload": { 36 | "psr-4": { 37 | "Afrux\\Asirem\\": "src/" 38 | } 39 | }, 40 | "autoload-dev": { 41 | "psr-4": { 42 | "Afrux\\Asirem\\Tests\\": "tests/" 43 | } 44 | }, 45 | "extra": { 46 | "branch-alias": { 47 | "dev-master": "0.1.x-dev" 48 | }, 49 | "flarum-extension": { 50 | "title": "Asirem", 51 | "category": "theme", 52 | "optional-dependencies": [ 53 | "flarum/tags", 54 | "flarum/approval", 55 | "sycho/flarum-advanced-extension-categories" 56 | ], 57 | "icon": { 58 | "image": "icon.svg" 59 | } 60 | }, 61 | "flarum-cli": { 62 | "modules": { 63 | "admin": true, 64 | "forum": true, 65 | "js": true, 66 | "jsCommon": false, 67 | "css": true, 68 | "gitConf": true, 69 | "githubActions": true, 70 | "prettier": true, 71 | "typescript": true, 72 | "bundlewatch": false, 73 | "backendTesting": false, 74 | "editorConfig": true, 75 | "styleci": true 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /extend.php: -------------------------------------------------------------------------------- 1 | splitToNavAndContent() 24 | ->normalizeStatusWidgetStructure() 25 | ->normalizeAdminHeaderStructure() 26 | ->normalizeExtensionPageStructure() 27 | ->normalizeUserTable() 28 | ->addExtensionsPage(), 29 | 30 | (new Extend\Frontend('forum')) 31 | ->js(__DIR__.'/js/dist/forum.js') 32 | ->css(__DIR__.'/less/forum.less'), 33 | 34 | (new Extend\Frontend('admin')) 35 | ->js(__DIR__.'/js/dist/admin.js') 36 | ->css(__DIR__.'/less/admin.less') 37 | ->content(function (Document $document) { 38 | $document->layoutView = "afrux-asirem::frontend.admin"; 39 | }), 40 | 41 | (new Extend\View) 42 | ->namespace("afrux-asirem", __DIR__."/views"), 43 | 44 | new Extend\Locales(__DIR__.'/locale'), 45 | ]; 46 | -------------------------------------------------------------------------------- /icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Asirem 6 | 7 | -------------------------------------------------------------------------------- /js/admin.js: -------------------------------------------------------------------------------- 1 | export * from './src/admin'; 2 | -------------------------------------------------------------------------------- /js/dist-typings/admin/index.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /js/dist-typings/forum/components/TagsPage.d.ts: -------------------------------------------------------------------------------- 1 | export default class TagsPage { 2 | view(): JSX.Element; 3 | } 4 | -------------------------------------------------------------------------------- /js/dist-typings/forum/index.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /js/dist/admin.js: -------------------------------------------------------------------------------- 1 | (()=>{var e={n:t=>{var o=t&&t.__esModule?()=>t.default:()=>t;return e.d(o,{a:o}),o},d:(t,o)=>{for(var r in o)e.o(o,r)&&!e.o(t,r)&&Object.defineProperty(t,r,{enumerable:!0,get:o[r]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r:e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})}},t={};(()=>{"use strict";e.r(t);const o=flarum.core.compat.extend,r=flarum.core.compat["admin/components/PermissionsPage"];var n=e.n(r);const a=flarum.core.compat["admin/components/PermissionGrid"];var i=e.n(a);app.initializers.add("afrux-asirem",(function(e){(0,o.extend)(n().prototype,"content",(function(e){e&&e[0]&&e[0].children&&e[0].children[1]&&(e[0].children[1].attrs.className+=" Button--dashed")})),(0,o.override)(i().prototype,"view",(function(e,t){return[m("div",{className:"PermissionsPage-permissions-overflow"},e(t))]})),(0,o.extend)(i().prototype,["oncreate","onupdate"],(function(e){$(".PermissionGrid-child .Button--text").removeClass("Button--text")}))}),-999999)})(),module.exports=t})(); 2 | //# sourceMappingURL=admin.js.map -------------------------------------------------------------------------------- /js/dist/admin.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"admin.js","mappings":"MACA,IAAIA,EAAsB,CCA1BA,EAAyBC,IACxB,IAAIC,EAASD,GAAUA,EAAOE,WAC7B,IAAOF,EAAiB,QACxB,IAAM,EAEP,OADAD,EAAoBI,EAAEF,EAAQ,CAAEG,EAAGH,IAC5BA,GCLRF,EAAwB,CAACM,EAASC,KACjC,IAAI,IAAIC,KAAOD,EACXP,EAAoBS,EAAEF,EAAYC,KAASR,EAAoBS,EAAEH,EAASE,IAC5EE,OAAOC,eAAeL,EAASE,EAAK,CAAEI,YAAY,EAAMC,IAAKN,EAAWC,MCJ3ER,EAAwB,CAACc,EAAKC,IAAUL,OAAOM,UAAUC,eAAeC,KAAKJ,EAAKC,GCClFf,EAAyBM,IACH,oBAAXa,QAA0BA,OAAOC,aAC1CV,OAAOC,eAAeL,EAASa,OAAOC,YAAa,CAAEC,MAAO,WAE7DX,OAAOC,eAAeL,EAAS,aAAc,CAAEe,OAAO,M,+BCLvD,MAAM,EAA+BC,OAAOC,KAAKC,OAAe,OCA1D,EAA+BF,OAAOC,KAAKC,OAAO,oC,aCAxD,MAAM,EAA+BF,OAAOC,KAAKC,OAAO,mC,aCIxDC,IAAIC,aAAaC,IACf,gBACA,SAACF,IACCG,EAAAA,EAAAA,QAAOC,IAAAA,UAA2B,WAAW,SAAUC,GACjDA,GAASA,EAAM,IAAMA,EAAM,GAAGC,UAAYD,EAAM,GAAGC,SAAS,KAC9DD,EAAM,GAAGC,SAAS,GAAGC,MAAMC,WAAa,uBAI5CC,EAAAA,EAAAA,UAASC,IAAAA,UAA0B,QAAQ,SAAUC,EAAUN,GAC7D,MAAO,CAAC,SAAKG,UAAU,wCAAwCG,EAASN,SAG1EF,EAAAA,EAAAA,QAAOO,IAAAA,UAA0B,CAAC,WAAY,aAAa,SAAUL,GACnEO,EAAE,uCAAuCC,YAAY,sBAGxD,S","sources":["webpack://@afrux/asirem/webpack/bootstrap","webpack://@afrux/asirem/webpack/runtime/compat get default export","webpack://@afrux/asirem/webpack/runtime/define property getters","webpack://@afrux/asirem/webpack/runtime/hasOwnProperty shorthand","webpack://@afrux/asirem/webpack/runtime/make namespace object","webpack://@afrux/asirem/external root \"flarum.core.compat['extend']\"","webpack://@afrux/asirem/external root \"flarum.core.compat['admin/components/PermissionsPage']\"","webpack://@afrux/asirem/external root \"flarum.core.compat['admin/components/PermissionGrid']\"","webpack://@afrux/asirem/./src/admin/index.js"],"sourcesContent":["// The require scope\nvar __webpack_require__ = {};\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['extend'];","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['admin/components/PermissionsPage'];","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['admin/components/PermissionGrid'];","import { extend, override } from 'flarum/extend';\nimport PermissionsPage from 'flarum/admin/components/PermissionsPage';\nimport PermissionGrid from 'flarum/admin/components/PermissionGrid';\n\napp.initializers.add(\n 'afrux-asirem',\n (app) => {\n extend(PermissionsPage.prototype, 'content', function (vnode) {\n if (vnode && vnode[0] && vnode[0].children && vnode[0].children[1]) {\n vnode[0].children[1].attrs.className += ' Button--dashed';\n }\n });\n\n override(PermissionGrid.prototype, 'view', function (original, vnode) {\n return [
{original(vnode)}
];\n });\n\n extend(PermissionGrid.prototype, ['oncreate', 'onupdate'], function (vnode) {\n $('.PermissionGrid-child .Button--text').removeClass('Button--text');\n });\n },\n -999999\n);\n"],"names":["__webpack_require__","module","getter","__esModule","d","a","exports","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","Symbol","toStringTag","value","flarum","core","compat","app","initializers","add","extend","PermissionsPage","vnode","children","attrs","className","override","PermissionGrid","original","$","removeClass"],"sourceRoot":""} -------------------------------------------------------------------------------- /js/dist/forum.js: -------------------------------------------------------------------------------- 1 | (()=>{var t={n:e=>{var s=e&&e.__esModule?()=>e.default:()=>e;return t.d(s,{a:s}),s},d:(e,s)=>{for(var a in s)t.o(s,a)&&!t.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:s[a]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e),r:t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})}},e={};(()=>{"use strict";function s(){return s=Object.assign||function(t){for(var e=1;e {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","export default function _extends() {\n _extends = Object.assign || function (target) {\n for (var i = 1; i < arguments.length; i++) {\n var source = arguments[i];\n\n for (var key in source) {\n if (Object.prototype.hasOwnProperty.call(source, key)) {\n target[key] = source[key];\n }\n }\n }\n\n return target;\n };\n\n return _extends.apply(this, arguments);\n}","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['common/extend'];","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['forum/components/DiscussionListItem'];","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['tags/components/TagsPage'];","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['components/IndexPage'];","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['components/Link'];","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['components/LoadingIndicator'];","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['helpers/listItems'];","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['helpers/humanTime'];","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['tags/common/helpers/tagIcon'];","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['tags/common/helpers/tagLabel'];","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['tags/common/utils/sortTags'];","import IndexPage from 'flarum/components/IndexPage';\nimport Link from 'flarum/components/Link';\nimport LoadingIndicator from 'flarum/components/LoadingIndicator';\nimport listItems from 'flarum/helpers/listItems';\nimport humanTime from 'flarum/helpers/humanTime';\n\nimport tagIcon from 'flarum/tags/common/helpers/tagIcon';\nimport tagLabel from 'flarum/tags/common/helpers/tagLabel';\nimport sortTags from 'flarum/tags/common/utils/sortTags';\n\nexport default class TagsPage {\n view() {\n if (this.loading) {\n return ;\n }\n\n const pinned = this.tags.filter((tag) => tag.position() !== null);\n const cloud = this.tags.filter((tag) => tag.position() === null);\n\n return (\n
\n {IndexPage.prototype.hero()}\n
\n \n\n
\n
    \n {pinned.map((tag) => {\n const lastPostedDiscussion = tag.lastPostedDiscussion();\n const children = sortTags(tag.children() || []);\n const tagIconNode = tagIcon(tag, {}, { useColor: false });\n\n if (tagIconNode.attrs.style && tagIconNode.attrs.style.backgroundColor) {\n delete tagIconNode.attrs.style.backgroundColor;\n }\n\n return (\n
  • \n \n
    {tagIconNode}
    \n
    \n

    {tag.name()}

    \n

    {tag.description()}

    \n {children && children.length ? (\n
    \n {children.map((child) => [\n \n {child.name()}\n ,\n ' ',\n ])}\n
    \n ) : (\n ''\n )}\n {lastPostedDiscussion ? (\n \n {lastPostedDiscussion.title()}\n {humanTime(lastPostedDiscussion.lastPostedAt())}\n \n ) : (\n \n )}\n
    \n \n
  • \n );\n })}\n
\n\n {cloud.length ?
{cloud.map((tag) => [tagLabel(tag, { link: true }), ' '])}
: ''}\n
\n
\n
\n );\n }\n}\n","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['utils/string'];","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.core.compat['extensions/afrux-theme-base/forum/components/Footer'];","import { extend, override } from 'flarum/common/extend';\nimport DiscussionListItem from 'flarum/forum/components/DiscussionListItem';\nimport TagsPage from 'flarum/tags/components/TagsPage';\nimport AsiremTagsPage from './components/TagsPage';\nimport { truncate } from 'flarum/utils/string';\n\nimport Footer from 'flarum/extensions/afrux-theme-base/forum/components/Footer';\n\napp.initializers.add('afrux-asirem', () => {\n extend(DiscussionListItem.prototype, 'view', function (vnode) {\n const discussionListItemContent = vnode.children.find(\n (e) => e && e.tag === 'div' && e.attrs && e.attrs.className.includes('DiscussionListItem-content')\n );\n\n discussionListItemContent.children[0] = (\n
{[discussionListItemContent.children[0], discussionListItemContent.children[1]]}
\n );\n\n delete discussionListItemContent.children[1];\n\n discussionListItemContent.children[3] =
{discussionListItemContent.children[3]}
;\n\n if (this.attrs.discussion.tags() && this.attrs.discussion.tags()[0] && this.attrs.discussion.tags()[0].color()) {\n vnode.attrs.style = { '--tag-color': this.attrs.discussion.tags()[0].color(), ...(vnode.attrs.style || {}) };\n }\n\n if (this.attrs.discussion.isUnread()) {\n vnode.attrs.className += ' DiscussionListItem--unread';\n }\n });\n\n extend(DiscussionListItem.prototype, 'infoItems', function (items) {\n if (!items.has('excerpt')) {\n const firstPost = this.attrs.discussion.firstPost();\n\n if (firstPost) {\n const excerpt = truncate(firstPost.contentPlain(), 175);\n\n items.add('excerpt',
{excerpt}
, -100);\n }\n }\n });\n\n override(Footer.prototype, 'separator', function () {\n return (\n \n \n \n );\n });\n\n override(TagsPage.prototype, 'view', AsiremTagsPage.prototype.view);\n});\n"],"names":["__webpack_require__","module","getter","__esModule","d","a","exports","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","Symbol","toStringTag","value","_extends","assign","target","i","arguments","length","source","apply","this","flarum","core","compat","TagsPage","view","loading","pinned","tags","filter","tag","position","cloud","className","IndexPage","listItems","toArray","map","lastPostedDiscussion","children","sortTags","tagIconNode","tagIcon","useColor","attrs","style","backgroundColor","color","href","app","route","name","description","child","discussion","lastPostNumber","title","humanTime","lastPostedAt","tagLabel","link","initializers","add","extend","DiscussionListItem","vnode","discussionListItemContent","find","e","includes","isUnread","items","has","firstPost","excerpt","truncate","contentPlain","override","Footer","xmlns","width","height","AsiremTagsPage"],"sourceRoot":""} -------------------------------------------------------------------------------- /js/forum.js: -------------------------------------------------------------------------------- 1 | export * from './src/forum'; 2 | -------------------------------------------------------------------------------- /js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@afrux/asirem", 3 | "version": "0.0.0", 4 | "private": true, 5 | "prettier": "@flarum/prettier-config", 6 | "dependencies": {}, 7 | "devDependencies": { 8 | "@flarum/prettier-config": "^1.0.0", 9 | "flarum-tsconfig": "^1.0.2", 10 | "flarum-webpack-config": "^2.0.0", 11 | "prettier": "^2.5.1", 12 | "typescript": "^4.5.4", 13 | "typescript-coverage-report": "^0.6.1", 14 | "webpack": "^5.65.0", 15 | "webpack-cli": "^4.9.1" 16 | }, 17 | "scripts": { 18 | "ci": "<% if (params.jsPackageManager) { %>yarn install --immutable --immutable-cache<% } else { %>npm ci<% } %>", 19 | "dev": "webpack --mode development --watch", 20 | "build": "webpack --mode production", 21 | "analyze": "cross-env ANALYZER=true yarn run build", 22 | "format": "prettier --write src", 23 | "format-check": "prettier --check src", 24 | "clean-typings": "npx rimraf dist-typings && mkdir dist-typings", 25 | "build-typings": "yarn run clean-typings && ([ -e src/@types ] && cp -r src/@types dist-typings/@types || true) && tsc && yarn run post-build-typings", 26 | "check-typings": "tsc --noEmit --emitDeclarationOnly false", 27 | "check-typings-coverage": "typescript-coverage-report", 28 | "post-build-typings": "find dist-typings -type f -name '*.d.ts' -print0 | xargs -0 sed -i 's,../src/@types,@types,g'" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /js/src/admin/index.js: -------------------------------------------------------------------------------- 1 | import { extend, override } from 'flarum/extend'; 2 | import PermissionsPage from 'flarum/admin/components/PermissionsPage'; 3 | import PermissionGrid from 'flarum/admin/components/PermissionGrid'; 4 | 5 | app.initializers.add( 6 | 'afrux-asirem', 7 | (app) => { 8 | extend(PermissionsPage.prototype, 'content', function (vnode) { 9 | if (vnode && vnode[0] && vnode[0].children && vnode[0].children[1]) { 10 | vnode[0].children[1].attrs.className += ' Button--dashed'; 11 | } 12 | }); 13 | 14 | override(PermissionGrid.prototype, 'view', function (original, vnode) { 15 | return [
{original(vnode)}
]; 16 | }); 17 | 18 | extend(PermissionGrid.prototype, ['oncreate', 'onupdate'], function (vnode) { 19 | $('.PermissionGrid-child .Button--text').removeClass('Button--text'); 20 | }); 21 | }, 22 | -999999 23 | ); 24 | -------------------------------------------------------------------------------- /js/src/forum/components/TagsPage.js: -------------------------------------------------------------------------------- 1 | import IndexPage from 'flarum/components/IndexPage'; 2 | import Link from 'flarum/components/Link'; 3 | import LoadingIndicator from 'flarum/components/LoadingIndicator'; 4 | import listItems from 'flarum/helpers/listItems'; 5 | import humanTime from 'flarum/helpers/humanTime'; 6 | 7 | import tagIcon from 'flarum/tags/common/helpers/tagIcon'; 8 | import tagLabel from 'flarum/tags/common/helpers/tagLabel'; 9 | import sortTags from 'flarum/tags/common/utils/sortTags'; 10 | 11 | export default class TagsPage { 12 | view() { 13 | if (this.loading) { 14 | return ; 15 | } 16 | 17 | const pinned = this.tags.filter((tag) => tag.position() !== null); 18 | const cloud = this.tags.filter((tag) => tag.position() === null); 19 | 20 | return ( 21 |
22 | {IndexPage.prototype.hero()} 23 |
24 | 27 | 28 |
29 |
    30 | {pinned.map((tag) => { 31 | const lastPostedDiscussion = tag.lastPostedDiscussion(); 32 | const children = sortTags(tag.children() || []); 33 | const tagIconNode = tagIcon(tag, {}, { useColor: false }); 34 | 35 | if (tagIconNode.attrs.style && tagIconNode.attrs.style.backgroundColor) { 36 | delete tagIconNode.attrs.style.backgroundColor; 37 | } 38 | 39 | return ( 40 |
  • 41 | 42 |
    {tagIconNode}
    43 |
    44 |

    {tag.name()}

    45 |

    {tag.description()}

    46 | {children && children.length ? ( 47 |
    48 | {children.map((child) => [ 49 | 50 | {child.name()} 51 | , 52 | ' ', 53 | ])} 54 |
    55 | ) : ( 56 | '' 57 | )} 58 | {lastPostedDiscussion ? ( 59 | 63 | {lastPostedDiscussion.title()} 64 | {humanTime(lastPostedDiscussion.lastPostedAt())} 65 | 66 | ) : ( 67 | 68 | )} 69 |
    70 | 71 |
  • 72 | ); 73 | })} 74 |
75 | 76 | {cloud.length ?
{cloud.map((tag) => [tagLabel(tag, { link: true }), ' '])}
: ''} 77 |
78 |
79 |
80 | ); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /js/src/forum/index.js: -------------------------------------------------------------------------------- 1 | import { extend, override } from 'flarum/common/extend'; 2 | import DiscussionListItem from 'flarum/forum/components/DiscussionListItem'; 3 | import TagsPage from 'flarum/tags/components/TagsPage'; 4 | import AsiremTagsPage from './components/TagsPage'; 5 | import { truncate } from 'flarum/utils/string'; 6 | 7 | import Footer from 'flarum/extensions/afrux-theme-base/forum/components/Footer'; 8 | 9 | app.initializers.add('afrux-asirem', () => { 10 | extend(DiscussionListItem.prototype, 'view', function (vnode) { 11 | const discussionListItemContent = vnode.children.find( 12 | (e) => e && e.tag === 'div' && e.attrs && e.attrs.className.includes('DiscussionListItem-content') 13 | ); 14 | 15 | discussionListItemContent.children[0] = ( 16 |
{[discussionListItemContent.children[0], discussionListItemContent.children[1]]}
17 | ); 18 | 19 | delete discussionListItemContent.children[1]; 20 | 21 | discussionListItemContent.children[3] =
{discussionListItemContent.children[3]}
; 22 | 23 | if (this.attrs.discussion.tags() && this.attrs.discussion.tags()[0] && this.attrs.discussion.tags()[0].color()) { 24 | vnode.attrs.style = { '--tag-color': this.attrs.discussion.tags()[0].color(), ...(vnode.attrs.style || {}) }; 25 | } 26 | 27 | if (this.attrs.discussion.isUnread()) { 28 | vnode.attrs.className += ' DiscussionListItem--unread'; 29 | } 30 | }); 31 | 32 | extend(DiscussionListItem.prototype, 'infoItems', function (items) { 33 | if (!items.has('excerpt')) { 34 | const firstPost = this.attrs.discussion.firstPost(); 35 | 36 | if (firstPost) { 37 | const excerpt = truncate(firstPost.contentPlain(), 175); 38 | 39 | items.add('excerpt',
{excerpt}
, -100); 40 | } 41 | } 42 | }); 43 | 44 | override(Footer.prototype, 'separator', function () { 45 | return ( 46 | 47 | 51 | 52 | ); 53 | }); 54 | 55 | override(TagsPage.prototype, 'view', AsiremTagsPage.prototype.view); 56 | }); 57 | -------------------------------------------------------------------------------- /js/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use Flarum's tsconfig as a starting point 3 | "extends": "flarum-tsconfig", 4 | // This will match all .ts, .tsx, .d.ts, .js, .jsx files in your `src` folder 5 | // and also tells your Typescript server to read core's global typings for 6 | // access to `dayjs` and `$` in the global namespace. 7 | "include": [ 8 | "src/**/*", 9 | "../vendor/*/*/js/dist-typings/@types/**/*", 10 | // 11 | // 12 | "@types/**/*" 13 | ], 14 | "compilerOptions": { 15 | // This will output typings to `dist-typings` 16 | "declarationDir": "./dist-typings", 17 | "baseUrl": ".", 18 | "paths": { 19 | "flarum/*": ["../vendor/flarum/core/js/dist-typings/*"], 20 | // 21 | // 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /js/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require('flarum-webpack-config')(); 2 | -------------------------------------------------------------------------------- /less/admin.less: -------------------------------------------------------------------------------- 1 | @import "common/common.less"; 2 | 3 | @import "admin/variables.less"; 4 | @import "admin/App.less"; 5 | @import "admin/sideNav.less"; 6 | @import "admin/AdminPage.less"; 7 | @import "admin/ExtensionsPage.less"; 8 | @import "admin/PermissionsPage.less"; 9 | @import "admin/GroupPermissions.less"; 10 | @import "admin/UserListPage.less"; 11 | @import "admin/Widget.less"; 12 | @import "admin/FormGroup.less"; 13 | @import "admin/StatusWidget.less"; 14 | @import "admin/StatisticsWidget.less"; 15 | @import "admin/Alert.less"; 16 | -------------------------------------------------------------------------------- /less/admin/AdminPage.less: -------------------------------------------------------------------------------- 1 | .ThemeBase-AdminHeader { 2 | background-color: transparent; 3 | color: @text-color; 4 | padding: 0; 5 | 6 | &-title, h2 { 7 | color: inherit !important; 8 | } 9 | 10 | &-description { 11 | color: inherit; 12 | opacity: 0.8; 13 | } 14 | 15 | &-container { 16 | padding: 0 0 15px 0px !important; 17 | border-radius: 0 14px 14px 0; 18 | 19 | @media @desktop-up { 20 | .App-content & { 21 | margin: 0 30px; 22 | width: unset; 23 | max-width: unset; 24 | } 25 | } 26 | } 27 | 28 | &-icon { 29 | --size: 60px; 30 | font-size: 24px; 31 | width: var(--size); 32 | height: var(--size); 33 | border-radius: 14px; 34 | margin-right: 20px; 35 | border-radius: 100%; 36 | box-shadow: @asirem-box-shadow; 37 | background-color: @body-bg; 38 | 39 | > .icon { 40 | margin: 0; 41 | } 42 | } 43 | } 44 | 45 | @media @tablet, @phone { 46 | .ThemeBase-AdminHeader { 47 | padding-left: 15px; 48 | padding-right: 15px; 49 | } 50 | } 51 | 52 | .AdminPage { 53 | background-color: transparent !important; 54 | } 55 | 56 | .ThemeBase-ExtensionPage { 57 | padding-bottom: 24px; 58 | 59 | &-body { 60 | > .container { 61 | > :first-child { 62 | border-top-left-radius: @border-radius; 63 | border-top-right-radius: @border-radius; 64 | } 65 | > :last-child { 66 | border-bottom-left-radius: @border-radius; 67 | border-bottom-right-radius: @border-radius; 68 | } 69 | } 70 | } 71 | 72 | &-section { 73 | padding: 15px; 74 | 75 | .container { 76 | padding: 0; 77 | max-width: unset !important; 78 | width: unset !important; 79 | } 80 | } 81 | 82 | &-headerItems { 83 | padding-top: 5px; 84 | padding-bottom: 5px; 85 | } 86 | 87 | &-headerItems, &-sections-nav { 88 | padding-left: 15px; 89 | padding-right: 15px; 90 | } 91 | 92 | &-headerItems, &-sections { 93 | background-color: @body-bg; 94 | 95 | .Button { 96 | --control-bg: @control-bg; 97 | 98 | &--primary { 99 | --control-bg: @primary-color; 100 | } 101 | } 102 | } 103 | 104 | &-sections-nav { 105 | background-color: @control-bg-light; 106 | 107 | .Button { 108 | border-radius: 0; 109 | --control-bg: @control-bg-light; 110 | 111 | &--active { 112 | --control-bg: @control-bg; 113 | } 114 | } 115 | } 116 | 117 | &-ExtensionInfo { 118 | li, a { 119 | color: @control-color; 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /less/admin/Alert.less: -------------------------------------------------------------------------------- 1 | .Alert { 2 | &--info { 3 | background: @alert-info-bg; 4 | color: @alert-info-color; 5 | } 6 | 7 | &-controls:empty { 8 | display: none; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /less/admin/App.less: -------------------------------------------------------------------------------- 1 | .Asirem-adminWaves { 2 | display: none; 3 | } 4 | 5 | .App-nav-title { 6 | display: none; 7 | } 8 | 9 | @media @desktop-up { 10 | .App-nav { 11 | background-color: @admin-nav-bg; 12 | } 13 | 14 | .ThemeBase-Header-title { 15 | margin: 0; 16 | height: 90px; 17 | margin-bottom: 30px; 18 | display: flex; 19 | justify-content: center; 20 | align-items: center; 21 | } 22 | 23 | // Overspecification is bad, but we do it because core overspecifies, forcing our hand 24 | .App-nav .AdminNav-Main { 25 | padding: 0 15px; 26 | 27 | .Dropdown-menu > li { 28 | margin-bottom: 3px; 29 | .asirem-border-radius(~'> a > .icon'); 30 | 31 | &.active { 32 | > a { 33 | background-color: @primary-bg; 34 | color: @primary-color; 35 | } 36 | } 37 | 38 | &.active, &:hover { 39 | > a { 40 | > .icon { 41 | background-color: @admin-nav-bg; 42 | } 43 | } 44 | } 45 | 46 | > a { 47 | border-radius: 25px; 48 | padding: 8px !important; 49 | display: flex !important; 50 | align-items: center; 51 | transition: background-color 0.4s, color 0.4s; 52 | color: @admin-nav-color; 53 | 54 | > .icon { 55 | .asirem-icon-block(32px); 56 | font-size: 16px !important; 57 | margin: 0 !important; 58 | float: none; 59 | transition: background-color 0.4s; 60 | } 61 | 62 | > .Button-label { 63 | padding-left: 14px;; 64 | } 65 | } 66 | 67 | &.item-extensions { 68 | .Button-label { 69 | width: calc(~"100% - 32px"); 70 | display: block; 71 | } 72 | } 73 | } 74 | } 75 | 76 | .Asirem-adminWaves { 77 | display: block; 78 | position: absolute; 79 | transform: scaleY(1) scaleX(.15) rotate(2deg); 80 | top: 0; 81 | left: -8.3rem; 82 | z-index: -1; 83 | max-height: 100%; 84 | fill: @admin-nav-bg; 85 | } 86 | } 87 | 88 | @media @tablet { 89 | .App { 90 | &-header { 91 | padding-left: 0; 92 | padding-right: 0; 93 | } 94 | } 95 | 96 | .container { 97 | width: unset; 98 | max-width: 100%; 99 | } 100 | } 101 | 102 | &::-webkit-scrollbar { 103 | width: 8px; 104 | } 105 | /*& { 106 | scrollbar-width: thin; 107 | scrollbar-color: @control-color @control-color; 108 | }*/ 109 | &::-webkit-scrollbar-track { 110 | background: transparent; 111 | } 112 | &::-webkit-scrollbar-thumb { 113 | background-color: @control-color; 114 | border-radius: 6px; 115 | } 116 | -------------------------------------------------------------------------------- /less/admin/ExtensionsPage.less: -------------------------------------------------------------------------------- 1 | .ThemeBase-ExtensionCategory { 2 | .asirem-block(); 3 | padding: 15px; 4 | } 5 | 6 | .ThemeBase-Extension { 7 | --icon-size: 48px; 8 | padding: 6px 18px 6px 6px; 9 | border-radius: @border-radius; 10 | transition: border-color 0.4s, background-color 0.4s; 11 | color: inherit; 12 | .asirem-border-radius(~'.ExtensionIcon'); 13 | 14 | &:hover { 15 | text-decoration: none; 16 | background-color: @control-bg; 17 | } 18 | 19 | &-icon { 20 | .ExtensionIcon { 21 | width: var(--icon-size); 22 | height: var(--icon-size); 23 | font-size: calc(~"var(--icon-size) / 2.5"); 24 | box-shadow: @asirem-box-shadow; 25 | } 26 | } 27 | 28 | &-title { 29 | &-value { 30 | font-size: 14px; 31 | font-weight: 600; 32 | } 33 | } 34 | 35 | &-details { 36 | font-size: 12px; 37 | opacity: 0.6; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /less/admin/FormGroup.less: -------------------------------------------------------------------------------- 1 | .AdminPage:not(.AppearancePage) .Form-group, 2 | .AppearancePage fieldset { 3 | background: @body-bg; 4 | padding: 25px; 5 | border-radius: 14px; 6 | 7 | .Button { 8 | --control-bg: @control-bg; 9 | 10 | &--primary { 11 | --control-bg: @primary-color; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /less/admin/GroupPermissions.less: -------------------------------------------------------------------------------- 1 | .GroupPermissions { 2 | --padding: 15px; 3 | margin-top: 10px; 4 | 5 | &-header { 6 | color: @control-color; 7 | background-color: @body-bg; 8 | font-weight: 600; 9 | margin: 0; 10 | padding: var(--padding); 11 | } 12 | 13 | &-permission { 14 | display: flex; 15 | align-items: center; 16 | padding: 0 var(--padding); 17 | background-color: @body-bg; 18 | min-height: 42px; 19 | 20 | &-label { 21 | &-icon { 22 | color: @control-color; 23 | } 24 | } 25 | 26 | &-control { 27 | margin-left: auto; 28 | } 29 | } 30 | 31 | &-permissionGroup-items { 32 | display: flex; 33 | flex-direction: column; 34 | gap: 1px; 35 | } 36 | 37 | &-permissionGroup-label { 38 | color: @control-color; 39 | background-color: @control-bg-light; 40 | padding: 5px var(--padding); 41 | } 42 | 43 | .Checkbox { 44 | &-display { 45 | float: none; 46 | } 47 | } 48 | } 49 | 50 | 51 | .IconicText { 52 | display: flex; 53 | align-items: center; 54 | gap: 4px; 55 | 56 | &-icon { 57 | width: 22px; 58 | text-align: center; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /less/admin/PermissionsPage.less: -------------------------------------------------------------------------------- 1 | .PermissionsPage { 2 | &-groups { 3 | padding: 0; 4 | overflow: visible; 5 | } 6 | 7 | &-permissions { 8 | overflow: unset; 9 | padding-bottom: 30px; 10 | 11 | &-overflow { 12 | overflow: auto; 13 | padding-bottom: 200px; 14 | max-height: 85vh; 15 | } 16 | } 17 | } 18 | 19 | .Group { 20 | padding: 15px 15px 10px; 21 | width: unset; 22 | min-width: 90px; 23 | margin-right: 5px; 24 | .asirem-border-radius(~'.Group-icon'); 25 | 26 | &, .Button-label { 27 | display: inline-flex; 28 | flex-direction: column; 29 | align-items: center; 30 | justify-content: center; 31 | } 32 | 33 | &--add { 34 | margin: 0; 35 | } 36 | 37 | &--add &-icon { 38 | margin: 0; 39 | box-shadow: none; 40 | background: transparent; 41 | } 42 | 43 | &-icon { 44 | .asirem-icon-block(60px, false); 45 | background-image: linear-gradient(-45deg, rgba(0, 0, 0, 19%), transparent); 46 | } 47 | 48 | &-icon .icon, & &-icon { 49 | font-size: 18px; 50 | } 51 | 52 | .icon.fa-caret-down { 53 | display: none; 54 | } 55 | } 56 | 57 | .PermissionGrid td, .PermissionGrid th { 58 | background: @body-bg; 59 | padding: 10px 15px; 60 | } 61 | 62 | .PermissionGrid { 63 | border-collapse: separate; 64 | border-spacing: 0 1px; 65 | min-width: 100%; 66 | 67 | &-section { 68 | td, th { 69 | background: @control-bg-light; 70 | } 71 | } 72 | 73 | td { 74 | padding: 0; 75 | 76 | .Button { 77 | padding: 10px 15px; 78 | height: auto; 79 | border: 0; 80 | margin: 0; 81 | display: flex; 82 | align-items: center; 83 | border-radius: 0; 84 | } 85 | } 86 | 87 | tbody .Button .Badge { 88 | margin: 0; 89 | } 90 | 91 | tbody .Button-label { 92 | display: inline-flex; 93 | align-items: center; 94 | gap: 3px; 95 | } 96 | 97 | thead { 98 | td:first-child { 99 | border-top-left-radius: 14px; 100 | } 101 | th:last-child { 102 | border-top-right-radius: 14px; 103 | } 104 | } 105 | tbody:last-child { 106 | tr:last-child { 107 | th:first-child { 108 | border-bottom-left-radius: 14px; 109 | } 110 | td:last-child { 111 | border-bottom-right-radius: 14px; 112 | } 113 | } 114 | } 115 | 116 | thead td:first-child, tbody th { 117 | position: sticky; 118 | left: 0; 119 | z-index: 2; 120 | background-image: linear-gradient(to left, rgba(0,0,0,.1), transparent 3%); 121 | } 122 | 123 | thead th { 124 | position: sticky; 125 | top: 0; 126 | z-index: 1; 127 | background-image: linear-gradient(to top, rgba(0,0,0,.07), transparent 13%); 128 | } 129 | } 130 | 131 | .Asirem-PermissionsPage-scopes { 132 | display: flex; 133 | align-items: flex-end; 134 | border-radius: 14px; 135 | margin-bottom: 15px; 136 | 137 | &-controls { 138 | margin-left: auto; 139 | 140 | .Button { 141 | margin-left: 4px; 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /less/admin/StatisticsWidget.less: -------------------------------------------------------------------------------- 1 | .App { 2 | .StatisticsWidget { 3 | background: transparent; 4 | padding: 0; 5 | 6 | &-table { 7 | display: flex; 8 | flex-direction: column; 9 | flex-wrap: wrap; 10 | margin: 0; 11 | } 12 | 13 | &-labels { 14 | margin-right: auto; 15 | } 16 | 17 | &-entity, &-chart { 18 | .asirem-block(); 19 | } 20 | 21 | &-entity { 22 | margin-right: 8px; 23 | position: relative; 24 | overflow: hidden; 25 | z-index: 0; 26 | 27 | &:last-of-type { 28 | margin-right: 0; 29 | } 30 | 31 | &.active { 32 | border: 0; 33 | padding-top: 15px; 34 | background-color: @primary-color; 35 | color: @body-bg; 36 | 37 | .StatisticsWidget { 38 | &-heading { 39 | color: lighten(@primary-color, 30); 40 | } 41 | 42 | &-change { 43 | background-color: mix(@control-bg, @primary-color, 60); 44 | } 45 | } 46 | } 47 | 48 | &::after { 49 | font-family: 'Font Awesome 5 Free'; 50 | font-weight: 600; 51 | position: absolute; 52 | bottom: -38px; 53 | right: -12px; 54 | font-size: 5rem; 55 | color: @control-bg; 56 | transform: rotate(-21deg); 57 | z-index: -1; 58 | opacity: 0.6; 59 | } 60 | 61 | &:first-of-type::after { 62 | content: "\f500"; 63 | } 64 | &:nth-of-type(2)::after { 65 | content: "\f27a"; 66 | bottom: -20px; 67 | right: -2px; 68 | font-size: 4rem; 69 | } 70 | &:last-of-type::after { 71 | content: "\f086"; 72 | bottom: -26px; 73 | right: 0; 74 | font-size: 4rem; 75 | } 76 | } 77 | 78 | &-chart { 79 | width: 100%; 80 | margin: 8px 0 0 0; 81 | 82 | .chart-container .axis line, .chart-container .chart-label line { 83 | stroke: @control-bg !important; 84 | } 85 | } 86 | 87 | &-change { 88 | font-size: 85%; 89 | font-weight: 600; 90 | display: inline-block; 91 | padding: 0.1em 0.5em; 92 | border-radius: @border-radius; 93 | text-transform: none; 94 | font-size: 11px; 95 | vertical-align: middle; 96 | background-color: @control-bg; 97 | 98 | &--up { 99 | color: #00a502; 100 | } 101 | &--down { 102 | color: #d0011b; 103 | } 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /less/admin/StatusWidget.less: -------------------------------------------------------------------------------- 1 | .ThemeBase-StatusWidget { 2 | padding: 0; 3 | background-color: transparent; 4 | 5 | > ul { 6 | > li { 7 | padding: 8px; 8 | background-color: @body-bg; 9 | border-radius: @border-radius; 10 | .asirem-border-radius(~'.ThemeBase-StatusWidget-icon'); 11 | width: 25%; 12 | } 13 | } 14 | 15 | @media @phone { 16 | > ul { 17 | flex-wrap: wrap; 18 | 19 | > li { 20 | width: calc(~"100% / 2 - 8px"); 21 | flex-grow: 0; 22 | margin-bottom: 8px; 23 | } 24 | } 25 | } 26 | 27 | &-icon { 28 | .asirem-icon-block(45px); 29 | font-size: 20px; 30 | } 31 | 32 | &-value { 33 | font-weight: 600; 34 | } 35 | 36 | &-control { 37 | --size: 35px; 38 | font-size: 18px; 39 | width: var(--size); 40 | height: var(--size); 41 | 42 | &.Button { 43 | --control-color: @alert-info-bg; 44 | --control-bg: @alert-info-color; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /less/admin/UserListPage.less: -------------------------------------------------------------------------------- 1 | .UserListPage-grid { 2 | grid-gap: 1px 0; 3 | width: -webkit-fit-content; 4 | width: fit-content; 5 | 6 | &-header, 7 | // @todo rowItem 8 | &-rowItem { 9 | background: @body-bg; 10 | padding: 10px 15px; 11 | color: @control-color; 12 | } 13 | 14 | &-rowItem { 15 | &[data-column-name="avatar"], &[data-column-name="profileLink"] { 16 | padding: 0; 17 | } 18 | 19 | &[data-column-name="emailAddress"] { 20 | padding: 0 0 0 15px; 21 | } 22 | 23 | &[data-column-name="username"] { 24 | a { 25 | color: @control-color; 26 | } 27 | } 28 | } 29 | 30 | &-header { 31 | border-bottom: none; 32 | color: @control-color; 33 | 34 | &:nth-child(2) { 35 | padding: 10px 0; 36 | text-align: center; 37 | } 38 | } 39 | 40 | &-avatar { 41 | --size: 32px; 42 | width: var(--size); 43 | height: var(--size); 44 | line-height: 0; 45 | display: flex; 46 | align-items: center; 47 | justify-content: center; 48 | font-size: 17px; 49 | } 50 | } 51 | 52 | .UserList { 53 | &-email { 54 | height: 100%; 55 | align-items: center; 56 | 57 | &[data-email-shown="false"] { 58 | .UserList-emailAddress { 59 | color: transparent; 60 | background-color: @control-bg; 61 | filter: none; 62 | } 63 | } 64 | 65 | &Address { 66 | transition: color .2s ease-out, background-color .2s ease-out; 67 | } 68 | } 69 | } 70 | 71 | .UserList-emailIconBtn, 72 | .UserList-profileLinkBtn { 73 | border-radius: 0; 74 | height: 100%; 75 | align-items: center; 76 | display: flex; 77 | justify-content: center; 78 | } 79 | 80 | .UserListPage-stat { 81 | padding: 15px; 82 | background: @body-bg; 83 | color: @control-color; 84 | border-radius: 14px; 85 | min-width: 15%; 86 | 87 | &-container { 88 | margin-bottom: 10px; 89 | display: flex; 90 | } 91 | 92 | &-value { 93 | font-size: 1.4rem; 94 | } 95 | 96 | &-key { 97 | opacity: 0.7; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /less/admin/Widget.less: -------------------------------------------------------------------------------- 1 | .Widget { 2 | background-color: @body-bg; 3 | 4 | .Button { 5 | background: var(--control-bg); 6 | color: var(--control-color); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /less/admin/sideNav.less: -------------------------------------------------------------------------------- 1 | .sideNavOffset { 2 | background-color: @control-bg; 3 | 4 | .Button { 5 | --control-bg: @body-bg; 6 | 7 | &--primary { 8 | --control-bg: @primary-color; 9 | } 10 | } 11 | .Button--dashed { 12 | .Button--dashed(); 13 | } 14 | 15 | } 16 | 17 | .Button { 18 | background-color: var(--control-bg); 19 | color: var(--control-color); 20 | 21 | &--primary { 22 | --control-color: @control-bg; 23 | --control-bg: @primary-color; 24 | } 25 | } 26 | 27 | @media @tablet { 28 | .sideNav { 29 | padding-left: 15px; 30 | padding-right: 15px; 31 | } 32 | } 33 | 34 | @media @tablet, @phone { 35 | .sideNavOffset { 36 | padding-top: 15px; 37 | } 38 | } 39 | 40 | @media @desktop-up { 41 | .sideNav { 42 | margin-right: 0; 43 | height: 100vh; 44 | } 45 | 46 | .sideNavOffset { 47 | position: relative; 48 | padding-left: 1rem; 49 | z-index: 0; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /less/admin/variables.less: -------------------------------------------------------------------------------- 1 | @alert-info-bg: #aecbff; 2 | @alert-info-color: #006dad; 3 | 4 | :root { 5 | --control-bg: @control-bg; 6 | --control-color: @control-color; 7 | } 8 | -------------------------------------------------------------------------------- /less/common/App.less: -------------------------------------------------------------------------------- 1 | .App { 2 | overflow-x: visible; 3 | } 4 | -------------------------------------------------------------------------------- /less/common/Button.less: -------------------------------------------------------------------------------- 1 | .Button { 2 | border: 1px solid transparent; 3 | transition: background-color 0.4s; 4 | } 5 | 6 | .Button--dashed { 7 | border: 1px dashed; 8 | --control-bg: transparent; 9 | } 10 | -------------------------------------------------------------------------------- /less/common/Dropdown.less: -------------------------------------------------------------------------------- 1 | .Dropdown-menu { 2 | padding: 8px; 3 | 4 | > li { 5 | > a, > button, > span { 6 | border-radius: @border-radius; 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /less/common/common.less: -------------------------------------------------------------------------------- 1 | @import "variables.less"; 2 | @import "mixins/asirem-block.less"; 3 | @import "mixins/asirem-icon-block.less"; 4 | @import "mixins/asirem-border-radius.less"; 5 | 6 | @import "scaffolding.less"; 7 | @import "App.less"; 8 | @import "Button.less"; 9 | @import "Dropdown.less"; 10 | -------------------------------------------------------------------------------- /less/common/mixins/asirem-block.less: -------------------------------------------------------------------------------- 1 | .asirem-block(@bg: @body-bg) { 2 | border-radius: @border-radius; 3 | background: @bg; 4 | } 5 | -------------------------------------------------------------------------------- /less/common/mixins/asirem-border-radius.less: -------------------------------------------------------------------------------- 1 | .asirem-border-radius(@elem) { 2 | &:nth-child(2n) { 3 | @{elem} { 4 | border-radius: @asirem-border-radius-a; 5 | } 6 | } 7 | 8 | &:nth-child(2n+1) { 9 | @{elem} { 10 | border-radius: @asirem-border-radius-b; 11 | } 12 | } 13 | 14 | &:nth-child(3n) { 15 | @{elem} { 16 | border-radius: @asirem-border-radius-c; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /less/common/mixins/asirem-icon-block.less: -------------------------------------------------------------------------------- 1 | .asirem-icon-block(@size: 30px, @bg: @control-bg, @br: @border-radius) { 2 | --size: @size; 3 | width: var(--size); 4 | height: var(--size); 5 | display: flex; 6 | align-items: center; 7 | justify-content: center; 8 | border-radius: @br; 9 | box-shadow: @asirem-box-shadow; 10 | 11 | & when (isstring(@bg)) { 12 | background-color: @bg; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /less/common/scaffolding.less: -------------------------------------------------------------------------------- 1 | body { 2 | overflow-x: hidden; 3 | } -------------------------------------------------------------------------------- /less/common/variables.less: -------------------------------------------------------------------------------- 1 | @border-radius: 14px; 2 | @asirem-border-radius-a: ~"43% 57% 68% 32% / 33% 50% 50% 67%"; 3 | @asirem-border-radius-b: ~"42% 58% 33% 67% / 57% 49% 51% 43%"; 4 | @asirem-border-radius-c: ~"58% 42% 68% 32% / 48% 73% 27% 52%"; 5 | 6 | .define-asirem-colors(@config-dark-mode); 7 | .define-asirem-colors(false) { 8 | @primary-bg: lighten(@primary-color, 40); 9 | @control-bg-light: lighten(@control-bg, 3); 10 | 11 | @unapproved-discussion-bg: #fdddc2; 12 | } 13 | .define-asirem-colors(true) { 14 | @primary-bg: mix(@body-bg, @primary-color, 90); 15 | @control-bg-light: darken(@control-bg, 6); 16 | 17 | @unapproved-discussion-bg: #1f1914; 18 | } 19 | 20 | .define-asirem-header(@config-colored-header); 21 | .define-asirem-header(false) { 22 | @admin-nav-bg: @body-bg; 23 | @admin-nav-color: @control-color; 24 | } 25 | .define-asirem-header(true) { 26 | @admin-nav-bg: @body-bg; 27 | @admin-nav-color: @control-color; 28 | } 29 | 30 | @asirem-box-shadow: rgba(0, 0, 0, 0.25) 0px 2px 5px -1px, rgba(0, 0, 0, 0.3) 0px 1px 3px -1px; 31 | 32 | @screen-desktop-hd: 1200px; 33 | -------------------------------------------------------------------------------- /less/forum.less: -------------------------------------------------------------------------------- 1 | @import "common/common.less"; 2 | 3 | .define-colors(false) { 4 | @primary-color: @config-primary-color; 5 | @secondary-color: @config-secondary-color; 6 | 7 | @body-bg: #fff; 8 | @text-color: #111; 9 | @link-color: saturate(@primary-color, 10%); 10 | @heading-color: @text-color; 11 | @muted-color: hsl(@secondary-hue, min(20%, @secondary-sat), 50%); 12 | @muted-more-color: #aaa; 13 | @shadow-color: rgba(0, 0, 0, 0.35); 14 | 15 | @control-bg: hsl(@secondary-hue, min(50%, @secondary-sat), 93%); 16 | @control-color: @muted-color; 17 | @control-danger-bg: #fdd; 18 | @control-danger-color: #d66; 19 | 20 | @overlay-bg: fade(@secondary-color, 90%); 21 | 22 | @code-bg: darken(@body-bg, 3%); 23 | @code-color: lighten(@text-color, 30%); 24 | } 25 | .define-colors(true) { 26 | @primary-color: @config-primary-color; 27 | @secondary-color: @config-secondary-color; 28 | 29 | @body-bg: hsl(@secondary-hue, min(20%, @secondary-sat), 13%); 30 | @text-color: #ddd; 31 | @link-color: saturate(@primary-color, 10%); 32 | @heading-color: @text-color; 33 | @muted-color: hsl(@secondary-hue, min(15%, @secondary-sat), 50%); 34 | @muted-more-color: hsl(@secondary-hue, min(10%, @secondary-sat), 40%); 35 | @shadow-color: rgba(0, 0, 0, 0.5); 36 | 37 | @control-bg: hsl(@secondary-hue, min(20%, @secondary-sat), 10%); 38 | @control-color: @muted-color; 39 | @control-danger-bg: #411; 40 | @control-danger-color: #a88; 41 | 42 | @overlay-bg: fade(darken(@body-bg, 5%), 90%); 43 | 44 | @code-bg: darken(@body-bg, 3%); 45 | @code-color: #fff; 46 | } 47 | 48 | @import "forum/App.less"; 49 | @import "forum/DiscussionList.less"; 50 | @import "forum/Hero.less"; 51 | @import "forum/Post.less"; 52 | @import "forum/TagsPage.less"; 53 | @import "forum/sideNav.less"; 54 | @import "forum/Footer.less"; 55 | -------------------------------------------------------------------------------- /less/forum/App.less: -------------------------------------------------------------------------------- 1 | .App { 2 | padding-bottom: 0; 3 | 4 | &-footer { 5 | &:empty { 6 | display: none; 7 | } 8 | } 9 | } 10 | 11 | @media @tablet-up { 12 | .App { 13 | display: flex; 14 | flex-direction: column; 15 | 16 | &-content { 17 | flex-grow: 1; 18 | border-top: 0; 19 | } 20 | } 21 | 22 | .container { 23 | padding: 0; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /less/forum/DiscussionList.less: -------------------------------------------------------------------------------- 1 | .DiscussionList { 2 | @media @phone { 3 | margin: 0; 4 | } 5 | 6 | &-discussions > li { 7 | .asirem-border-radius(~'.Avatar'); 8 | } 9 | 10 | &Item { 11 | background: @control-bg; 12 | margin: 0 0 4px; 13 | padding: 0; 14 | border-radius: @border-radius; 15 | 16 | &-controls .Dropdown-toggle { 17 | border-radius: @border-radius; 18 | } 19 | 20 | &-info { 21 | font-size: 12px; 22 | } 23 | 24 | &--unapproved { 25 | background-color: @unapproved-discussion-bg; 26 | 27 | .DiscussionListItem-content { 28 | opacity: 1; 29 | } 30 | } 31 | 32 | &-badges { 33 | .Badge { 34 | --size: 20px; 35 | box-shadow: -1px 1px 2px #00000038; 36 | width: var(--size); 37 | height: var(--size); 38 | line-height: var(--size); 39 | 40 | &-icon { 41 | font-size: 10px; 42 | } 43 | } 44 | } 45 | 46 | &--unread &-title::after { 47 | --size: 8px; 48 | content: ""; 49 | height: var(--size); 50 | width: var(--size); 51 | line-height: var(--size); 52 | background: @primary-color; 53 | color: #fff; 54 | border-radius: 100%; 55 | font-size: 10px; 56 | display: inline-flex; 57 | align-items: center; 58 | justify-content: center; 59 | vertical-align: text-top; 60 | margin-top: 6px; 61 | margin-left: 5px; 62 | } 63 | } 64 | 65 | &-loadMore { 66 | margin-top: 5px; 67 | 68 | .Button { 69 | width: 100%; 70 | height: 100%; 71 | } 72 | } 73 | } 74 | 75 | .TagsLabel .TagLabel:not(.overspecifying__) { 76 | border-radius: @border-radius; 77 | margin-right: 5px; 78 | font-size: 11px; 79 | } 80 | 81 | .App { 82 | .IndexPage .DiscussionListItem, 83 | .UserPage .DiscussionListItem { 84 | &-info { 85 | > .item-tags { 86 | top: 0; 87 | right: 0; 88 | } 89 | } 90 | } 91 | 92 | .DiscussionListItem { 93 | &-info { 94 | display: flex; 95 | align-items: center; 96 | flex-wrap: wrap; 97 | } 98 | 99 | &-title { 100 | color: @text-color !important; 101 | } 102 | 103 | &-main { 104 | padding: 0; 105 | margin: 0; 106 | position: relative; 107 | } 108 | 109 | &-badges { 110 | margin: 0; 111 | width: auto; 112 | float: none; 113 | position: absolute; 114 | right: -7px; 115 | top: -4px; 116 | display: block; 117 | } 118 | 119 | &-author { 120 | float: none; 121 | margin: 0; 122 | display: block; 123 | 124 | &-container { 125 | margin-right: 15px; 126 | position: relative; 127 | 128 | > .tooltip { 129 | width: max-content; 130 | } 131 | } 132 | 133 | .Avatar { 134 | .asirem-icon-block(50px, @body-bg); 135 | } 136 | } 137 | 138 | &-content { 139 | display: flex; 140 | padding: 12px; 141 | } 142 | 143 | &-stats { 144 | margin-left: 10px; 145 | margin-right: 15px; 146 | } 147 | 148 | &-count { 149 | margin: 0; 150 | padding: 0; 151 | float: none; 152 | display: block; 153 | 154 | &::before { 155 | margin: 0 5px 0 0; 156 | padding: 0; 157 | float: none; 158 | } 159 | } 160 | } 161 | } 162 | 163 | @media @phone { 164 | .DiscussionListItem { 165 | .unread &-count { 166 | padding: 0 4px; 167 | } 168 | } 169 | } 170 | 171 | 172 | @media @tablet-up { 173 | .DiscussionListItem { 174 | .item-excerpt { 175 | margin-right: 0; 176 | width: 100%; 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /less/forum/Footer.less: -------------------------------------------------------------------------------- 1 | .ThemeBaseFooter { 2 | position: relative; 3 | } 4 | 5 | .Asirem-footerWaves { 6 | display: none; 7 | } 8 | 9 | @media @tablet-up { 10 | .Asirem-footerWaves { 11 | display: block; 12 | position: absolute; 13 | top: -5.1rem; 14 | left: 50%; 15 | z-index: -1; 16 | transform: scaleY(.35) translateX(-50%) translateY(-21rem); 17 | fill: @control-bg; 18 | max-width: 100%; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /less/forum/Hero.less: -------------------------------------------------------------------------------- 1 | .Hero { 2 | z-index: 0; 3 | margin: 15px auto 0; 4 | max-width: @screen-desktop-hd; 5 | border-radius: @border-radius; 6 | 7 | @media @desktop { 8 | max-width: @screen-desktop; 9 | } 10 | 11 | @media @tablet { 12 | max-width: @screen-tablet; 13 | } 14 | 15 | @media @phone { 16 | max-width: calc(~"100% - 30px"); 17 | margin-bottom: 15px; 18 | } 19 | 20 | &--banner { 21 | --banner-position: center; 22 | background-image: var(--banner-url); 23 | background-repeat: no-repeat; 24 | background-size: 100%; 25 | background-position: var(--banner-position); 26 | color: #fff; 27 | } 28 | 29 | .container { 30 | padding: 30px; 31 | } 32 | 33 | + .container { 34 | position: relative; 35 | } 36 | } 37 | 38 | .darkenBackground { 39 | border-radius: @border-radius; 40 | } 41 | -------------------------------------------------------------------------------- /less/forum/Post.less: -------------------------------------------------------------------------------- 1 | .Post { 2 | margin: 0 0 15px 0; 3 | background: @control-bg; 4 | border-radius: 8px; 5 | } 6 | 7 | .PostStream-item { 8 | &:not(:last-child) { 9 | border-bottom: none; 10 | } 11 | } 12 | 13 | .ReplyPlaceholder { 14 | margin-top: 30px; 15 | } 16 | 17 | .EventPost { 18 | background-color: transparent; 19 | 20 | .Post-footer { 21 | display: none; 22 | } 23 | } 24 | 25 | .EventPost .Post-actions, .Post--hidden:not(.revealContent) .Post-actions { 26 | margin-top: -30px; 27 | } 28 | -------------------------------------------------------------------------------- /less/forum/TagsPage.less: -------------------------------------------------------------------------------- 1 | .Asirem-TagTiles { 2 | padding: 0; 3 | margin: 0; 4 | list-style: none; 5 | display: grid; 6 | grid-template-columns: repeat(2, 50%); 7 | gap: 8px; 8 | 9 | @media @phone { 10 | grid-template-columns: 100%; 11 | } 12 | } 13 | 14 | .Asirem-TagTile { 15 | --tag-bg: @control-bg; 16 | --tag-color: @control-color; 17 | .asirem-border-radius(~".Asirem-TagTile-icon .icon"); 18 | display: flex; 19 | 20 | &.colored { 21 | --tag-color: @body-bg; 22 | } 23 | 24 | &:not(.colored) &-icon .icon { 25 | color: var(--tag-color); 26 | } 27 | 28 | &-content { 29 | display: flex; 30 | flex-direction: column; 31 | } 32 | 33 | &-name { 34 | margin: 0; 35 | } 36 | 37 | &-info { 38 | flex-grow: 1; 39 | display: grid; 40 | grid-template-columns: 88px 1fr; 41 | color: var(--tag-color); 42 | background: var(--tag-bg); 43 | border-radius: @border-radius; 44 | padding: 16px 16px 16px 0; 45 | 46 | &:hover { 47 | text-decoration: none; 48 | } 49 | } 50 | 51 | &-icon { 52 | display: flex; 53 | justify-content: center; 54 | align-items: flex-start; 55 | 56 | .icon { 57 | .asirem-icon-block(50px); 58 | font-size: 28px; 59 | background-color: @body-bg; 60 | color: var(--tag-bg); 61 | } 62 | } 63 | 64 | &-lastPostedDiscussion { 65 | margin-top: auto; 66 | font-weight: bold; 67 | color: var(--tag-color); 68 | opacity: 0.6; 69 | display: grid; 70 | grid-template-columns: auto auto; 71 | 72 | &-title { 73 | overflow: hidden; 74 | text-overflow: ellipsis; 75 | white-space: nowrap; 76 | display: block; 77 | } 78 | 79 | time { 80 | margin-left: auto; 81 | font-style: italic; 82 | } 83 | } 84 | 85 | &-children + &-lastPostedDiscussion { 86 | padding-top: 16px; 87 | } 88 | } 89 | 90 | .Asirem-TagCloud { 91 | text-align: center; 92 | margin: 16px 0; 93 | } 94 | -------------------------------------------------------------------------------- /less/forum/sideNav.less: -------------------------------------------------------------------------------- 1 | .sideNavOffset, .sideNav > ul { 2 | margin-top: 0; 3 | } 4 | 5 | .AfruxWidgets-sideNavAlt { 6 | padding-top: 0; 7 | } 8 | 9 | @media @desktop-up { 10 | .sideNav { 11 | .Dropdown--select .Dropdown-menu { 12 | > li { 13 | margin: 2px 0; 14 | 15 | > a { 16 | --icon-size: 30px; 17 | padding: 6px; 18 | display: grid; 19 | grid-template-columns: var(--icon-size) 1fr; 20 | align-items: center; 21 | border-radius: 25px; 22 | transition: background-color 0.4s, color 0.4s; 23 | font-size: 14px; 24 | grid-gap: 10px; 25 | 26 | .Button-icon { 27 | .asirem-icon-block(var(--icon-size)); 28 | float: none; 29 | margin: 0; 30 | border-radius: 100%; 31 | transition: background-color 0.4s; 32 | } 33 | 34 | .Button-label { 35 | overflow: hidden; 36 | text-overflow: ellipsis; 37 | } 38 | } 39 | 40 | &.active { 41 | > a { 42 | background-color: @primary-bg; 43 | 44 | .Button-icon { 45 | background-color: @body-bg; 46 | } 47 | } 48 | } 49 | 50 | &:hover { 51 | > a { 52 | background-color: @control-bg; 53 | color: @text-color; 54 | 55 | .Button-icon { 56 | background-color: @body-bg; 57 | } 58 | } 59 | } 60 | } 61 | > li[class^="item-tag"]:not(.item-tags) a { 62 | font-size: 13px; 63 | 64 | .icon { 65 | --size: 24px; 66 | } 67 | } 68 | > .Dropdown-separator { 69 | margin: 12px 0; 70 | } 71 | } 72 | } 73 | 74 | .sideNavContainer { 75 | margin-top: 30px; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /locale/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afrux/asirem/31e5baa5d009a44b7c74d6e68aafcd5f9822654c/locale/.gitkeep -------------------------------------------------------------------------------- /views/frontend/admin.blade.php: -------------------------------------------------------------------------------- 1 | @extends('afrux-theme-base::frontend.admin') 2 | 3 | @section('separator') 4 | 5 | 6 | 7 | @endsection 8 | --------------------------------------------------------------------------------