Skip to content

Commit

Permalink
Mithril 2 Update (#93)
Browse files Browse the repository at this point in the history
Update for Mithril 2

- TagLinkButtons now have children passed in, even though those children are not directly shown. This is because those children are used if that TagLinkButton is the active element in a dropdown.
- Since `m.redraw.strategy('all')` is no longer an option, we use keys to force a full-page rerender after rearranging tag order in the admin dashboard
  • Loading branch information
askvortsov1 authored Sep 24, 2020
1 parent 9625d22 commit dfeeaa6
Show file tree
Hide file tree
Showing 17 changed files with 205 additions and 207 deletions.
7 changes: 3 additions & 4 deletions js/src/admin/addTagsPane.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,15 @@ import AdminLinkButton from 'flarum/components/AdminLinkButton';
import TagsPage from './components/TagsPage';

export default function() {
app.routes.tags = {path: '/tags', component: TagsPage.component()};
app.routes.tags = {path: '/tags', component: TagsPage};

app.extensionSettings['flarum-tags'] = () => m.route(app.route('tags'));
app.extensionSettings['flarum-tags'] = () => m.route.set(app.route('tags'));

extend(AdminNav.prototype, 'items', items => {
items.add('tags', AdminLinkButton.component({
href: app.route('tags'),
icon: 'fas fa-tags',
children: app.translator.trans('flarum-tags.admin.nav.tags_button'),
description: app.translator.trans('flarum-tags.admin.nav.tags_text')
}));
}, app.translator.trans('flarum-tags.admin.nav.tags_button')));
});
}
17 changes: 5 additions & 12 deletions js/src/admin/addTagsPermissionScope.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,18 +48,11 @@ export default function() {
const tags = sortTags(app.store.all('tags').filter(tag => !tag.isRestricted()));

if (tags.length) {
items.add('tag', Dropdown.component({
className: 'Dropdown--restrictByTag',
buttonClassName: 'Button Button--text',
label: app.translator.trans('flarum-tags.admin.permissions.restrict_by_tag_heading'),
icon: 'fas fa-plus',
caretIcon: null,
children: tags.map(tag => Button.component({
icon: true,
children: [tagIcon(tag, {className: 'Button-icon'}), ' ', tag.name()],
onclick: () => tag.save({isRestricted: true})
}))
}));
items.add('tag', <Dropdown className='Dropdown--restrictByTag' buttonClassName='Button Button--text' label={app.translator.trans('flarum-tags.admin.permissions.restrict_by_tag_heading')} icon='fas fa-plus' caretIcon={null}>
{tags.map(tag => <Button icon={true} onclick={() => tag.save({ isRestricted: true })}>
{[tagIcon(tag, { className: 'Button-icon' }), ' ', tag.name()]}
</Button>)}
</Dropdown>);
}
});
}
31 changes: 15 additions & 16 deletions js/src/admin/components/EditTagModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@ import tagLabel from '../../common/helpers/tagLabel';
* to create or edit a tag.
*/
export default class EditTagModal extends Modal {
init() {
super.init();
oninit(vnode) {
super.oninit(vnode);

this.tag = this.props.tag || app.store.createRecord('tags');
this.tag = this.attrs.model || app.store.createRecord('tags');

this.name = m.prop(this.tag.name() || '');
this.slug = m.prop(this.tag.slug() || '');
this.description = m.prop(this.tag.description() || '');
this.color = m.prop(this.tag.color() || '');
this.icon = m.prop(this.tag.icon() || '');
this.isHidden = m.prop(this.tag.isHidden() || false);
this.name = m.stream(this.tag.name() || '');
this.slug = m.stream(this.tag.slug() || '');
this.description = m.stream(this.tag.description() || '');
this.color = m.stream(this.tag.color() || '');
this.icon = m.stream(this.tag.icon() || '');
this.isHidden = m.stream(this.tag.isHidden() || false);
}

className() {
Expand Down Expand Up @@ -60,31 +60,31 @@ export default class EditTagModal extends Modal {

items.add('slug', <div className="Form-group">
<label>{app.translator.trans('flarum-tags.admin.edit_tag.slug_label')}</label>
<input className="FormControl" value={this.slug()} oninput={m.withAttr('value', this.slug)}/>
<input className="FormControl" bidi={this.slug}/>
</div>, 40);

items.add('description', <div className="Form-group">
<label>{app.translator.trans('flarum-tags.admin.edit_tag.description_label')}</label>
<textarea className="FormControl" value={this.description()} oninput={m.withAttr('value', this.description)}/>
<textarea className="FormControl" bidi={this.description}/>
</div>, 30);

items.add('color', <div className="Form-group">
<label>{app.translator.trans('flarum-tags.admin.edit_tag.color_label')}</label>
<input className="FormControl" placeholder="#aaaaaa" value={this.color()} oninput={m.withAttr('value', this.color)}/>
<input className="FormControl" placeholder="#aaaaaa" bidi={this.color}/>
</div>, 20);

items.add('icon', <div className="Form-group">
<label>{app.translator.trans('flarum-tags.admin.edit_tag.icon_label')}</label>
<div className="helpText">
{app.translator.trans('flarum-tags.admin.edit_tag.icon_text', {a: <a href="https://fontawesome.com/icons?m=free" tabindex="-1"/>})}
</div>
<input className="FormControl" placeholder="fas fa-bolt" value={this.icon()} oninput={m.withAttr('value', this.icon)}/>
<input className="FormControl" placeholder="fas fa-bolt" bidi={this.icon}/>
</div>, 10);

items.add('hidden', <div className="Form-group">
<div>
<label className="checkbox">
<input type="checkbox" value="1" checked={this.isHidden()} onchange={m.withAttr('checked', this.isHidden)}/>
<input type="checkbox" bidi={this.isHidden()}/>
{app.translator.trans('flarum-tags.admin.edit_tag.hide_label')}
</label>
</div>
Expand All @@ -95,8 +95,7 @@ export default class EditTagModal extends Modal {
type: 'submit',
className: 'Button Button--primary EditTagModal-save',
loading: this.loading,
children: app.translator.trans('flarum-tags.admin.edit_tag.submit_button')
})}
}, app.translator.trans('flarum-tags.admin.edit_tag.submit_button'))}
{this.tag.exists ? (
<button type="button" className="Button EditTagModal-delete" onclick={this.delete.bind(this)}>
{app.translator.trans('flarum-tags.admin.edit_tag.delete_tag_button')}
Expand Down
5 changes: 3 additions & 2 deletions js/src/admin/components/TagSettingsModal.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import SettingsModal from 'flarum/components/SettingsModal';
import withAttr from 'flarum/utils/withAttr';

export default class TagSettingsModal extends SettingsModal {
setMinTags(minTags, maxTags, value) {
Expand Down Expand Up @@ -32,7 +33,7 @@ export default class TagSettingsModal extends SettingsModal {
type="number"
min="0"
value={minPrimaryTags()}
oninput={m.withAttr('value', this.setMinTags.bind(this, minPrimaryTags, maxPrimaryTags))} />
oninput={withAttr('value', this.setMinTags.bind(this, minPrimaryTags, maxPrimaryTags))} />
{app.translator.trans('flarum-tags.admin.tag_settings.range_separator_text')}
<input className="FormControl"
type="number"
Expand All @@ -51,7 +52,7 @@ export default class TagSettingsModal extends SettingsModal {
type="number"
min="0"
value={minSecondaryTags()}
oninput={m.withAttr('value', this.setMinTags.bind(this, minSecondaryTags, maxSecondaryTags))} />
oninput={withAttr('value', this.setMinTags.bind(this, minSecondaryTags, maxSecondaryTags))} />
{app.translator.trans('flarum-tags.admin.tag_settings.range_separator_text')}
<input className="FormControl"
type="number"
Expand Down
147 changes: 76 additions & 71 deletions js/src/admin/components/TagsPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ import sortTags from '../../common/utils/sortTags';

function tagItem(tag) {
return (
<li data-id={tag.id()} style={{color: tag.color()}}>
<li data-id={tag.id()} style={{ color: tag.color() }}>
<div className="TagListItem-info">
{tagIcon(tag)}
<span className="TagListItem-name">{tag.name()}</span>
{Button.component({
className: 'Button Button--link',
icon: 'fas fa-pencil-alt',
onclick: () => app.modal.show(EditTagModal, {tag})
onclick: () => app.modal.show(EditTagModal, { model: tag })
})}
</div>
{!tag.isChild() && tag.position() !== null ? (
Expand All @@ -32,6 +32,16 @@ function tagItem(tag) {
}

export default class TagsPage extends Page {
oninit(vnode) {
super.oninit(vnode);

// A regular redraw won't work here, because sortable has mucked around
// with the DOM which will confuse Mithril's diffing algorithm. Instead
// we force a full reconstruction of the DOM by changing the key, which
// makes mithril completely re-render the component on redraw.
this.forcedRefreshKey = 0;
}

view() {
return (
<div className="TagsPage">
Expand All @@ -43,18 +53,16 @@ export default class TagsPage extends Page {
{Button.component({
className: 'Button Button--primary',
icon: 'fas fa-plus',
children: app.translator.trans('flarum-tags.admin.tags.create_tag_button'),
onclick: () => app.modal.show(EditTagModal)
})}
}, app.translator.trans('flarum-tags.admin.tags.create_tag_button'))}
{Button.component({
className: 'Button',
children: app.translator.trans('flarum-tags.admin.tags.settings_button'),
onclick: () => app.modal.show(TagSettingsModal)
})}
}, app.translator.trans('flarum-tags.admin.tags.settings_button'))}
</div>
</div>
<div className="TagsPage-list">
<div className="container">
<div className="container" key={this.forcedRefreshKey} oncreate={this.onListOnCreate.bind(this)}>
<div className="TagGroup">
<label>{app.translator.trans('flarum-tags.admin.tags.primary_heading')}</label>
<ol className="TagList TagList--primary">
Expand All @@ -79,80 +87,77 @@ export default class TagsPage extends Page {
);
}

config() {
this.$('.TagList').get().map(e => {
sortable.create(e, {
group: 'tags',
animation: 150,
swapThreshold: 0.65,
dragClass: 'sortable-dragging',
ghostClass: 'sortable-placeholder',
onSort: (e) => this.onSortUpdate(e)
})
});
onListOnCreate(vnode) {
this.$('.TagList').get().map(e => {
sortable.create(e, {
group: 'tags',
animation: 150,
swapThreshold: 0.65,
dragClass: 'sortable-dragging',
ghostClass: 'sortable-placeholder',
onSort: (e) => this.onSortUpdate(e)
})
});
}

onSortUpdate(e) {
// If we've moved a tag from 'primary' to 'secondary', then we'll update
// its attributes in our local store so that when we redraw the change
// will be made.
if (e.from instanceof HTMLOListElement && e.to instanceof HTMLUListElement) {
app.store.getById('tags', e.item.getAttribute('data-id')).pushData({
attributes: {
position: null,
isChild: false
},
relationships: {parent: null}
});
}
// If we've moved a tag from 'primary' to 'secondary', then we'll update
// its attributes in our local store so that when we redraw the change
// will be made.
if (e.from instanceof HTMLOListElement && e.to instanceof HTMLUListElement) {
app.store.getById('tags', e.item.getAttribute('data-id')).pushData({
attributes: {
position: null,
isChild: false
},
relationships: { parent: null }
});
}

// Construct an array of primary tag IDs and their children, in the same
// order that they have been arranged in.
const order = this.$('.TagList--primary > li')
.map(function() {
return {
id: $(this).data('id'),
children: $(this).find('li')
.map(function() {
return $(this).data('id');
}).get()
};
}).get();
// Construct an array of primary tag IDs and their children, in the same
// order that they have been arranged in.
const order = this.$('.TagList--primary > li')
.map(function () {
return {
id: $(this).data('id'),
children: $(this).find('li')
.map(function () {
return $(this).data('id');
}).get()
};
}).get();

// Now that we have an accurate representation of the order which the
// primary tags are in, we will update the tag attributes in our local
// store to reflect this order.
order.forEach((tag, i) => {
const parent = app.store.getById('tags', tag.id);
parent.pushData({
attributes: {
position: i,
isChild: false
},
relationships: { parent: null }
});

// Now that we have an accurate representation of the order which the
// primary tags are in, we will update the tag attributes in our local
// store to reflect this order.
order.forEach((tag, i) => {
const parent = app.store.getById('tags', tag.id);
parent.pushData({
tag.children.forEach((child, j) => {
app.store.getById('tags', child).pushData({
attributes: {
position: i,
isChild: false
position: j,
isChild: true
},
relationships: {parent: null}
});

tag.children.forEach((child, j) => {
app.store.getById('tags', child).pushData({
attributes: {
position: j,
isChild: true
},
relationships: {parent}
});
relationships: { parent }
});
});
});

app.request({
url: app.forum.attribute('apiUrl') + '/tags/order',
method: 'POST',
data: {order}
});
app.request({
url: app.forum.attribute('apiUrl') + '/tags/order',
method: 'POST',
body: { order }
});

// A diff redraw won't work here, because sortable has mucked around
// with the DOM which will confuse Mithril's diffing algorithm. Instead
// we force a full reconstruction of the DOM.
m.redraw.strategy('all');
m.redraw();
this.forcedRefreshKey++;
m.redraw();
}
}
3 changes: 1 addition & 2 deletions js/src/common/helpers/tagLabel.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ export default function tagLabel(tag, attrs = {}) {

if (link) {
attrs.title = tag.description() || '';
attrs.href = app.route('tag', {tags: tag.slug()});
attrs.config = m.route;
attrs.route = app.route('tag', {tags: tag.slug()});
}
} else {
attrs.className += ' untagged';
Expand Down
Loading

0 comments on commit dfeeaa6

Please sign in to comment.