mirror of
https://github.com/flarum/framework.git
synced 2024-12-12 06:03:39 +08:00
Discussion list refactor, gestures
Also make base Component class automatically assign this.element :)
This commit is contained in:
parent
748abd9b0b
commit
972bd24c7a
128
framework/core/js/forum/src/components/discussion-list-item.js
Normal file
128
framework/core/js/forum/src/components/discussion-list-item.js
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
import Component from 'flarum/component';
|
||||||
|
import avatar from 'flarum/helpers/avatar';
|
||||||
|
import listItems from 'flarum/helpers/list-items';
|
||||||
|
import highlight from 'flarum/helpers/highlight';
|
||||||
|
import icon from 'flarum/helpers/icon';
|
||||||
|
import humanTime from 'flarum/utils/human-time';
|
||||||
|
import classList from 'flarum/utils/class-list';
|
||||||
|
import ItemList from 'flarum/utils/item-list';
|
||||||
|
import abbreviateNumber from 'flarum/utils/abbreviate-number';
|
||||||
|
import DropdownButton from 'flarum/components/dropdown-button';
|
||||||
|
import TerminalPost from 'flarum/components/terminal-post';
|
||||||
|
import PostPreview from 'flarum/components/post-preview';
|
||||||
|
import SubtreeRetainer from 'flarum/utils/subtree-retainer';
|
||||||
|
import slidable from 'flarum/utils/slidable';
|
||||||
|
|
||||||
|
export default class DiscussionListItem extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.subtree = new SubtreeRetainer(
|
||||||
|
() => this.props.discussion.freshness,
|
||||||
|
() => app.session.user() && app.session.user().readTime()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
view() {
|
||||||
|
var discussion = this.props.discussion;
|
||||||
|
|
||||||
|
var startUser = discussion.startUser();
|
||||||
|
var isUnread = discussion.isUnread();
|
||||||
|
var displayUnread = this.props.countType !== 'replies' && isUnread;
|
||||||
|
var jumpTo = Math.min(discussion.lastPostNumber(), (discussion.readNumber() || 0) + 1);
|
||||||
|
var relevantPosts = this.props.q ? discussion.relevantPosts() : '';
|
||||||
|
var controls = discussion.controls(this).toArray();
|
||||||
|
var isActive = m.route.param('id') === discussion.id();
|
||||||
|
|
||||||
|
return this.subtree.retain() || m('div.discussion-list-item', {config: this.onload.bind(this)}, [
|
||||||
|
controls.length ? DropdownButton.component({
|
||||||
|
items: controls,
|
||||||
|
className: 'contextual-controls',
|
||||||
|
buttonClass: 'btn btn-default btn-icon btn-sm btn-naked slidable-underneath slidable-underneath-right',
|
||||||
|
menuClass: 'pull-right'
|
||||||
|
}) : '',
|
||||||
|
|
||||||
|
m('a.slidable-underneath.slidable-underneath-left.elastic', {
|
||||||
|
className: discussion.isUnread() ? '' : 'disabled',
|
||||||
|
onclick: this.markAsRead.bind(this)
|
||||||
|
}, icon('check icon')),
|
||||||
|
|
||||||
|
m('div.slidable-slider.discussion-summary', {
|
||||||
|
className: classList({
|
||||||
|
unread: isUnread,
|
||||||
|
active: isActive
|
||||||
|
})
|
||||||
|
}, [
|
||||||
|
|
||||||
|
m((startUser ? 'a' : 'span')+'.author', {
|
||||||
|
href: startUser ? app.route.user(startUser) : undefined,
|
||||||
|
config: function(element, isInitialized, context) {
|
||||||
|
$(element).tooltip({ placement: 'right' });
|
||||||
|
m.route.apply(this, arguments);
|
||||||
|
},
|
||||||
|
title: 'Started by '+(startUser ? startUser.username() : '[deleted]')+' '+humanTime(discussion.startTime())
|
||||||
|
}, [
|
||||||
|
avatar(startUser, {title: ''})
|
||||||
|
]),
|
||||||
|
|
||||||
|
m('ul.badges', listItems(discussion.badges().toArray())),
|
||||||
|
|
||||||
|
m('a.main', {href: app.route.discussion(discussion, jumpTo), config: m.route}, [
|
||||||
|
m('h3.title', highlight(discussion.title(), this.props.q)),
|
||||||
|
m('ul.info', listItems(this.infoItems().toArray()))
|
||||||
|
]),
|
||||||
|
|
||||||
|
m('span.count', {onclick: this.markAsRead.bind(this)}, [
|
||||||
|
abbreviateNumber(discussion[displayUnread ? 'unreadCount' : 'repliesCount']()),
|
||||||
|
m('span.label', displayUnread ? 'unread' : 'replies')
|
||||||
|
]),
|
||||||
|
|
||||||
|
(relevantPosts && relevantPosts.length)
|
||||||
|
? m('div.relevant-posts', relevantPosts.map(post => PostPreview.component({post, highlight: this.props.q})))
|
||||||
|
: ''
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
markAsRead() {
|
||||||
|
var discussion = this.props.discussion;
|
||||||
|
|
||||||
|
if (discussion.isUnread()) {
|
||||||
|
discussion.save({ readNumber: discussion.lastPostNumber() });
|
||||||
|
m.redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Build an item list of info for a discussion listing. By default this is
|
||||||
|
just the first/last post indicator.
|
||||||
|
|
||||||
|
@return {ItemList}
|
||||||
|
*/
|
||||||
|
infoItems() {
|
||||||
|
var items = new ItemList();
|
||||||
|
|
||||||
|
items.add('terminalPost',
|
||||||
|
TerminalPost.component({
|
||||||
|
discussion: this.props.discussion,
|
||||||
|
lastPost: this.props.terminalPostType !== 'start'
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
onload(element, isInitialized, context) {
|
||||||
|
if (isInitialized) return;
|
||||||
|
|
||||||
|
if (window.ontouchstart !== 'undefined') {
|
||||||
|
this.$().addClass('slidable');
|
||||||
|
|
||||||
|
var slidableInstance = slidable(element);
|
||||||
|
|
||||||
|
this.$('.contextual-controls').on('hidden.bs.dropdown', function() {
|
||||||
|
slidableInstance.reset();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,16 +1,7 @@
|
||||||
import Component from 'flarum/component';
|
import Component from 'flarum/component';
|
||||||
import avatar from 'flarum/helpers/avatar';
|
import DiscussionListItem from 'flarum/components/discussion-list-item';
|
||||||
import listItems from 'flarum/helpers/list-items';
|
|
||||||
import highlight from 'flarum/helpers/highlight';
|
|
||||||
import humanTime from 'flarum/utils/human-time';
|
|
||||||
import ItemList from 'flarum/utils/item-list';
|
|
||||||
import abbreviateNumber from 'flarum/utils/abbreviate-number';
|
|
||||||
import ActionButton from 'flarum/components/action-button';
|
import ActionButton from 'flarum/components/action-button';
|
||||||
import DropdownButton from 'flarum/components/dropdown-button';
|
|
||||||
import LoadingIndicator from 'flarum/components/loading-indicator';
|
import LoadingIndicator from 'flarum/components/loading-indicator';
|
||||||
import TerminalPost from 'flarum/components/terminal-post';
|
|
||||||
import PostPreview from 'flarum/components/post-preview';
|
|
||||||
import SubtreeRetainer from 'flarum/utils/subtree-retainer';
|
|
||||||
|
|
||||||
export default class DiscussionList extends Component {
|
export default class DiscussionList extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -19,7 +10,6 @@ export default class DiscussionList extends Component {
|
||||||
this.loading = m.prop(true);
|
this.loading = m.prop(true);
|
||||||
this.moreResults = m.prop(false);
|
this.moreResults = m.prop(false);
|
||||||
this.discussions = m.prop([]);
|
this.discussions = m.prop([]);
|
||||||
this.subtrees = [];
|
|
||||||
|
|
||||||
this.refresh();
|
this.refresh();
|
||||||
|
|
||||||
|
@ -38,10 +28,6 @@ export default class DiscussionList extends Component {
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|
||||||
willBeRedrawn() {
|
|
||||||
this.subtrees.map(subtree => subtree.invalidate());
|
|
||||||
}
|
|
||||||
|
|
||||||
sortMap() {
|
sortMap() {
|
||||||
var map = {};
|
var map = {};
|
||||||
if (this.props.params.q) {
|
if (this.props.params.q) {
|
||||||
|
@ -96,32 +82,16 @@ export default class DiscussionList extends Component {
|
||||||
this.loadResults(this.discussions().length).then((results) => this.parseResults(results));
|
this.loadResults(this.discussions().length).then((results) => this.parseResults(results));
|
||||||
}
|
}
|
||||||
|
|
||||||
initSubtree(discussion) {
|
|
||||||
this.subtrees[discussion.id()] = new SubtreeRetainer(
|
|
||||||
() => discussion.freshness,
|
|
||||||
() => app.session.user() && app.session.user().readTime()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
parseResults(results) {
|
parseResults(results) {
|
||||||
m.startComputation();
|
m.startComputation();
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
|
|
||||||
results.forEach(this.initSubtree.bind(this));
|
|
||||||
|
|
||||||
[].push.apply(this.discussions(), results);
|
[].push.apply(this.discussions(), results);
|
||||||
this.moreResults(!!results.payload.links.next);
|
this.moreResults(!!results.payload.links.next);
|
||||||
m.endComputation();
|
m.endComputation();
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
markAsRead(discussion) {
|
|
||||||
if (discussion.isUnread()) {
|
|
||||||
discussion.save({ readNumber: discussion.lastPostNumber() });
|
|
||||||
m.redraw();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
removeDiscussion(discussion) {
|
removeDiscussion(discussion) {
|
||||||
var index = this.discussions().indexOf(discussion);
|
var index = this.discussions().indexOf(discussion);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
|
@ -131,57 +101,21 @@ export default class DiscussionList extends Component {
|
||||||
|
|
||||||
addDiscussion(discussion) {
|
addDiscussion(discussion) {
|
||||||
this.discussions().unshift(discussion);
|
this.discussions().unshift(discussion);
|
||||||
this.initSubtree(discussion);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
view() {
|
view() {
|
||||||
return m('div.discussion-list', [
|
return m('div.discussion-list', [
|
||||||
m('ul', [
|
m('ul', [
|
||||||
this.discussions().map(discussion => {
|
this.discussions().map(discussion => {
|
||||||
var startUser = discussion.startUser();
|
return m('li', {
|
||||||
var isUnread = discussion.isUnread();
|
|
||||||
var displayUnread = this.countType() !== 'replies' && isUnread;
|
|
||||||
var jumpTo = Math.min(discussion.lastPostNumber(), (discussion.readNumber() || 0) + 1);
|
|
||||||
var relevantPosts = this.props.params.q ? discussion.relevantPosts() : '';
|
|
||||||
|
|
||||||
var controls = discussion.controls(this).toArray();
|
|
||||||
|
|
||||||
var active = m.route.param('id') === discussion.id();
|
|
||||||
|
|
||||||
var subtree = this.subtrees[discussion.id()];
|
|
||||||
return m('li.discussion-summary'+(isUnread ? '.unread' : '')+(active ? '.active' : ''), {
|
|
||||||
key: discussion.id(),
|
key: discussion.id(),
|
||||||
'data-id': discussion.id()
|
'data-id': discussion.id()
|
||||||
}, (subtree && subtree.retain()) || m('div', [
|
}, DiscussionListItem.component({
|
||||||
controls.length ? DropdownButton.component({
|
discussion,
|
||||||
items: controls,
|
q: this.props.params.q,
|
||||||
className: 'contextual-controls',
|
countType: this.countType(),
|
||||||
buttonClass: 'btn btn-default btn-icon btn-sm btn-naked',
|
terminalPostType: this.terminalPostType()
|
||||||
menuClass: 'pull-right'
|
}));
|
||||||
}) : '',
|
|
||||||
m((startUser ? 'a' : 'span')+'.author', {
|
|
||||||
href: startUser ? app.route('user', { username: startUser.username() }) : undefined,
|
|
||||||
config: function(element, isInitialized, context) {
|
|
||||||
$(element).tooltip({ placement: 'right' })
|
|
||||||
m.route.apply(this, arguments)
|
|
||||||
},
|
|
||||||
title: 'Started by '+(startUser ? startUser.username() : '[deleted]')+' '+humanTime(discussion.startTime())
|
|
||||||
}, [
|
|
||||||
avatar(startUser, {title: ''})
|
|
||||||
]),
|
|
||||||
m('ul.badges', listItems(discussion.badges().toArray())),
|
|
||||||
m('a.main', {href: app.route('discussion.near', {id: discussion.id(), slug: discussion.slug(), near: jumpTo}), config: m.route}, [
|
|
||||||
m('h3.title', highlight(discussion.title(), this.props.params.q)),
|
|
||||||
m('ul.info', listItems(this.infoItems(discussion).toArray()))
|
|
||||||
]),
|
|
||||||
m('span.count', {onclick: this.markAsRead.bind(this, discussion)}, [
|
|
||||||
abbreviateNumber(discussion[displayUnread ? 'unreadCount' : 'repliesCount']()),
|
|
||||||
m('span.label', displayUnread ? 'unread' : 'replies')
|
|
||||||
]),
|
|
||||||
(relevantPosts && relevantPosts.length)
|
|
||||||
? m('div.relevant-posts', relevantPosts.map(post => PostPreview.component({post, highlight: this.props.params.q})))
|
|
||||||
: ''
|
|
||||||
]))
|
|
||||||
})
|
})
|
||||||
]),
|
]),
|
||||||
this.loading()
|
this.loading()
|
||||||
|
@ -193,23 +127,4 @@ export default class DiscussionList extends Component {
|
||||||
})) : '')
|
})) : '')
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
Build an item list of info for a discussion listing. By default this is
|
|
||||||
just the first/last post indicator.
|
|
||||||
|
|
||||||
@return {ItemList}
|
|
||||||
*/
|
|
||||||
infoItems(discussion) {
|
|
||||||
var items = new ItemList();
|
|
||||||
|
|
||||||
items.add('terminalPost',
|
|
||||||
TerminalPost.component({
|
|
||||||
discussion,
|
|
||||||
lastPost: this.terminalPostType() !== 'start'
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,9 +24,7 @@ export default class DiscussionPage extends mixin(Component, evented) {
|
||||||
this.refresh();
|
this.refresh();
|
||||||
|
|
||||||
if (app.cache.discussionList) {
|
if (app.cache.discussionList) {
|
||||||
if (!(app.current instanceof DiscussionPage)) {
|
if (app.current instanceof DiscussionPage) {
|
||||||
app.cache.discussionList.subtrees.map(subtree => subtree.invalidate());
|
|
||||||
} else {
|
|
||||||
m.redraw.strategy('diff'); // otherwise pane redraws (killing retained subtrees) and mouseenter event is triggered so it doesn't hide
|
m.redraw.strategy('diff'); // otherwise pane redraws (killing retained subtrees) and mouseenter event is triggered so it doesn't hide
|
||||||
}
|
}
|
||||||
app.pane.enable();
|
app.pane.enable();
|
||||||
|
|
|
@ -33,12 +33,6 @@ export default class IndexPage extends Component {
|
||||||
var params = this.params();
|
var params = this.params();
|
||||||
|
|
||||||
if (app.cache.discussionList) {
|
if (app.cache.discussionList) {
|
||||||
// The discussion list component is stored in the app's cache so that it
|
|
||||||
// can persist across interfaces. Since we will soon be redrawing the
|
|
||||||
// discussion list from scratch, we need to invalidate the component's
|
|
||||||
// subtree cache to ensure that it re-constructs the view.
|
|
||||||
app.cache.discussionList.willBeRedrawn();
|
|
||||||
|
|
||||||
// Compare the requested parameters (sort, search query) to the ones that
|
// Compare the requested parameters (sort, search query) to the ones that
|
||||||
// are currently present in the cached discussion list. If they differ, we
|
// are currently present in the cached discussion list. If they differ, we
|
||||||
// will clear the cache and set up a new discussion list component with
|
// will clear the cache and set up a new discussion list component with
|
||||||
|
|
102
framework/core/js/forum/src/utils/slidable.js
Normal file
102
framework/core/js/forum/src/utils/slidable.js
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
export default function slidable(element) {
|
||||||
|
var $slidable = $(element);
|
||||||
|
|
||||||
|
var startX;
|
||||||
|
var startY;
|
||||||
|
var couldBeSliding = false;
|
||||||
|
var isSliding = false;
|
||||||
|
var threshold = 50;
|
||||||
|
var pos = 0;
|
||||||
|
|
||||||
|
var underneathLeft;
|
||||||
|
var underneathRight;
|
||||||
|
|
||||||
|
var animatePos = function(pos, options) {
|
||||||
|
options = options || {};
|
||||||
|
options.duration = options.duration || 'fast';
|
||||||
|
options.step = function(pos) {
|
||||||
|
$(this).css('transform', 'translate('+pos+'px, 0)');
|
||||||
|
};
|
||||||
|
|
||||||
|
$slidable.find('.slidable-slider').animate({'background-position-x': pos}, options);
|
||||||
|
};
|
||||||
|
|
||||||
|
var reset = function() {
|
||||||
|
animatePos(0, {
|
||||||
|
complete: function() {
|
||||||
|
$slidable.removeClass('sliding');
|
||||||
|
underneathLeft.hide();
|
||||||
|
underneathRight.hide();
|
||||||
|
isSliding = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$slidable.find('.slidable-slider')
|
||||||
|
.on('touchstart', function(e) {
|
||||||
|
underneathLeft = $slidable.find('.slidable-underneath-left:not(.disabled)');
|
||||||
|
underneathRight = $slidable.find('.slidable-underneath-right:not(.disabled)');
|
||||||
|
|
||||||
|
startX = e.originalEvent.targetTouches[0].clientX;
|
||||||
|
startY = e.originalEvent.targetTouches[0].clientY;
|
||||||
|
|
||||||
|
couldBeSliding = true;
|
||||||
|
console.log('GO')
|
||||||
|
})
|
||||||
|
|
||||||
|
.on('touchmove', function(e) {
|
||||||
|
var newX = e.originalEvent.targetTouches[0].clientX;
|
||||||
|
var newY = e.originalEvent.targetTouches[0].clientY;
|
||||||
|
|
||||||
|
if (couldBeSliding && Math.abs(newX - startX) > Math.abs(newY - startY)) {
|
||||||
|
isSliding = true;
|
||||||
|
}
|
||||||
|
couldBeSliding = false;
|
||||||
|
|
||||||
|
if (isSliding) {
|
||||||
|
pos = newX - startX;
|
||||||
|
|
||||||
|
if (underneathLeft.length) {
|
||||||
|
if (pos > 0 && underneathLeft.hasClass('elastic')) {
|
||||||
|
pos -= pos * 0.5;
|
||||||
|
}
|
||||||
|
underneathLeft.toggle(pos > 0);
|
||||||
|
underneathLeft.find('.icon').css('transform', 'scale('+Math.max(0, Math.min(1, (Math.abs(pos) - 25) / threshold))+')');
|
||||||
|
} else {
|
||||||
|
pos = Math.min(0, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (underneathRight.length) {
|
||||||
|
if (pos < 0 && underneathRight.hasClass('elastic')) {
|
||||||
|
pos -= pos * 0.5;
|
||||||
|
}
|
||||||
|
underneathRight.toggle(pos < 0);
|
||||||
|
underneathRight.find('.icon').css('transform', 'scale('+Math.max(0, Math.min(1, (Math.abs(pos) - 25) / threshold))+')');
|
||||||
|
} else {
|
||||||
|
pos = Math.max(0, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
$(this).css('transform', 'translate('+pos+'px, 0)');
|
||||||
|
$(this).css('background-position-x', pos+'px');
|
||||||
|
|
||||||
|
$slidable.toggleClass('sliding', !!pos);
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
.on('touchend', function(e) {
|
||||||
|
if (underneathRight.length && pos < -threshold) {
|
||||||
|
underneathRight.click();
|
||||||
|
underneathRight.hasClass('elastic') ? reset() : animatePos(-$slidable.width());
|
||||||
|
} else if (underneathLeft.length && pos > threshold) {
|
||||||
|
underneathLeft.click();
|
||||||
|
underneathLeft.hasClass('elastic') ? reset() : animatePos(-$slidable.width());
|
||||||
|
} else {
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
couldBeSliding = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {reset};
|
||||||
|
};
|
|
@ -18,14 +18,6 @@ export default class Component {
|
||||||
return selector ? $(this.element()).find(selector) : $(this.element());
|
return selector ? $(this.element()).find(selector) : $(this.element());
|
||||||
}
|
}
|
||||||
|
|
||||||
onload(element) {
|
|
||||||
this.element(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
config() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
@ -38,13 +30,12 @@ export default class Component {
|
||||||
component.props = props;
|
component.props = props;
|
||||||
var vdom = component.view();
|
var vdom = component.view();
|
||||||
vdom.attrs = vdom.attrs || {};
|
vdom.attrs = vdom.attrs || {};
|
||||||
if (!vdom.attrs.config) {
|
var oldConfig = vdom.attrs.config;
|
||||||
vdom.attrs.config = function() {
|
vdom.attrs.config = function() {
|
||||||
var args = [].slice.apply(arguments);
|
var args = [].slice.apply(arguments);
|
||||||
if (!args[1]) {
|
component.element(args[0]);
|
||||||
component.onload.apply(component, args);
|
if (oldConfig) {
|
||||||
}
|
oldConfig.apply(component, args);
|
||||||
component.config.apply(component, args);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return vdom;
|
return vdom;
|
||||||
|
|
|
@ -10,7 +10,7 @@ export default class DropdownButton extends Component {
|
||||||
'data-toggle': 'dropdown',
|
'data-toggle': 'dropdown',
|
||||||
onclick: this.props.buttonClick
|
onclick: this.props.buttonClick
|
||||||
}, this.props.buttonContent || [
|
}, this.props.buttonContent || [
|
||||||
icon((this.props.icon || 'ellipsis-v')+' icon-glyph'),
|
icon((this.props.icon || 'ellipsis-v')+' icon-glyph icon'),
|
||||||
m('span.label', this.props.label || 'Controls'),
|
m('span.label', this.props.label || 'Controls'),
|
||||||
icon('caret-down icon-caret')
|
icon('caret-down icon-caret')
|
||||||
]),
|
]),
|
||||||
|
|
|
@ -83,16 +83,18 @@
|
||||||
& .hero, & .index-nav, & .index-toolbar {
|
& .hero, & .index-nav, & .index-toolbar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
& .discussion-list > ul > li {
|
& .discussion-list-item {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding-left: 57px + 15px;
|
padding: 0;
|
||||||
padding-right: 65px + 15px;
|
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
background: @fl-body-control-bg;
|
background: @fl-body-control-bg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
& .discussion-summary {
|
& .discussion-summary {
|
||||||
|
padding-left: 57px + 15px;
|
||||||
|
padding-right: 65px + 15px;
|
||||||
|
|
||||||
& .title {
|
& .title {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
@ -148,19 +150,6 @@
|
||||||
@media @phone {
|
@media @phone {
|
||||||
.discussion-list {
|
.discussion-list {
|
||||||
margin: 0 -15px;
|
margin: 0 -15px;
|
||||||
|
|
||||||
& > ul > li {
|
|
||||||
& .contextual-controls {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media @tablet, @desktop, @desktop-hd {
|
|
||||||
.discussion-list > ul > li {
|
|
||||||
margin-right: -25px;
|
|
||||||
padding-right: 65px + 25px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,15 +160,6 @@
|
||||||
color: @fl-body-muted-color;
|
color: @fl-body-muted-color;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
& .contextual-controls {
|
|
||||||
visibility: hidden;
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
top: 13px;
|
|
||||||
}
|
|
||||||
&:hover .contextual-controls, & .contextual-controls.open {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
& .author {
|
& .author {
|
||||||
float: left;
|
float: left;
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
|
@ -308,12 +288,16 @@
|
||||||
color: @fl-body-control-color;
|
color: @fl-body-control-color;
|
||||||
border-radius: @border-radius-base;
|
border-radius: @border-radius-base;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
padding: 1px 6px;
|
padding: 2px 6px;
|
||||||
|
|
||||||
.unread& {
|
.unread& {
|
||||||
background: @fl-body-primary-color;
|
background: @fl-body-primary-color;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
& .label {
|
& .label {
|
||||||
|
@ -323,7 +307,79 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.slidable {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
& .contextual-controls {
|
||||||
|
display: block;
|
||||||
|
position: static;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .slidable-underneath {
|
||||||
|
display: none;
|
||||||
|
background: @fl-secondary-color !important;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
z-index: 0;
|
||||||
|
color: #fff !important;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
.box-shadow(none);
|
||||||
|
padding: 20px 0;
|
||||||
|
text-align: right;
|
||||||
|
|
||||||
|
&.slidable-underneath-left {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .icon {
|
||||||
|
width: 50px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
& .slidable-slider {
|
||||||
|
.transition(~"box-shadow 0.2s, border-radius 0.2s");
|
||||||
|
|
||||||
|
.sliding& {
|
||||||
|
position: relative;
|
||||||
|
background: #fff;
|
||||||
|
z-index: 2;
|
||||||
|
border-radius: 2px;
|
||||||
|
.box-shadow(0 2px 6px @fl-shadow-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media @tablet, @desktop, @desktop-hd {
|
@media @tablet, @desktop, @desktop-hd {
|
||||||
|
.slidable-underneath {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.discussion-list-item {
|
||||||
|
position: relative;
|
||||||
|
margin-right: -25px;
|
||||||
|
padding-right: 25px;
|
||||||
|
|
||||||
|
& .contextual-controls {
|
||||||
|
visibility: hidden;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 8px;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
& .dropdown-toggle {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:hover .contextual-controls, & .contextual-controls.open {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
.discussion-summary {
|
.discussion-summary {
|
||||||
padding-left: 57px;
|
padding-left: 57px;
|
||||||
padding-right: 65px;
|
padding-right: 65px;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user