diff --git a/js/forum/Gulpfile.js b/js/forum/Gulpfile.js index 46a77c40d..8a1ab4698 100644 --- a/js/forum/Gulpfile.js +++ b/js/forum/Gulpfile.js @@ -1,26 +1,30 @@ var gulp = require('flarum-gulp'); +var nodeDir = 'node_modules'; +var bowerDir = '../bower_components'; + gulp({ files: [ - 'node_modules/babel-core/external-helpers.js', - '../bower_components/es6-promise-polyfill/promise.js', - '../bower_components/es6-micro-loader/dist/system-polyfill.js', + nodeDir + '/babel-core/external-helpers.js', - '../bower_components/mithril/mithril.js', - '../bower_components/jquery/dist/jquery.js', - '../bower_components/jquery.hotkeys/jquery.hotkeys.js', - '../bower_components/color-thief/js/color-thief.js', - '../bower_components/moment/moment.js', + bowerDir + '/es6-promise-polyfill/promise.js', + bowerDir + '/es6-micro-loader/dist/system-polyfill.js', - '../bower_components/bootstrap/js/affix.js', - '../bower_components/bootstrap/js/dropdown.js', - '../bower_components/bootstrap/js/modal.js', - '../bower_components/bootstrap/js/tooltip.js', - '../bower_components/bootstrap/js/transition.js', + bowerDir + '/mithril/mithril.js', + bowerDir + '/jquery/dist/jquery.js', + bowerDir + '/jquery.hotkeys/jquery.hotkeys.js', + bowerDir + '/color-thief/js/color-thief.js', + bowerDir + '/moment/moment.js', - '../bower_components/spin.js/spin.js', - '../bower_components/spin.js/jquery.spin.js', - '../bower_components/fastclick/lib/fastclick.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' ], moduleFiles: [ 'src/**/*.js', diff --git a/js/forum/src/components/Activity.js b/js/forum/src/components/Activity.js index 8e246923b..2c397586c 100644 --- a/js/forum/src/components/Activity.js +++ b/js/forum/src/components/Activity.js @@ -17,11 +17,11 @@ export default class Activity extends Component { const activity = this.props.activity; return ( -
- {avatar(this.user(), {className: 'activity-icon'})} +
+ {avatar(this.user(), {className: 'Activity-avatar'})} -
- {this.description()} +
+ {this.description()} {humanTime(activity.time())}
diff --git a/js/forum/src/components/ActivityPage.js b/js/forum/src/components/ActivityPage.js index 79d22a6a3..cedc3b535 100644 --- a/js/forum/src/components/ActivityPage.js +++ b/js/forum/src/components/ActivityPage.js @@ -47,10 +47,10 @@ export default class ActivityPage extends UserPage { footer = LoadingIndicator.component(); } else if (this.moreResults) { footer = ( -
+
{Button.component({ children: 'Load More', - className: 'btn btn-default', + className: 'Button--default', onclick: this.loadMore.bind(this) })}
@@ -58,8 +58,8 @@ export default class ActivityPage extends UserPage { } return ( -
-
    +
    +
      {this.activity.map(activity => { const ActivityComponent = app.activityComponents[activity.contentType()]; return ActivityComponent ?
    • {ActivityComponent.component({activity})}
    • : ''; diff --git a/js/forum/src/components/AvatarEditor.js b/js/forum/src/components/AvatarEditor.js index 77a6821a0..40730693d 100644 --- a/js/forum/src/components/AvatarEditor.js +++ b/js/forum/src/components/AvatarEditor.js @@ -37,15 +37,14 @@ export default class AvatarEditor extends Component { const user = this.props.user; return ( -
      + diff --git a/js/forum/src/components/ChangeEmailModal.js b/js/forum/src/components/ChangeEmailModal.js index 0db5e0b38..b58235752 100644 --- a/js/forum/src/components/ChangeEmailModal.js +++ b/js/forum/src/components/ChangeEmailModal.js @@ -24,7 +24,7 @@ export default class ChangeEmailModal extends Modal { } className() { - return 'modal-sm change-email-modal'; + return 'ChangeEmailModal Modal--small'; } title() { @@ -36,11 +36,11 @@ export default class ChangeEmailModal extends Modal { const emailProviderName = this.email().split('@')[1]; return ( -
      -
      -

      We've sent a confirmation email to {this.email()}. If it doesn't arrive soon, check your spam folder.

      -
      - Go to {emailProviderName} +
      +
      +

      We've sent a confirmation email to {this.email()}. If it doesn't arrive soon, check your spam folder.

      +
      @@ -48,17 +48,17 @@ export default class ChangeEmailModal extends Modal { } return ( -
      -
      -
      - +
      +
      +
      -
      - +
      +
      diff --git a/js/forum/src/components/ChangePasswordModal.js b/js/forum/src/components/ChangePasswordModal.js index d143065a6..946af808e 100644 --- a/js/forum/src/components/ChangePasswordModal.js +++ b/js/forum/src/components/ChangePasswordModal.js @@ -6,7 +6,7 @@ import Modal from 'flarum/components/Modal'; */ export default class ChangePasswordModal extends Modal { className() { - return 'modal-sm change-password-modal'; + return 'ChangePasswordModal Modal--small'; } title() { @@ -15,11 +15,11 @@ export default class ChangePasswordModal extends Modal { content() { return ( -
      -
      -

      Click the button below and check your email for a link to change your password.

      -
      - +
      +
      +

      Click the button below and check your email for a link to change your password.

      +
      +
      diff --git a/js/forum/src/components/CommentPost.js b/js/forum/src/components/CommentPost.js index 50efa2904..73e873591 100644 --- a/js/forum/src/components/CommentPost.js +++ b/js/forum/src/components/CommentPost.js @@ -7,7 +7,7 @@ import EditPostComposer from 'flarum/components/EditPostComposer'; import Composer from 'flarum/components/Composer'; import ItemList from 'flarum/utils/ItemList'; import listItems from 'flarum/helpers/listItems'; -import icon from 'flarum/helpers/icon'; +import Button from 'flarum/components/Button'; /** * The `CommentPost` component displays a standard `comment`-typed post. This @@ -38,10 +38,10 @@ export default class CommentPost extends Post { content() { return [ -
        {listItems(this.headerItems().toArray())}
      , -
      {m.trust(this.props.post.contentHtml())}
      , - , - +
        {listItems(this.headerItems().toArray())}
      , +
      {m.trust(this.props.post.contentHtml())}
      , +
        {listItems(this.footerItems().toArray())}
      , + ]; } @@ -50,10 +50,10 @@ export default class CommentPost extends Post { return { className: classList({ - 'comment-post': true, - 'is-hidden': post.isHidden(), - 'is-edited': post.isEdited(), - 'reveal-content': this.revealContent, + 'CommentPost': true, + 'hidden': post.isHidden(), + 'edited': post.isEdited(), + 'revealContent': this.revealContent, 'editing': app.composer.component instanceof EditPostComposer && app.composer.component.props.post === post && app.composer.position !== Composer.PositionEnum.MINIMIZED @@ -89,11 +89,11 @@ export default class CommentPost extends Post { // of the post's content. if (post.isHidden()) { items.add('toggle', ( - + Button.component({ + className: 'Button Button--default Button--more', + icon: 'ellipsis-h', + onclick: this.toggleContent.bind(this) + }) )); } diff --git a/js/forum/src/components/Composer.js b/js/forum/src/components/Composer.js index a0fef8a53..f971ed749 100644 --- a/js/forum/src/components/Composer.js +++ b/js/forum/src/components/Composer.js @@ -62,7 +62,7 @@ class Composer extends Component { view() { const classes = { 'minimized': this.position === Composer.PositionEnum.MINIMIZED, - 'full-screen': this.position === Composer.PositionEnum.FULLSCREEN + 'fullScreen': this.position === Composer.PositionEnum.FULLSCREEN }; classes.visible = this.position === Composer.PositionEnum.NORMAL || classes.minimized || classes.fullScreen; @@ -76,10 +76,10 @@ class Composer extends Component { }; return ( -
      -
      -
        {listItems(this.controlItems().toArray())}
      -
      +
      +
      +
        {listItems(this.controlItems().toArray())}
      +
      {this.component ? this.component.render() : ''}
      @@ -113,20 +113,8 @@ class Composer extends Component { return (this.component && this.component.preventExit()) || null; }; - // Add the necessary event handlers to the composer's handle so that it can - // be used to resize the composer. - const composer = this; const handlers = {}; - this.$('.composer-handle').css('cursor', 'row-resize') - .bind('dragstart mousedown', e => e.preventDefault()) - .mousedown(function(e) { - composer.mouseStart = e.clientY; - composer.heightStart = composer.$().height(); - composer.handle = $(this); - $('body').css('cursor', 'row-resize'); - }); - $(window).on('resize', handlers.onresize = this.updateHeight.bind(this)).resize(); $(document) @@ -142,6 +130,28 @@ class Composer extends Component { }; } + /** + * Add the necessary event handlers to the composer's handle so that it can + * be used to resize the composer. + * + * @param {DOMElement} element + * @param {Boolean} isInitialized + */ + configHandle(element, isInitialized) { + if (isInitialized) return; + + const composer = this; + + $(element).css('cursor', 'row-resize') + .bind('dragstart mousedown', e => e.preventDefault()) + .mousedown(function(e) { + composer.mouseStart = e.clientY; + composer.heightStart = composer.$().height(); + composer.handle = $(this); + $('body').css('cursor', 'row-resize'); + }); + } + /** * Resize the composer according to mouse movement. * @@ -185,15 +195,17 @@ class Composer extends Component { * of any flexible elements inside the composer's body. */ updateHeight() { + // TODO: update this in a way that is independent of the TextEditor being + // present. const height = this.computedHeight(); - const $flexible = this.$('.flexible-height'); + const $flexible = this.$('.TextEditor-flexible'); this.$().height(height); if ($flexible.length) { const headerHeight = $flexible.offset().top - this.$().offset().top; const paddingBottom = parseInt($flexible.css('padding-bottom'), 10); - const footerHeight = this.$('.text-editor-controls').outerHeight(true); + const footerHeight = this.$('.TextEditor-controls').outerHeight(true); $flexible.height(height - headerHeight - paddingBottom - footerHeight); } @@ -209,7 +221,7 @@ class Composer extends Component { this.position !== Composer.PositionEnum.MINIMIZED; const paddingBottom = visible - ? this.computedHeight() - parseInt($('#page').css('padding-bottom'), 10) + ? this.computedHeight() - parseInt($('#app').css('padding-bottom'), 10) : 0; $('#content').css({paddingBottom}); } @@ -431,7 +443,7 @@ class Composer extends Component { icon: 'minus minimize', title: 'Minimize', onclick: this.minimize.bind(this), - wrapperClass: 'back-control' + itemClassName: 'App-backControl' })); items.add('fullScreen', ComposerButton.component({ diff --git a/js/forum/src/components/ComposerBody.js b/js/forum/src/components/ComposerBody.js index c4c080e7f..46ebc7b2e 100644 --- a/js/forum/src/components/ComposerBody.js +++ b/js/forum/src/components/ComposerBody.js @@ -58,13 +58,13 @@ export default class ComposerBody extends Component { this.editor.props.disabled = this.loading; return ( -
      - {avatar(this.props.user, {className: 'composer-avatar'})} -
      -
        {listItems(this.headerItems().toArray())}
      -
      {this.editor.render()}
      +
      + {avatar(this.props.user, {className: 'ComposerBody-avatar'})} +
      +
        {listItems(this.headerItems().toArray())}
      +
      {this.editor.render()}
      - {LoadingIndicator.component({className: 'composer-loading' + (this.loading ? ' active' : '')})} + {LoadingIndicator.component({className: 'ComposerBody-loading' + (this.loading ? ' active' : '')})}
      ); } diff --git a/js/forum/src/components/ComposerButton.js b/js/forum/src/components/ComposerButton.js index dd7d1cc88..9f7b22331 100644 --- a/js/forum/src/components/ComposerButton.js +++ b/js/forum/src/components/ComposerButton.js @@ -8,6 +8,6 @@ export default class ComposerButton extends Button { static initProps(props) { super.initProps(props); - props.className = props.className || 'btn btn-icon btn-link'; + props.className = props.className || 'Button Button--icon Button--link'; } } diff --git a/js/forum/src/components/DeleteAccountModal.js b/js/forum/src/components/DeleteAccountModal.js index 308b80285..463101a7a 100644 --- a/js/forum/src/components/DeleteAccountModal.js +++ b/js/forum/src/components/DeleteAccountModal.js @@ -19,7 +19,7 @@ export default class DeleteAccountModal extends Modal { } className() { - return 'modal-sm delete-account-modal'; + return 'DeleteAccountModal Modal--small'; } title() { @@ -28,24 +28,24 @@ export default class DeleteAccountModal extends Modal { content() { return ( -
      -
      -
      +
      +
      +

      Hold up! If you delete your account, there's no going back. Keep in mind:

      • Your username will be released, so someone else will be able to sign up with your name.
      • All of your posts will remain, but no longer associated with your account.
      -
      - +
      -
      +
      diff --git a/js/forum/src/components/DiscussionComposer.js b/js/forum/src/components/DiscussionComposer.js index 6d3fcf9d0..df2c2f3f0 100644 --- a/js/forum/src/components/DiscussionComposer.js +++ b/js/forum/src/components/DiscussionComposer.js @@ -37,7 +37,7 @@ export default class DiscussionComposer extends ComposerBody { items.add('title', (

      - +
      -
        {listItems(this.items().toArray())}
      +
        {listItems(this.items().toArray())}
      ); @@ -31,10 +31,10 @@ export default class DiscussionHero extends Component { const badges = discussion.badges().toArray(); if (badges.length) { - items.add('badges',
        {listItems(badges)}
      ); + items.add('badges',
        {listItems(badges)}
      ); } - items.add('title',

      {discussion.title()}

      ); + items.add('title',

      {discussion.title()}

      ); return items; } diff --git a/js/forum/src/components/DiscussionList.js b/js/forum/src/components/DiscussionList.js index e3d886666..8b1e0e82f 100644 --- a/js/forum/src/components/DiscussionList.js +++ b/js/forum/src/components/DiscussionList.js @@ -52,20 +52,16 @@ export default class DiscussionList extends Component { if (this.loading) { loading = LoadingIndicator.component(); } else if (this.moreResults) { - loading = ( -
      - {Button.component({ - children: 'Load More', - className: 'btn btn-default', - onclick: this.loadMore.bind(this) - })} -
      - ); + loading = Button.component({ + children: 'Load More', + className: 'Button', + onclick: this.loadMore.bind(this) + }); } return ( -
      -
        +
        +
          {this.discussions.map(discussion => { return (
        • @@ -74,7 +70,9 @@ export default class DiscussionList extends Component { ); })}
        - {loading} +
        + {loading} +
        ); } diff --git a/js/forum/src/components/DiscussionListItem.js b/js/forum/src/components/DiscussionListItem.js index 2a2637d67..1155ee726 100644 --- a/js/forum/src/components/DiscussionListItem.js +++ b/js/forum/src/components/DiscussionListItem.js @@ -45,28 +45,27 @@ export default class DiscussionListItem extends Component { const isUnread = discussion.isUnread(); const showUnread = !this.showRepliesCount() && isUnread; const jumpTo = Math.min(discussion.lastPostNumber(), (discussion.readNumber() || 0) + 1); - const relevantPosts = this.props.params.q ? discussion.relevantPosts() : ''; + const relevantPosts = this.props.params.q ? discussion.relevantPosts() : []; const controls = DiscussionControls.controls(discussion, this).toArray(); return this.subtree.retain() || ( -
        +
        {controls.length ? Dropdown.component({ icon: 'ellipsis-v', children: controls, - className: 'contextual-controls', - buttonClassName: 'btn btn-default btn-naked btn-controls slidable-underneath slidable-underneath-right', - menuClassName: 'dropdown-menu-right' + className: 'DiscussionListItem-controls', + buttonClassName: 'Button Button--icon Button--flat Slidable-underneath Slidable-underneath--right' }) : ''} - - {icon('check', {className: 'icon'})} + {icon('check')} -
        +
        -
          {listItems(discussion.badges().toArray())}
        +
          + {listItems(discussion.badges().toArray())} +
        -

        {highlight(discussion.title(), this.props.params.q)}

        -
          {listItems(this.infoItems().toArray())}
        + className="DiscussionListItem-main"> +

        {highlight(discussion.title(), this.props.params.q)}

        +
          {listItems(this.infoItems().toArray())}
        - {abbreviateNumber(discussion[showUnread ? 'unreadCount' : 'repliesCount']())} {relevantPosts && relevantPosts.length - ?
        + ?
        {relevantPosts.map(post => PostPreview.component({post, highlight: this.props.params.q}))}
        : ''} @@ -108,9 +109,9 @@ export default class DiscussionListItem extends Component { // This allows the user to drag the row to either side of the screen to // reveal controls. if ('ontouchstart' in window) { - const slidableInstance = slidable(this.$().addClass('slidable')); + const slidableInstance = slidable(this.$().addClass('Slidable')); - this.$('.contextual-controls') + this.$('.DiscussionListItem-controls') .on('hidden.bs.dropdown', () => slidableInstance.reset()); } } diff --git a/js/forum/src/components/DiscussionPage.js b/js/forum/src/components/DiscussionPage.js index 03e011c67..ec1e04ae0 100644 --- a/js/forum/src/components/DiscussionPage.js +++ b/js/forum/src/components/DiscussionPage.js @@ -92,25 +92,27 @@ export default class DiscussionPage extends mixin(Component, evented) { const discussion = this.discussion; return ( -
        +
        {app.cache.discussionList - ?
        + ?
        {app.cache.discussionList.render()}
        : ''} -
        +
        {discussion ? [ DiscussionHero.component({discussion}),
        -
        ] - : LoadingIndicator.component({className: 'loading-indicator-block'})} + : LoadingIndicator.component({className: 'LoadingIndicator--block'})}
        ); @@ -119,10 +121,10 @@ export default class DiscussionPage extends mixin(Component, evented) { config(isInitialized, context) { if (isInitialized) return; - context.retain = true; + // context.retain = true; - $('body').addClass('discussion-page'); - context.onunload = () => $('body').removeClass('discussion-page'); + $('#app').addClass('App--discussion'); + context.onunload = () => $('#app').removeClass('App--discussion'); } /** @@ -198,7 +200,7 @@ export default class DiscussionPage extends mixin(Component, evented) { .filter(record => record.type === 'posts' && record.relationships && record.relationships.discussion) .map(record => app.store.getById('posts', record.id)) .sort((a, b) => a.id() - b.id()) - .splice(20); + .slice(0, 20); } // Set up the post stream for this discussion, along with the first page of @@ -240,7 +242,7 @@ export default class DiscussionPage extends mixin(Component, evented) { // If the discussion we are viewing is listed in the discussion list, then // we will make sure it is visible in the viewport – if it is not we will // scroll the list down to it. - const $discussion = $list.find('.discussion-list-item.active'); + const $discussion = $list.find('.DiscussionListItem.active'); if ($discussion.length) { const listTop = $list.offset().top; const listBottom = listTop + $list.outerHeight(); @@ -265,15 +267,15 @@ export default class DiscussionPage extends mixin(Component, evented) { SplitDropdown.component({ children: DiscussionControls.controls(this.discussion, this).toArray(), icon: 'ellipsis-v', - className: 'primary-control', - buttonClassName: 'btn btn-primary' + className: 'App-primaryControl', + buttonClassName: 'Button--primary' }) ); items.add('scrubber', PostStreamScrubber.component({ stream: this.stream, - className: 'title-control' + className: 'App-titleControl' }) ); diff --git a/js/forum/src/components/DiscussionRenamedPost.js b/js/forum/src/components/DiscussionRenamedPost.js index 576db0d4e..21c39241e 100644 --- a/js/forum/src/components/DiscussionRenamedPost.js +++ b/js/forum/src/components/DiscussionRenamedPost.js @@ -18,6 +18,6 @@ export default class DiscussionRenamedPost extends EventPost { const oldTitle = post.content()[0]; const newTitle = post.content()[1]; - return ['changed the title from ', m('strong.old-title', oldTitle), ' to ', m('strong.new-title', newTitle), '.']; + return ['changed the title from ', m('strong.DiscussionRenamedPost-old', oldTitle), ' to ', m('strong.DiscussionRenamedPost-new', newTitle), '.']; } } diff --git a/js/forum/src/components/DiscussionsSearchSource.js b/js/forum/src/components/DiscussionsSearchSource.js index 9988fdb13..6cef59a62 100644 --- a/js/forum/src/components/DiscussionsSearchSource.js +++ b/js/forum/src/components/DiscussionsSearchSource.js @@ -1,5 +1,5 @@ import highlight from 'flarum/helpers/highlight'; -import Button from 'flarum/components/Button'; +import LinkButton from 'flarum/components/LinkButton'; /** * The `DiscussionsSearchSource` finds and displays discussion search results in @@ -28,13 +28,12 @@ export default class DiscussionsSearchSource { const results = this.results[query] || []; return [ -
      • Discussions
      • , +
      • Discussions
      • ,
      • - {Button.component({ + {LinkButton.component({ icon: 'search', children: 'Search all discussions for "' + query + '"', - href: app.route('index', {q: query}), - config: m.route + href: app.route('index', {q: query}) })}
      • , results.map(discussion => { @@ -42,10 +41,10 @@ export default class DiscussionsSearchSource { const post = relevantPosts && relevantPosts[0]; return ( -
      • +
      • -
        {highlight(discussion.title(), query)}
        - {post ?
        {highlight(post.contentPlain(), query, 100)}
        : ''} +
        {highlight(discussion.title(), query)}
        + {post ?
        {highlight(post.contentPlain(), query, 100)}
        : ''}
      • ); diff --git a/js/forum/src/components/EditPostComposer.js b/js/forum/src/components/EditPostComposer.js index e0b013dae..2dbabc4b4 100644 --- a/js/forum/src/components/EditPostComposer.js +++ b/js/forum/src/components/EditPostComposer.js @@ -11,7 +11,7 @@ import icon from 'flarum/helpers/icon'; * - All of the props for ComposerBody * - `post` */ -export default class EditComposer extends ComposerBody { +export default class EditPostComposer extends ComposerBody { static initProps(props) { super.initProps(props); @@ -27,7 +27,7 @@ export default class EditComposer extends ComposerBody { items.add('title', (

        - {icon('pencil')} + {icon('pencil')}{' '} Post #{post.number()} in {post.discussion().title()} diff --git a/js/forum/src/components/EventPost.js b/js/forum/src/components/EventPost.js index 0f51cfe24..8aefd32ee 100644 --- a/js/forum/src/components/EventPost.js +++ b/js/forum/src/components/EventPost.js @@ -16,7 +16,7 @@ import icon from 'flarum/helpers/icon'; export default class EventPost extends Post { attrs() { return { - className: 'event-post ' + this.props.post.contentType() + '-post' + className: 'EventPost EventPost--' + this.props.post.contentType() }; } @@ -25,9 +25,9 @@ export default class EventPost extends Post { const username = usernameHelper(user); return [ - icon(this.icon(), {className: 'event-post-icon'}), -