1
0
mirror of https://github.com/flarum/core.git synced 2025-08-04 23:47:32 +02:00

WIP: set up webpack, TypeScript environment, start JS app refactor

This commit is contained in:
Toby Zerner
2017-06-24 23:00:43 +09:30
parent 5b68b80e73
commit 8b3971f202
191 changed files with 2716 additions and 60095 deletions

2
.gitignore vendored
View File

@@ -5,3 +5,5 @@ Thumbs.db
tests/_output/*
.vagrant
.idea/*
node_modules
bower_components

1
js/.gitignore vendored
View File

@@ -1 +0,0 @@
bower_components

2
js/admin/.gitignore vendored
View File

@@ -1 +1 @@
node_modules
dist

View File

@@ -1,31 +0,0 @@
var gulp = require('flarum-gulp');
var bowerDir = '../bower_components';
gulp({
includeHelpers: true,
files: [
bowerDir + '/es6-micro-loader/dist/system-polyfill.js',
bowerDir + '/mithril/mithril.js',
bowerDir + '/m.attrs.bidi/bidi.js',
bowerDir + '/jquery/dist/jquery.js',
bowerDir + '/moment/moment.js',
bowerDir + '/bootstrap/js/affix.js',
bowerDir + '/bootstrap/js/dropdown.js',
bowerDir + '/bootstrap/js/modal.js',
bowerDir + '/bootstrap/js/tooltip.js',
bowerDir + '/bootstrap/js/transition.js',
bowerDir + '/spin.js/spin.js',
bowerDir + '/spin.js/jquery.spin.js'
],
modules: {
'flarum': [
'src/**/*.js',
'../lib/**/*.js'
]
},
outputFile: 'dist/app.js'
});

25004
js/admin/dist/app.js vendored

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +0,0 @@
{
"private": true,
"devDependencies": {
"gulp": "^3.9.1",
"flarum-gulp": "^0.2.0"
}
}

View File

@@ -0,0 +1,53 @@
import * as m from 'mithril';
import Application from './lib/Application';
import routes from './routes';
import Nav from './components/Nav';
export default class AdminApplication extends Application {
/**
* A map of extension names to their settings callbacks.
*
* @type {Object}
*/
extensionSettings = {};
/**
* Construct a list of permissions required to have the given permission.
*
* @param {String} permission
* @return {Array}
*/
getRequiredPermissions(permission) {
const required = [];
if (permission === 'startDiscussion' || permission.indexOf('discussion.') === 0) {
required.push('viewDiscussions');
}
if (permission === 'discussion.delete') {
required.push('discussion.hide');
}
if (permission === 'discussion.deletePosts') {
required.push('discussion.editPosts');
}
return required;
}
/**
* @inheritdoc
*/
mount() {
m.route.prefix('#');
super.mount();
m.mount(document.getElementById('nav'), <Nav/>);
}
/**
* @inheritdoc
*/
registerDefaultRoutes(router) {
routes(router);
}
}

View File

@@ -1,33 +0,0 @@
import App from 'flarum/App';
import store from 'flarum/initializers/store';
import preload from 'flarum/initializers/preload';
import routes from 'flarum/initializers/routes';
import boot from 'flarum/initializers/boot';
const app = new App();
app.initializers.add('store', store);
app.initializers.add('routes', routes);
app.initializers.add('preload', preload, -100);
app.initializers.add('boot', boot, -100);
app.extensionSettings = {};
app.getRequiredPermissions = function(permission) {
const required = [];
if (permission === 'startDiscussion' || permission.indexOf('discussion.') === 0) {
required.push('viewDiscussions');
}
if (permission === 'discussion.delete') {
required.push('discussion.hide');
}
if (permission === 'discussion.deletePosts') {
required.push('discussion.editPosts');
}
return required;
};
export default app;

View File

@@ -1,30 +0,0 @@
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import Modal from 'flarum/components/Modal';
export default class AddExtensionModal extends Modal {
className() {
return 'AddExtensionModal Modal--small';
}
title() {
return app.translator.trans('core.admin.add_extension.title');
}
content() {
return (
<div className="Modal-body">
<p>{app.translator.trans('core.admin.add_extension.temporary_text')}</p>
<p>{app.translator.trans('core.admin.add_extension.install_text', {a: <a href="https://discuss.flarum.org/t/extensions" target="_blank"/>})}</p>
<p>{app.translator.trans('core.admin.add_extension.developer_text', {a: <a href="http://flarum.org/docs/extend" target="_blank"/>})}</p>
</div>
);
}
}

View File

@@ -0,0 +1,40 @@
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import flarum from 'flarum';
import Modal from 'flarum/lib/components/Modal';
export default class AddExtensionModal extends Modal {
/**
* @inheritdoc
*/
className() {
return super.className() + ' AddExtensionModal Modal--small';
}
/**
* @inheritdoc
*/
title() {
return flarum.translator.trans('admin.add_extension.title');
}
/**
* @inheritdoc
*/
content() {
return (
<div className="Modal-body">
<p>{flarum.translator.trans('admin.add_extension.temporary_text')}</p>
<p>{flarum.translator.trans('admin.add_extension.install_text', {a: <a href="https://discuss.flarum.org/t/extensions" target="_blank"/>})}</p>
<p>{flarum.translator.trans('admin.add_extension.developer_text', {a: <a href="http://flarum.org/docs/extend" target="_blank"/>})}</p>
</div>
);
}
}

View File

@@ -1,24 +0,0 @@
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import LinkButton from 'flarum/components/LinkButton';
export default class AdminLinkButton extends LinkButton {
getButtonContent() {
const content = super.getButtonContent();
content.push(
<div className="AdminLinkButton-description">
{this.props.description}
</div>
);
return content;
}
}

View File

@@ -0,0 +1,33 @@
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import LinkButton from 'flarum/lib/components/LinkButton';
/**
*
*/
export default class AdminLinkButton extends LinkButton {
/**
* @inheritdoc
*/
className() {
return super.className() + ' AdminLinkButton';
}
/**
* @inheritdoc
*/
content() {
const content = super.content();
content.push(<div className="AdminLinkButton-description">{this.attrs.description}</div>);
return content;
}
}

View File

@@ -1,79 +0,0 @@
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import Component from 'flarum/Component';
import AdminLinkButton from 'flarum/components/AdminLinkButton';
import SelectDropdown from 'flarum/components/SelectDropdown';
import ItemList from 'flarum/utils/ItemList';
export default class AdminNav extends Component {
view() {
return (
<SelectDropdown
className="AdminNav App-titleControl"
buttonClassName="Button"
children={this.items().toArray()}
/>
);
}
/**
* Build an item list of links to show in the admin navigation.
*
* @return {ItemList}
*/
items() {
const items = new ItemList();
items.add('dashboard', AdminLinkButton.component({
href: app.route('dashboard'),
icon: 'bar-chart',
children: app.translator.trans('core.admin.nav.dashboard_button'),
description: app.translator.trans('core.admin.nav.dashboard_text')
}));
items.add('basics', AdminLinkButton.component({
href: app.route('basics'),
icon: 'pencil',
children: app.translator.trans('core.admin.nav.basics_button'),
description: app.translator.trans('core.admin.nav.basics_text')
}));
items.add('mail', AdminLinkButton.component({
href: app.route('mail'),
icon: 'envelope',
children: app.translator.trans('core.admin.nav.email_button'),
description: app.translator.trans('core.admin.nav.email_text')
}));
items.add('permissions', AdminLinkButton.component({
href: app.route('permissions'),
icon: 'key',
children: app.translator.trans('core.admin.nav.permissions_button'),
description: app.translator.trans('core.admin.nav.permissions_text')
}));
items.add('appearance', AdminLinkButton.component({
href: app.route('appearance'),
icon: 'paint-brush',
children: app.translator.trans('core.admin.nav.appearance_button'),
description: app.translator.trans('core.admin.nav.appearance_text')
}));
items.add('extensions', AdminLinkButton.component({
href: app.route('extensions'),
icon: 'puzzle-piece',
children: app.translator.trans('core.admin.nav.extensions_button'),
description: app.translator.trans('core.admin.nav.extensions_text')
}));
return items;
}
}

View File

@@ -0,0 +1,52 @@
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import Component from 'flarum/lib/Component';
import ItemList from 'flarum/lib/utils/ItemList';
import AdminLinkButton from 'flarum/components/AdminLinkButton';
import SelectDropdown from 'flarum/components/SelectDropdown';
function addLink(items, route, icon) {
items.add(route, <AdminLinkButton
href={flarum.router.to(route)}
icon={icon}
children={flarum.translator.trans(`admin.nav.${route}_button`)}
description={flarum.translator.trans(`admin.nav.${route}_text`)}/>);
}
/**
*
*/
export default class Nav extends Component {
/**
* @inheritdoc
*/
view() {
return <SelectDropdown className="Nav" buttonClassName="Button" children={this.items().toArray()}/>;
}
/**
* Build an item list of links to show in the admin navigation.
*
* @return {ItemList}
* @public
*/
items() {
const items = new ItemList();
addLink(items, 'dashboard', 'bar-chart'));
addLink(items, 'basics', 'pencil'));
addLink(items, 'mail', 'envelope'));
addLink(items, 'permissions', 'key'));
addLink(items, 'appearance', 'paint-brush'));
addLink(items, 'extensions', 'puzzle-piece'));
return items;
}
}

View File

@@ -0,0 +1,25 @@
import Component from 'flarum/Component';
import ItemList from 'flarum/utils/ItemList';
import SessionDropdown from './SessionDropdown';
export default class Header extends Component {
/**
* @inheritdoc
*/
view() {
return this.items().toVnodes();
}
/**
* Build an item list for the header contents.
*
* @return {ItemList}
*/
items() {
const items = new ItemList();
items.add('session', <SessionDropdown/>);
return items;
}
}

View File

@@ -1,37 +0,0 @@
import Component from 'flarum/Component';
import SessionDropdown from 'flarum/components/SessionDropdown';
import ItemList from 'flarum/utils/ItemList';
import listItems from 'flarum/helpers/listItems';
/**
* The `HeaderSecondary` component displays secondary header controls.
*/
export default class HeaderSecondary extends Component {
view() {
return (
<ul className="Header-controls">
{listItems(this.items().toArray())}
</ul>
);
}
config(isInitialized, context) {
// Since this component is 'above' the content of the page (that is, it is a
// part of the global UI that persists between routes), we will flag the DOM
// to be retained across route changes.
context.retain = true;
}
/**
* Build an item list for the controls.
*
* @return {ItemList}
*/
items() {
const items = new ItemList();
items.add('session', SessionDropdown.component());
return items;
}
}

9
js/admin/src/index.ts Normal file
View File

@@ -0,0 +1,9 @@
import AdminApplication from './AdminApplication';
export const app = new AdminApplication();
export const extensions = [];
// Export public API
// export { default as Extend } from './Extend';
// export { IndexPage, DicsussionList } from './components';

View File

@@ -1,66 +0,0 @@
/*global FastClick*/
import ScrollListener from 'flarum/utils/ScrollListener';
import Drawer from 'flarum/utils/Drawer';
import mapRoutes from 'flarum/utils/mapRoutes';
import Navigation from 'flarum/components/Navigation';
import HeaderPrimary from 'flarum/components/HeaderPrimary';
import HeaderSecondary from 'flarum/components/HeaderSecondary';
import AdminNav from 'flarum/components/AdminNav';
import ModalManager from 'flarum/components/ModalManager';
import AlertManager from 'flarum/components/AlertManager';
/**
* The `boot` initializer boots up the admin app. It initializes some app
* globals, mounts components to the page, and begins routing.
*
* @param {ForumApp} app
*/
export default function boot(app) {
m.startComputation();
m.mount(document.getElementById('app-navigation'), Navigation.component({className: 'App-backControl', drawer: true}));
m.mount(document.getElementById('header-navigation'), Navigation.component());
m.mount(document.getElementById('header-primary'), HeaderPrimary.component());
m.mount(document.getElementById('header-secondary'), HeaderSecondary.component());
m.mount(document.getElementById('admin-navigation'), AdminNav.component());
app.drawer = new Drawer();
app.modal = m.mount(document.getElementById('modal'), ModalManager.component());
app.alerts = m.mount(document.getElementById('alerts'), AlertManager.component());
app.history = {
canGoBack: () => true,
getPrevious: () => {},
backUrl: () => app.forum.attribute('baseUrl'),
back: function() {
window.location = this.backUrl();
}
};
m.route.mode = 'hash';
m.route(document.getElementById('content'), '/', mapRoutes(app.routes));
m.endComputation();
// Add a class to the body which indicates that the page has been scrolled
// down.
new ScrollListener(top => {
const $app = $('#app');
const offset = $app.offset().top;
$app
.toggleClass('affix', top >= offset)
.toggleClass('scrolled', top > offset);
}).start();
app.booted = true;
// If an extension has just been enabled, then we will run its settings
// callback.
const enabled = localStorage.getItem('enabledExtension');
if (enabled && app.extensionSettings[enabled]) {
app.extensionSettings[enabled]();
localStorage.removeItem('enabledExtension');
}
}

View File

@@ -1,22 +0,0 @@
import DashboardPage from 'flarum/components/DashboardPage';
import BasicsPage from 'flarum/components/BasicsPage';
import PermissionsPage from 'flarum/components/PermissionsPage';
import AppearancePage from 'flarum/components/AppearancePage';
import ExtensionsPage from 'flarum/components/ExtensionsPage';
import MailPage from 'flarum/components/MailPage';
/**
* The `routes` initializer defines the admin app's routes.
*
* @param {App} app
*/
export default function(app) {
app.routes = {
'dashboard': {path: '/', component: DashboardPage.component()},
'basics': {path: '/basics', component: BasicsPage.component()},
'permissions': {path: '/permissions', component: PermissionsPage.component()},
'appearance': {path: '/appearance', component: AppearancePage.component()},
'extensions': {path: '/extensions', component: ExtensionsPage.component()},
'mail': {path: '/mail', component: MailPage.component()}
};
}

1
js/admin/src/lib Symbolic link
View File

@@ -0,0 +1 @@
/Users/toby/Projects/Flarum/app/packages/core/js/lib

15
js/admin/src/routes.ts Normal file
View File

@@ -0,0 +1,15 @@
import DashboardPage from './components/DashboardPage';
import BasicsPage from './components/BasicsPage';
import PermissionsPage from './components/PermissionsPage';
import AppearancePage from './components/AppearancePage';
import ExtensionsPage from './components/ExtensionsPage';
import MailPage from './components/MailPage';
export default function(router) {
router.add('dashboard', '/', DashboardPage);
router.add('basics', '/basics', BasicsPage);
router.add('permissions', '/permissions', PermissionsPage);
router.add('appearance', '/appearance', AppearancePage);
router.add('extensions', '/extensions', ExtensionsPage);
router.add('mail', '/mail', MailPage);
}

View File

@@ -1,14 +0,0 @@
export default function saveSettings(settings) {
const oldSettings = JSON.parse(JSON.stringify(app.data.settings));
Object.assign(app.data.settings, settings);
return app.request({
method: 'POST',
url: app.forum.attribute('apiUrl') + '/settings',
data: settings
}).catch(error => {
app.data.settings = oldSettings;
throw error;
});
}

View File

@@ -0,0 +1,22 @@
import flarum from 'flarum';
/**
* Make a request to save the given settings to the database.
*
* @param {Object} settings
* @return {Promise}
*/
export default function saveSettings(settings) {
const oldSettings = JSON.parse(JSON.stringify(flarum.data.settings));
Object.assign(flarum.data.settings, settings);
return flarum.ajax.request({
method: 'POST',
url: flarum.forum.apiUrl + '/settings',
data: settings
}).catch(error => {
flarum.data.settings = oldSettings;
throw error;
});
}

23
js/admin/tsconfig.json Normal file
View File

@@ -0,0 +1,23 @@
{
"compilerOptions": {
"outDir": "./dist/",
"sourceMap": true,
"noImplicitAny": false,
"module": "commonjs",
"target": "es5",
"jsx": "react",
"jsxFactory": "m",
"declaration": true,
"lib": ["dom", "es2015"],
"types": [
"mithril",
"classnames"
]
},
"include": [
"./src/**/*"
],
"exclude": [
"node_modules"
]
}

View File

@@ -0,0 +1,30 @@
const path = require('path');
const { CheckerPlugin } = require('awesome-typescript-loader');
module.exports = {
entry: path.resolve(__dirname, 'src/index.tsx'),
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx']
},
devtool: 'source-map',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
library: 'flarum',
libraryTarget: 'var'
},
module: {
loaders: [
{
test: /\.tsx?$/,
loader: 'awesome-typescript-loader'
}
]
},
plugins: [
new CheckerPlugin()
],
externals: {
mithril: 'm'
}
};

View File

@@ -1,17 +0,0 @@
{
"name": "flarum",
"dependencies": {
"jquery": "~3.1.1",
"jquery.hotkeys": "jeresig/jquery.hotkeys#0.2.0",
"bootstrap": "~3.3.2",
"spin.js": "~2.0.1",
"moment": "~2.8.4",
"color-thief": "v2.0",
"mithril": "lhorie/mithril.js#v0.2.5",
"es6-micro-loader": "caridy/es6-micro-loader#v0.2.1",
"fastclick": "~1.0.6",
"autolink": "~1.0.0",
"m.attrs.bidi": "tobscure/m.attrs.bidi",
"punycode": "http://cdnjs.cloudflare.com/ajax/libs/punycode/1.4.1/punycode.js"
}
}

2
js/forum/.gitignore vendored
View File

@@ -1 +1 @@
node_modules
dist

View File

@@ -1,36 +0,0 @@
var gulp = require('flarum-gulp');
var bowerDir = '../bower_components';
gulp({
includeHelpers: true,
files: [
bowerDir + '/es6-micro-loader/dist/system-polyfill.js',
bowerDir + '/mithril/mithril.js',
bowerDir + '/m.attrs.bidi/bidi.js',
bowerDir + '/jquery/dist/jquery.js',
bowerDir + '/jquery.hotkeys/jquery.hotkeys.js',
bowerDir + '/color-thief/src/color-thief.js',
bowerDir + '/moment/moment.js',
bowerDir + '/autolink/autolink-min.js',
bowerDir + '/bootstrap/js/affix.js',
bowerDir + '/bootstrap/js/dropdown.js',
bowerDir + '/bootstrap/js/modal.js',
bowerDir + '/bootstrap/js/tooltip.js',
bowerDir + '/bootstrap/js/transition.js',
bowerDir + '/spin.js/spin.js',
bowerDir + '/spin.js/jquery.spin.js',
bowerDir + '/fastclick/lib/fastclick.js',
bowerDir + '/punycode/index.js'
],
modules: {
'flarum': [
'src/**/*.js',
'../lib/**/*.js'
]
},
outputFile: 'dist/app.js'
});

33996
js/forum/dist/app.js vendored

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +0,0 @@
{
"private": true,
"devDependencies": {
"gulp": "^3.9.1",
"flarum-gulp": "^0.2.0"
}
}

View File

@@ -1,57 +1,46 @@
import History from 'flarum/utils/History';
import App from 'flarum/App';
import Search from 'flarum/components/Search';
import Composer from 'flarum/components/Composer';
import ReplyComposer from 'flarum/components/ReplyComposer';
import DiscussionPage from 'flarum/components/DiscussionPage';
import SignUpModal from 'flarum/components/SignUpModal';
import * as m from 'mithril';
import Application from './lib/Application';
import routes from './routes';
import Nav from './components/Nav';
export default class ForumApp extends App {
constructor(...args) {
super(...args);
export default class ForumApplication extends Application {
/**
* A map of notification types to their components.
*
* @type {Object}
*/
notificationComponents = {};
/**
* The app's history stack, which keeps track of which routes the user visits
* so that they can easily navigate back to the previous route.
*
* @type {History}
*/
this.history = new History();
/**
* A map of post types to their components.
*
* @type {Object}
*/
postComponents = {};
/**
* An object which controls the state of the page's side pane.
*
* @type {Pane}
*/
this.pane = null;
/**
* The page's search component instance.
*
* @type {SearchBox}
*/
search = new Search();
/**
* The page's search component instance.
*
* @type {SearchBox}
*/
this.search = new Search();
/**
* @inheritdoc
*/
mount() {
m.route.prefix('#');
/**
* An object which controls the state of the page's drawer.
*
* @type {Drawer}
*/
this.drawer = null;
super.mount();
/**
* A map of post types to their components.
*
* @type {Object}
*/
this.postComponents = {};
m.mount(document.getElementById('nav'), <Nav/>);
}
/**
* A map of notification types to their components.
*
* @type {Object}
*/
this.notificationComponents = {};
/**
* @inheritdoc
*/
registerDefaultRoutes(router) {
routes(router);
}
/**

View File

@@ -1,21 +0,0 @@
import ForumApp from 'flarum/ForumApp';
import store from 'flarum/initializers/store';
import preload from 'flarum/initializers/preload';
import routes from 'flarum/initializers/routes';
import components from 'flarum/initializers/components';
import humanTime from 'flarum/initializers/humanTime';
import boot from 'flarum/initializers/boot';
import alertEmailConfirmation from 'flarum/initializers/alertEmailConfirmation';
const app = new ForumApp();
app.initializers.add('store', store);
app.initializers.add('routes', routes);
app.initializers.add('components', components);
app.initializers.add('humanTime', humanTime);
app.initializers.add('preload', preload, -100);
app.initializers.add('boot', boot, -100);
app.initializers.add('alertEmailConfirmation', alertEmailConfirmation, -100);
export default app;

View File

@@ -1,33 +0,0 @@
import Component from 'flarum/Component';
import ItemList from 'flarum/utils/ItemList';
import listItems from 'flarum/helpers/listItems';
/**
* The `HeaderPrimary` component displays primary header controls. On the
* default skin, these are shown just to the right of the forum title.
*/
export default class HeaderPrimary extends Component {
view() {
return (
<ul className="Header-controls">
{listItems(this.items().toArray())}
</ul>
);
}
config(isInitialized, context) {
// Since this component is 'above' the content of the page (that is, it is a
// part of the global UI that persists between routes), we will flag the DOM
// to be retained across route changes.
context.retain = true;
}
/**
* Build an item list for the controls.
*
* @return {ItemList}
*/
items() {
return new ItemList();
}
}

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