Convert `Discourse.Post` to ES6 and use Store model
- Includes acceptance tests for composer (post, edit) - Supports acceptance testing of bootbox
This commit is contained in:
parent
19a9a8b408
commit
22ffcba8e6
|
@ -0,0 +1,11 @@
|
|||
import RestAdapter from 'discourse/adapters/rest';
|
||||
|
||||
export default RestAdapter.extend({
|
||||
|
||||
// GET /posts doesn't include a type
|
||||
find(store, type, findArgs) {
|
||||
return this._super(store, type, findArgs).then(function(result) {
|
||||
return {post: result};
|
||||
});
|
||||
}
|
||||
});
|
|
@ -61,7 +61,8 @@ export default DiscourseController.extend({
|
|||
if (postId) {
|
||||
this.set('model.loading', true);
|
||||
const composer = this;
|
||||
return Discourse.Post.load(postId).then(function(post) {
|
||||
|
||||
return this.store.find('post', postId).then(function(post) {
|
||||
const quote = Discourse.Quote.build(post, post.get("raw"));
|
||||
composer.appendBlockAtCursor(quote);
|
||||
composer.set('model.loading', false);
|
||||
|
@ -412,7 +413,7 @@ export default DiscourseController.extend({
|
|||
composerModel.set('topic', opts.topic);
|
||||
}
|
||||
} else {
|
||||
composerModel = composerModel || Discourse.Composer.create();
|
||||
composerModel = composerModel || Discourse.Composer.create({ store: this.store });
|
||||
composerModel.open(opts);
|
||||
}
|
||||
|
||||
|
|
|
@ -323,7 +323,13 @@
|
|||
// Adds a listener callback to a DOM element which is fired on a specified
|
||||
// event.
|
||||
util.addEvent = function (elem, event, listener) {
|
||||
elem.addEventListener(event, listener, false);
|
||||
var wrapped = function() {
|
||||
var wrappedArgs = Array.prototype.slice(arguments);
|
||||
Ember.run(function() {
|
||||
listener.call(this, wrappedArgs);
|
||||
});
|
||||
};
|
||||
elem.addEventListener(event, wrapped, false);
|
||||
};
|
||||
|
||||
|
||||
|
@ -904,7 +910,7 @@
|
|||
// TODO allow us to inject this in (its our debouncer)
|
||||
var debounce = function(func,wait,trickle) {
|
||||
var timeout = null;
|
||||
return function(){
|
||||
return function() {
|
||||
var context = this;
|
||||
var args = arguments;
|
||||
|
||||
|
@ -924,8 +930,8 @@
|
|||
currentWait = wait;
|
||||
}
|
||||
|
||||
if (timeout) { clearTimeout(timeout); }
|
||||
timeout = setTimeout(later, currentWait);
|
||||
if (timeout) { Ember.run.cancel(timeout); }
|
||||
timeout = Ember.run.later(later, currentWait);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ const CLOSED = 'closed',
|
|||
const Composer = Discourse.Model.extend({
|
||||
|
||||
archetypes: function() {
|
||||
return Discourse.Site.currentProp('archetypes');
|
||||
return this.site.get('archetypes');
|
||||
}.property(),
|
||||
|
||||
creatingTopic: Em.computed.equal('action', CREATE_TOPIC),
|
||||
|
@ -127,21 +127,16 @@ const Composer = Discourse.Model.extend({
|
|||
} else {
|
||||
// has a category? (when needed)
|
||||
return this.get('canCategorize') &&
|
||||
!Discourse.SiteSettings.allow_uncategorized_topics &&
|
||||
!this.siteSettings.allow_uncategorized_topics &&
|
||||
!this.get('categoryId') &&
|
||||
!Discourse.User.currentProp('staff');
|
||||
!this.user.get('staff');
|
||||
}
|
||||
}.property('loading', 'canEditTitle', 'titleLength', 'targetUsernames', 'replyLength', 'categoryId', 'missingReplyCharacters'),
|
||||
|
||||
/**
|
||||
Is the title's length valid?
|
||||
|
||||
@property titleLengthValid
|
||||
**/
|
||||
titleLengthValid: function() {
|
||||
if (Discourse.User.currentProp('admin') && this.get('post.static_doc') && this.get('titleLength') > 0) return true;
|
||||
if (this.user.get('admin') && this.get('post.static_doc') && this.get('titleLength') > 0) return true;
|
||||
if (this.get('titleLength') < this.get('minimumTitleLength')) return false;
|
||||
return (this.get('titleLength') <= Discourse.SiteSettings.max_topic_title_length);
|
||||
return (this.get('titleLength') <= this.siteSettings.max_topic_title_length);
|
||||
}.property('minimumTitleLength', 'titleLength', 'post.static_doc'),
|
||||
|
||||
// The icon for the save button
|
||||
|
@ -194,9 +189,9 @@ const Composer = Discourse.Model.extend({
|
|||
**/
|
||||
minimumTitleLength: function() {
|
||||
if (this.get('privateMessage')) {
|
||||
return Discourse.SiteSettings.min_private_message_title_length;
|
||||
return this.siteSettings.min_private_message_title_length;
|
||||
} else {
|
||||
return Discourse.SiteSettings.min_topic_title_length;
|
||||
return this.siteSettings.min_topic_title_length;
|
||||
}
|
||||
}.property('privateMessage'),
|
||||
|
||||
|
@ -216,12 +211,12 @@ const Composer = Discourse.Model.extend({
|
|||
**/
|
||||
minimumPostLength: function() {
|
||||
if( this.get('privateMessage') ) {
|
||||
return Discourse.SiteSettings.min_private_message_post_length;
|
||||
return this.siteSettings.min_private_message_post_length;
|
||||
} else if (this.get('topicFirstPost')) {
|
||||
// first post (topic body)
|
||||
return Discourse.SiteSettings.min_first_post_length;
|
||||
return this.siteSettings.min_first_post_length;
|
||||
} else {
|
||||
return Discourse.SiteSettings.min_post_length;
|
||||
return this.siteSettings.min_post_length;
|
||||
}
|
||||
}.property('privateMessage', 'topicFirstPost'),
|
||||
|
||||
|
@ -249,7 +244,7 @@ const Composer = Discourse.Model.extend({
|
|||
_setupComposer: function() {
|
||||
const val = (Discourse.Mobile.mobileView ? false : (Discourse.KeyValueStore.get('composer.showPreview') || 'true'));
|
||||
this.set('showPreview', val === 'true');
|
||||
this.set('archetypeId', Discourse.Site.currentProp('default_archetype'));
|
||||
this.set('archetypeId', this.site.get('default_archetype'));
|
||||
}.on('init'),
|
||||
|
||||
/**
|
||||
|
@ -349,15 +344,15 @@ const Composer = Discourse.Model.extend({
|
|||
|
||||
this.setProperties({
|
||||
categoryId: opts.categoryId || this.get('topic.category.id'),
|
||||
archetypeId: opts.archetypeId || Discourse.Site.currentProp('default_archetype'),
|
||||
archetypeId: opts.archetypeId || this.site.get('default_archetype'),
|
||||
metaData: opts.metaData ? Em.Object.create(opts.metaData) : null,
|
||||
reply: opts.reply || this.get("reply") || ""
|
||||
});
|
||||
|
||||
if (opts.postId) {
|
||||
this.set('loading', true);
|
||||
Discourse.Post.load(opts.postId).then(function(result) {
|
||||
composer.set('post', result);
|
||||
this.store.find('post', opts.postId).then(function(post) {
|
||||
composer.set('post', post);
|
||||
composer.set('loading', false);
|
||||
});
|
||||
}
|
||||
|
@ -370,10 +365,10 @@ const Composer = Discourse.Model.extend({
|
|||
|
||||
this.setProperties(topicProps);
|
||||
|
||||
Discourse.Post.load(opts.post.get('id')).then(function(result) {
|
||||
this.store.find('post', opts.post.get('id')).then(function(post) {
|
||||
composer.setProperties({
|
||||
reply: result.get('raw'),
|
||||
originalText: result.get('raw'),
|
||||
reply: post.get('raw'),
|
||||
originalText: post.get('raw'),
|
||||
loading: false
|
||||
});
|
||||
});
|
||||
|
@ -467,7 +462,7 @@ const Composer = Discourse.Model.extend({
|
|||
createPost(opts) {
|
||||
const post = this.get('post'),
|
||||
topic = this.get('topic'),
|
||||
currentUser = Discourse.User.current(),
|
||||
user = this.user,
|
||||
postStream = this.get('topic.postStream');
|
||||
|
||||
let addedToStream = false;
|
||||
|
@ -477,17 +472,17 @@ const Composer = Discourse.Model.extend({
|
|||
imageSizes: opts.imageSizes,
|
||||
cooked: this.getCookedHtml(),
|
||||
reply_count: 0,
|
||||
name: currentUser.get('name'),
|
||||
display_username: currentUser.get('name'),
|
||||
username: currentUser.get('username'),
|
||||
user_id: currentUser.get('id'),
|
||||
user_title: currentUser.get('title'),
|
||||
uploaded_avatar_id: currentUser.get('uploaded_avatar_id'),
|
||||
user_custom_fields: currentUser.get('custom_fields'),
|
||||
post_type: Discourse.Site.currentProp('post_types.regular'),
|
||||
name: user.get('name'),
|
||||
display_username: user.get('name'),
|
||||
username: user.get('username'),
|
||||
user_id: user.get('id'),
|
||||
user_title: user.get('title'),
|
||||
uploaded_avatar_id: user.get('uploaded_avatar_id'),
|
||||
user_custom_fields: user.get('custom_fields'),
|
||||
post_type: this.site.get('post_types.regular'),
|
||||
actions_summary: [],
|
||||
moderator: currentUser.get('moderator'),
|
||||
admin: currentUser.get('admin'),
|
||||
moderator: user.get('moderator'),
|
||||
admin: user.get('admin'),
|
||||
yours: true,
|
||||
newPost: true,
|
||||
read: true
|
||||
|
@ -520,7 +515,7 @@ const Composer = Discourse.Model.extend({
|
|||
// we would need to handle oneboxes and other bits that are not even in the
|
||||
// engine, staging will just cause a blank post to render
|
||||
if (!_.isEmpty(createdPost.get('cooked'))) {
|
||||
state = postStream.stagePost(createdPost, currentUser);
|
||||
state = postStream.stagePost(createdPost, user);
|
||||
|
||||
if(state === "alreadyStaging"){
|
||||
return;
|
||||
|
@ -529,69 +524,64 @@ const Composer = Discourse.Model.extend({
|
|||
}
|
||||
}
|
||||
|
||||
const composer = this,
|
||||
promise = new Ember.RSVP.Promise(function(resolve, reject) {
|
||||
composer.set('composeState', SAVING);
|
||||
|
||||
createdPost.save(function(result) {
|
||||
let saving = true;
|
||||
|
||||
createdPost.updateFromJson(result);
|
||||
|
||||
if (topic) {
|
||||
// It's no longer a new post
|
||||
createdPost.set('newPost', false);
|
||||
topic.set('draft_sequence', result.draft_sequence);
|
||||
postStream.commitPost(createdPost);
|
||||
addedToStream = true;
|
||||
} else {
|
||||
// We created a new topic, let's show it.
|
||||
composer.set('composeState', CLOSED);
|
||||
saving = false;
|
||||
|
||||
// Update topic_count for the category
|
||||
const category = Discourse.Site.currentProp('categories').find(function(x) { return x.get('id') === (parseInt(createdPost.get('category'),10) || 1); });
|
||||
if (category) category.incrementProperty('topic_count');
|
||||
Discourse.notifyPropertyChange('globalNotice');
|
||||
}
|
||||
|
||||
composer.clearState();
|
||||
composer.set('createdPost', createdPost);
|
||||
|
||||
if (addedToStream) {
|
||||
composer.set('composeState', CLOSED);
|
||||
} else if (saving) {
|
||||
composer.set('composeState', SAVING);
|
||||
}
|
||||
|
||||
return resolve({ post: result });
|
||||
}, function(error) {
|
||||
// If an error occurs
|
||||
if (postStream) {
|
||||
postStream.undoPost(createdPost);
|
||||
}
|
||||
composer.set('composeState', OPEN);
|
||||
|
||||
// TODO extract error handling code
|
||||
let parsedError;
|
||||
try {
|
||||
const parsedJSON = $.parseJSON(error.responseText);
|
||||
if (parsedJSON.errors) {
|
||||
parsedError = parsedJSON.errors[0];
|
||||
} else if (parsedJSON.failed) {
|
||||
parsedError = parsedJSON.message;
|
||||
}
|
||||
}
|
||||
catch(ex) {
|
||||
parsedError = "Unknown error saving post, try again. Error: " + error.status + " " + error.statusText;
|
||||
}
|
||||
reject(parsedError);
|
||||
});
|
||||
});
|
||||
|
||||
const composer = this;
|
||||
composer.set('composeState', SAVING);
|
||||
composer.set("stagedPost", state === "staged" && createdPost);
|
||||
|
||||
return promise;
|
||||
return createdPost.save().then(function(result) {
|
||||
let saving = true;
|
||||
createdPost.updateFromJson(result);
|
||||
|
||||
if (topic) {
|
||||
// It's no longer a new post
|
||||
createdPost.set('newPost', false);
|
||||
topic.set('draft_sequence', result.draft_sequence);
|
||||
postStream.commitPost(createdPost);
|
||||
addedToStream = true;
|
||||
} else {
|
||||
// We created a new topic, let's show it.
|
||||
composer.set('composeState', CLOSED);
|
||||
saving = false;
|
||||
|
||||
// Update topic_count for the category
|
||||
const category = composer.site.get('categories').find(function(x) { return x.get('id') === (parseInt(createdPost.get('category'),10) || 1); });
|
||||
if (category) category.incrementProperty('topic_count');
|
||||
Discourse.notifyPropertyChange('globalNotice');
|
||||
}
|
||||
|
||||
composer.clearState();
|
||||
composer.set('createdPost', createdPost);
|
||||
|
||||
if (addedToStream) {
|
||||
composer.set('composeState', CLOSED);
|
||||
} else if (saving) {
|
||||
composer.set('composeState', SAVING);
|
||||
}
|
||||
|
||||
return { post: result };
|
||||
}).catch(function(error) {
|
||||
|
||||
// If an error occurs
|
||||
if (postStream) {
|
||||
postStream.undoPost(createdPost);
|
||||
}
|
||||
composer.set('composeState', OPEN);
|
||||
|
||||
// TODO extract error handling code
|
||||
let parsedError;
|
||||
try {
|
||||
const parsedJSON = $.parseJSON(error.responseText);
|
||||
if (parsedJSON.errors) {
|
||||
parsedError = parsedJSON.errors[0];
|
||||
} else if (parsedJSON.failed) {
|
||||
parsedError = parsedJSON.message;
|
||||
}
|
||||
}
|
||||
catch(ex) {
|
||||
parsedError = "Unknown error saving post, try again. Error: " + error.status + " " + error.statusText;
|
||||
}
|
||||
throw parsedError;
|
||||
});
|
||||
},
|
||||
|
||||
getCookedHtml() {
|
||||
|
@ -604,7 +594,7 @@ const Composer = Discourse.Model.extend({
|
|||
// Do not save when there is no reply
|
||||
if (!this.get('reply')) return;
|
||||
// Do not save when the reply's length is too small
|
||||
if (this.get('replyLength') < Discourse.SiteSettings.min_post_length) return;
|
||||
if (this.get('replyLength') < this.siteSettings.min_post_length) return;
|
||||
|
||||
const data = {
|
||||
reply: this.get('reply'),
|
||||
|
@ -673,6 +663,14 @@ Composer.reopenClass({
|
|||
}
|
||||
},
|
||||
|
||||
create(args) {
|
||||
args = args || {};
|
||||
args.user = args.user || Discourse.User.current();
|
||||
args.site = args.site || Discourse.Site.current();
|
||||
args.siteSettings = args.siteSettings || Discourse.SiteSettings;
|
||||
return this._super(args);
|
||||
},
|
||||
|
||||
serializeToTopic(fieldName, property) {
|
||||
if (!property) { property = fieldName; }
|
||||
_edit_topic_serializer[fieldName] = property;
|
||||
|
|
|
@ -1,20 +1,12 @@
|
|||
/**
|
||||
A data model representing a post in a topic
|
||||
const Post = Discourse.Model.extend({
|
||||
|
||||
@class Post
|
||||
@extends Discourse.Model
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
Discourse.Post = Discourse.Model.extend({
|
||||
|
||||
init: function() {
|
||||
init() {
|
||||
this.set('replyHistory', []);
|
||||
},
|
||||
|
||||
shareUrl: function() {
|
||||
var user = Discourse.User.current();
|
||||
var userSuffix = user ? '?u=' + user.get('username_lower') : '';
|
||||
const user = Discourse.User.current();
|
||||
const userSuffix = user ? '?u=' + user.get('username_lower') : '';
|
||||
|
||||
if (this.get('firstPost')) {
|
||||
return this.get('topic.url') + userSuffix;
|
||||
|
@ -33,7 +25,7 @@ Discourse.Post = Discourse.Model.extend({
|
|||
userDeleted: Em.computed.empty('user_id'),
|
||||
|
||||
showName: function() {
|
||||
var name = this.get('name');
|
||||
const name = this.get('name');
|
||||
return name && (name !== this.get('username')) && Discourse.SiteSettings.display_name_on_posts;
|
||||
}.property('name', 'username'),
|
||||
|
||||
|
@ -69,17 +61,17 @@ Discourse.Post = Discourse.Model.extend({
|
|||
}.property("user_id"),
|
||||
|
||||
wikiChanged: function() {
|
||||
var data = { wiki: this.get("wiki") };
|
||||
const data = { wiki: this.get("wiki") };
|
||||
this._updatePost("wiki", data);
|
||||
}.observes('wiki'),
|
||||
|
||||
postTypeChanged: function () {
|
||||
var data = { post_type: this.get("post_type") };
|
||||
const data = { post_type: this.get("post_type") };
|
||||
this._updatePost("post_type", data);
|
||||
}.observes("post_type"),
|
||||
|
||||
_updatePost: function (field, data) {
|
||||
var self = this;
|
||||
_updatePost(field, data) {
|
||||
const self = this;
|
||||
Discourse.ajax("/posts/" + this.get("id") + "/" + field, {
|
||||
type: "PUT",
|
||||
data: data
|
||||
|
@ -103,7 +95,7 @@ Discourse.Post = Discourse.Model.extend({
|
|||
editCount: function() { return this.get('version') - 1; }.property('version'),
|
||||
|
||||
flagsAvailable: function() {
|
||||
var post = this;
|
||||
const post = this;
|
||||
return Discourse.Site.currentProp('flagTypes').filter(function(item) {
|
||||
return post.get("actionByName." + item.get('name_key') + ".can_act");
|
||||
});
|
||||
|
@ -119,9 +111,8 @@ Discourse.Post = Discourse.Model.extend({
|
|||
});
|
||||
}.property('actions_summary.@each.users', 'actions_summary.@each.count'),
|
||||
|
||||
// Save a post and call the callback when done.
|
||||
save: function(complete, error) {
|
||||
var self = this;
|
||||
save() {
|
||||
const self = this;
|
||||
if (!this.get('newPost')) {
|
||||
// We're updating a post
|
||||
return Discourse.ajax("/posts/" + (this.get('id')), {
|
||||
|
@ -135,19 +126,17 @@ Discourse.Post = Discourse.Model.extend({
|
|||
// If we received a category update, update it
|
||||
self.set('version', result.post.version);
|
||||
if (result.category) Discourse.Site.current().updateCategory(result.category);
|
||||
if (complete) complete(Discourse.Post.create(result.post));
|
||||
}).catch(function(result) {
|
||||
// Post failed to update
|
||||
if (error) error(result);
|
||||
return Discourse.Post.create(result.post);
|
||||
});
|
||||
|
||||
} else {
|
||||
// We're saving a post
|
||||
var data = this.getProperties(Discourse.Composer.serializedFieldsForCreate());
|
||||
const data = this.getProperties(Discourse.Composer.serializedFieldsForCreate());
|
||||
data.reply_to_post_number = this.get('reply_to_post_number');
|
||||
data.image_sizes = this.get('imageSizes');
|
||||
data.nested_post = true;
|
||||
|
||||
var metaData = this.get('metaData');
|
||||
const metaData = this.get('metaData');
|
||||
// Put the metaData into the request
|
||||
if (metaData) {
|
||||
data.meta_data = {};
|
||||
|
@ -158,34 +147,22 @@ Discourse.Post = Discourse.Model.extend({
|
|||
type: 'POST',
|
||||
data: data
|
||||
}).then(function(result) {
|
||||
// Post created
|
||||
if (complete) complete(Discourse.Post.create(result));
|
||||
}).catch(function(result) {
|
||||
// Failed to create a post
|
||||
if (error) error(result);
|
||||
return Discourse.Post.create(result.post);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
Expands the first post's content, if embedded and shortened.
|
||||
|
||||
@method expandFirstPost
|
||||
**/
|
||||
expand: function() {
|
||||
var self = this;
|
||||
// Expands the first post's content, if embedded and shortened.
|
||||
expand() {
|
||||
const self = this;
|
||||
return Discourse.ajax("/posts/" + this.get('id') + "/expand-embed").then(function(post) {
|
||||
self.set('cooked', "<section class='expanded-embed'>" + post.cooked + "</section>" );
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
Recover a deleted post
|
||||
|
||||
@method recover
|
||||
**/
|
||||
recover: function() {
|
||||
var post = this;
|
||||
// Recover a deleted post
|
||||
recover() {
|
||||
const post = this;
|
||||
post.setProperties({
|
||||
deleted_at: null,
|
||||
deleted_by: null,
|
||||
|
@ -207,11 +184,8 @@ Discourse.Post = Discourse.Model.extend({
|
|||
/**
|
||||
Changes the state of the post to be deleted. Does not call the server, that should be
|
||||
done elsewhere.
|
||||
|
||||
@method setDeletedState
|
||||
@param {Discourse.User} deletedBy The user deleting the post
|
||||
**/
|
||||
setDeletedState: function(deletedBy) {
|
||||
setDeletedState(deletedBy) {
|
||||
this.set('oldCooked', this.get('cooked'));
|
||||
|
||||
// Moderators can delete posts. Users can only trigger a deleted at message, unless delete_removed_posts_after is 0.
|
||||
|
@ -237,10 +211,8 @@ Discourse.Post = Discourse.Model.extend({
|
|||
Changes the state of the post to NOT be deleted. Does not call the server.
|
||||
This can only be called after setDeletedState was called, but the delete
|
||||
failed on the server.
|
||||
|
||||
@method undoDeletedState
|
||||
**/
|
||||
undoDeleteState: function() {
|
||||
undoDeleteState() {
|
||||
if (this.get('oldCooked')) {
|
||||
this.setProperties({
|
||||
deleted_at: null,
|
||||
|
@ -253,13 +225,7 @@ Discourse.Post = Discourse.Model.extend({
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
Deletes a post
|
||||
|
||||
@method destroy
|
||||
@param {Discourse.User} deletedBy The user deleting the post
|
||||
**/
|
||||
destroy: function(deletedBy) {
|
||||
destroy(deletedBy) {
|
||||
this.setDeletedState(deletedBy);
|
||||
return Discourse.ajax("/posts/" + this.get('id'), {
|
||||
data: { context: window.location.pathname },
|
||||
|
@ -270,14 +236,11 @@ Discourse.Post = Discourse.Model.extend({
|
|||
/**
|
||||
Updates a post from another's attributes. This will normally happen when a post is loading but
|
||||
is already found in an identity map.
|
||||
|
||||
@method updateFromPost
|
||||
@param {Discourse.Post} otherPost The post we're updating from
|
||||
**/
|
||||
updateFromPost: function(otherPost) {
|
||||
var self = this;
|
||||
updateFromPost(otherPost) {
|
||||
const self = this;
|
||||
Object.keys(otherPost).forEach(function (key) {
|
||||
var value = otherPost[key],
|
||||
let value = otherPost[key],
|
||||
oldValue = self[key];
|
||||
|
||||
if (key === "replyHistory") {
|
||||
|
@ -287,7 +250,7 @@ Discourse.Post = Discourse.Model.extend({
|
|||
if (!value) { value = null; }
|
||||
if (!oldValue) { oldValue = null; }
|
||||
|
||||
var skip = false;
|
||||
let skip = false;
|
||||
if (typeof value !== "function" && oldValue !== value) {
|
||||
// wishing for an identity map
|
||||
if (key === "reply_to_user" && value && oldValue) {
|
||||
|
@ -304,17 +267,14 @@ Discourse.Post = Discourse.Model.extend({
|
|||
/**
|
||||
Updates a post from a JSON packet. This is normally done after the post is saved to refresh any
|
||||
attributes.
|
||||
|
||||
@method updateFromJson
|
||||
@param {Object} obj The Json data to update with
|
||||
**/
|
||||
updateFromJson: function(obj) {
|
||||
updateFromJson(obj) {
|
||||
if (!obj) return;
|
||||
|
||||
var skip, oldVal;
|
||||
let skip, oldVal;
|
||||
|
||||
// Update all the properties
|
||||
var post = this;
|
||||
const post = this;
|
||||
_.each(obj, function(val,key) {
|
||||
if (key !== 'actions_summary'){
|
||||
oldVal = post[key];
|
||||
|
@ -336,12 +296,11 @@ Discourse.Post = Discourse.Model.extend({
|
|||
// Rebuild actions summary
|
||||
this.set('actions_summary', Em.A());
|
||||
if (obj.actions_summary) {
|
||||
var lookup = Em.Object.create();
|
||||
const lookup = Em.Object.create();
|
||||
_.each(obj.actions_summary,function(a) {
|
||||
var actionSummary;
|
||||
a.post = post;
|
||||
a.actionType = Discourse.Site.current().postActionTypeById(a.id);
|
||||
actionSummary = Discourse.ActionSummary.create(a);
|
||||
const actionSummary = Discourse.ActionSummary.create(a);
|
||||
post.get('actions_summary').pushObject(actionSummary);
|
||||
lookup.set(a.actionType.get('name_key'), actionSummary);
|
||||
});
|
||||
|
@ -350,7 +309,7 @@ Discourse.Post = Discourse.Model.extend({
|
|||
},
|
||||
|
||||
// Load replies to this post
|
||||
loadReplies: function() {
|
||||
loadReplies() {
|
||||
if(this.get('loadingReplies')){
|
||||
return;
|
||||
}
|
||||
|
@ -358,12 +317,12 @@ Discourse.Post = Discourse.Model.extend({
|
|||
this.set('loadingReplies', true);
|
||||
this.set('replies', []);
|
||||
|
||||
var self = this;
|
||||
const self = this;
|
||||
return Discourse.ajax("/posts/" + (this.get('id')) + "/replies")
|
||||
.then(function(loaded) {
|
||||
var replies = self.get('replies');
|
||||
const replies = self.get('replies');
|
||||
_.each(loaded,function(reply) {
|
||||
var post = Discourse.Post.create(reply);
|
||||
const post = Discourse.Post.create(reply);
|
||||
post.set('topic', self.get('topic'));
|
||||
replies.pushObject(post);
|
||||
});
|
||||
|
@ -375,7 +334,7 @@ Discourse.Post = Discourse.Model.extend({
|
|||
|
||||
// Whether to show replies directly below
|
||||
showRepliesBelow: function() {
|
||||
var replyCount = this.get('reply_count');
|
||||
const replyCount = this.get('reply_count');
|
||||
|
||||
// We don't show replies if there aren't any
|
||||
if (replyCount === 0) return false;
|
||||
|
@ -387,13 +346,13 @@ Discourse.Post = Discourse.Model.extend({
|
|||
if (replyCount > 1) return true;
|
||||
|
||||
// If we have *exactly* one reply, we have to consider if it's directly below us
|
||||
var topic = this.get('topic');
|
||||
const topic = this.get('topic');
|
||||
return !topic.isReplyDirectlyBelow(this);
|
||||
|
||||
}.property('reply_count'),
|
||||
|
||||
expandHidden: function() {
|
||||
var self = this;
|
||||
expandHidden() {
|
||||
const self = this;
|
||||
return Discourse.ajax("/posts/" + this.get('id') + "/cooked.json").then(function (result) {
|
||||
self.setProperties({
|
||||
cooked: result.cooked,
|
||||
|
@ -402,17 +361,17 @@ Discourse.Post = Discourse.Model.extend({
|
|||
});
|
||||
},
|
||||
|
||||
rebake: function () {
|
||||
rebake() {
|
||||
return Discourse.ajax("/posts/" + this.get("id") + "/rebake", { type: "PUT" });
|
||||
},
|
||||
|
||||
unhide: function () {
|
||||
unhide() {
|
||||
return Discourse.ajax("/posts/" + this.get("id") + "/unhide", { type: "PUT" });
|
||||
},
|
||||
|
||||
toggleBookmark: function() {
|
||||
var self = this,
|
||||
bookmarkedTopic;
|
||||
toggleBookmark() {
|
||||
const self = this;
|
||||
let bookmarkedTopic;
|
||||
|
||||
this.toggleProperty("bookmarked");
|
||||
|
||||
|
@ -435,16 +394,16 @@ Discourse.Post = Discourse.Model.extend({
|
|||
}
|
||||
});
|
||||
|
||||
Discourse.Post.reopenClass({
|
||||
Post.reopenClass({
|
||||
|
||||
createActionSummary: function(result) {
|
||||
createActionSummary(result) {
|
||||
if (result.actions_summary) {
|
||||
var lookup = Em.Object.create();
|
||||
const lookup = Em.Object.create();
|
||||
// this area should be optimized, it is creating way too many objects per post
|
||||
result.actions_summary = result.actions_summary.map(function(a) {
|
||||
a.post = result;
|
||||
a.actionType = Discourse.Site.current().postActionTypeById(a.id);
|
||||
var actionSummary = Discourse.ActionSummary.create(a);
|
||||
const actionSummary = Discourse.ActionSummary.create(a);
|
||||
lookup[a.actionType.name_key] = actionSummary;
|
||||
return actionSummary;
|
||||
});
|
||||
|
@ -452,8 +411,8 @@ Discourse.Post.reopenClass({
|
|||
}
|
||||
},
|
||||
|
||||
create: function(obj) {
|
||||
var result = this._super.apply(this, arguments);
|
||||
create(obj) {
|
||||
const result = this._super.apply(this, arguments);
|
||||
this.createActionSummary(result);
|
||||
if (obj && obj.reply_to_user) {
|
||||
result.set('reply_to_user', Discourse.User.create(obj.reply_to_user));
|
||||
|
@ -461,14 +420,14 @@ Discourse.Post.reopenClass({
|
|||
return result;
|
||||
},
|
||||
|
||||
updateBookmark: function(postId, bookmarked) {
|
||||
updateBookmark(postId, bookmarked) {
|
||||
return Discourse.ajax("/posts/" + postId + "/bookmark", {
|
||||
type: 'PUT',
|
||||
data: { bookmarked: bookmarked }
|
||||
});
|
||||
},
|
||||
|
||||
deleteMany: function(selectedPosts, selectedReplies) {
|
||||
deleteMany(selectedPosts, selectedReplies) {
|
||||
return Discourse.ajax("/posts/destroy_many", {
|
||||
type: 'DELETE',
|
||||
data: {
|
||||
|
@ -478,37 +437,33 @@ Discourse.Post.reopenClass({
|
|||
});
|
||||
},
|
||||
|
||||
loadRevision: function(postId, version) {
|
||||
loadRevision(postId, version) {
|
||||
return Discourse.ajax("/posts/" + postId + "/revisions/" + version + ".json").then(function (result) {
|
||||
return Ember.Object.create(result);
|
||||
});
|
||||
},
|
||||
|
||||
hideRevision: function(postId, version) {
|
||||
hideRevision(postId, version) {
|
||||
return Discourse.ajax("/posts/" + postId + "/revisions/" + version + "/hide", { type: 'PUT' });
|
||||
},
|
||||
|
||||
showRevision: function(postId, version) {
|
||||
showRevision(postId, version) {
|
||||
return Discourse.ajax("/posts/" + postId + "/revisions/" + version + "/show", { type: 'PUT' });
|
||||
},
|
||||
|
||||
loadQuote: function(postId) {
|
||||
loadQuote(postId) {
|
||||
return Discourse.ajax("/posts/" + postId + ".json").then(function (result) {
|
||||
var post = Discourse.Post.create(result);
|
||||
const post = Discourse.Post.create(result);
|
||||
return Discourse.Quote.build(post, post.get('raw'));
|
||||
});
|
||||
},
|
||||
|
||||
loadRawEmail: function(postId) {
|
||||
loadRawEmail(postId) {
|
||||
return Discourse.ajax("/posts/" + postId + "/raw-email").then(function (result) {
|
||||
return result.raw_email;
|
||||
});
|
||||
},
|
||||
|
||||
load: function(postId) {
|
||||
return Discourse.ajax("/posts/" + postId + ".json").then(function (result) {
|
||||
return Discourse.Post.create(result);
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export default Post;
|
|
@ -25,6 +25,7 @@
|
|||
//= require ./discourse/lib/safari-hacks
|
||||
//= require_tree ./discourse/adapters
|
||||
//= require ./discourse/models/model
|
||||
//= require ./discourse/models/post
|
||||
//= require ./discourse/models/user_action
|
||||
//= require ./discourse/models/composer
|
||||
//= require ./discourse/models/post-stream
|
||||
|
|
|
@ -336,7 +336,11 @@ class PostsController < ApplicationController
|
|||
# doesn't return the post as the root JSON object, but as a nested object.
|
||||
# If a param is present it uses that result structure.
|
||||
def backwards_compatible_json(json_obj, success)
|
||||
json_obj = json_obj[:post] || json_obj['post'] unless params[:nested_post]
|
||||
json_obj.symbolize_keys!
|
||||
if params[:nested_post].blank? && json_obj[:errors].blank?
|
||||
json_obj = json_obj[:post]
|
||||
end
|
||||
|
||||
render json: json_obj, status: (!!success) ? 200 : 422
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
import { acceptance } from "helpers/qunit-helpers";
|
||||
|
||||
acceptance("Composer", { loggedIn: true });
|
||||
|
||||
test("Tests the Composer controls", () => {
|
||||
visit("/");
|
||||
andThen(() => {
|
||||
ok(exists('#create-topic'), 'the create button is visible');
|
||||
});
|
||||
|
||||
click('#create-topic');
|
||||
andThen(() => {
|
||||
ok(exists('#wmd-input'), 'the composer input is visible');
|
||||
ok(exists('.title-input .popup-tip.bad.hide'), 'title errors are hidden by default');
|
||||
ok(exists('.textarea-wrapper .popup-tip.bad.hide'), 'body errors are hidden by default');
|
||||
});
|
||||
|
||||
click('a.toggle-preview');
|
||||
andThen(() => {
|
||||
ok(!exists('#wmd-preview:visible'), "clicking the toggle hides the preview");
|
||||
});
|
||||
|
||||
click('a.toggle-preview');
|
||||
andThen(() => {
|
||||
ok(exists('#wmd-preview:visible'), "clicking the toggle shows the preview again");
|
||||
});
|
||||
|
||||
click('#reply-control button.create');
|
||||
andThen(() => {
|
||||
ok(!exists('.title-input .popup-tip.bad.hide'), 'it shows the empty title error');
|
||||
ok(!exists('.textarea-wrapper .popup-tip.bad.hide'), 'it shows the empty body error');
|
||||
});
|
||||
|
||||
fillIn('#reply-title', "this is my new topic title");
|
||||
andThen(() => {
|
||||
ok(exists('.title-input .popup-tip.good'), 'the title is now good');
|
||||
});
|
||||
|
||||
fillIn('#wmd-input', "this is the *content* of a post");
|
||||
andThen(() => {
|
||||
equal(find('#wmd-preview').html(), "<p>this is the <em>content</em> of a post</p>", "it previews content");
|
||||
ok(exists('.textarea-wrapper .popup-tip.good'), 'the body is now good');
|
||||
});
|
||||
|
||||
click('#reply-control a.cancel');
|
||||
andThen(() => {
|
||||
ok(exists('.bootbox.modal'), 'it pops up a confirmation dialog');
|
||||
});
|
||||
|
||||
click('.modal-footer a:eq(1)');
|
||||
andThen(() => {
|
||||
ok(!exists('.bootbox.modal'), 'the confirmation can be cancelled');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
test("Create a topic with server side errors", () => {
|
||||
visit("/");
|
||||
click('#create-topic');
|
||||
fillIn('#reply-title', "this title triggers an error");
|
||||
fillIn('#wmd-input', "this is the *content* of a post");
|
||||
click('#reply-control button.create');
|
||||
andThen(() => {
|
||||
ok(exists('.bootbox.modal'), 'it pops up an error message');
|
||||
});
|
||||
click('.bootbox.modal a.btn-primary');
|
||||
andThen(() => {
|
||||
ok(!exists('.bootbox.modal'), 'it dismisses the error');
|
||||
ok(exists('#wmd-input'), 'the composer input is visible');
|
||||
});
|
||||
});
|
||||
|
||||
test("Create a Topic", () => {
|
||||
visit("/");
|
||||
click('#create-topic');
|
||||
fillIn('#reply-title', "Internationalization Localization");
|
||||
fillIn('#wmd-input', "this is the *content* of a new topic post");
|
||||
click('#reply-control button.create');
|
||||
andThen(() => {
|
||||
equal(currentURL(), "/t/internationalization-localization/280", "it transitions to the newly created topic URL");
|
||||
});
|
||||
});
|
||||
|
||||
test("Create a Reply", () => {
|
||||
visit("/t/internationalization-localization/280");
|
||||
|
||||
click('#topic-footer-buttons .btn.create');
|
||||
andThen(() => {
|
||||
ok(exists('#wmd-input'), 'the composer input is visible');
|
||||
ok(!exists('#reply-title'), 'there is no title since this is a reply');
|
||||
});
|
||||
|
||||
fillIn('#wmd-input', 'this is the content of my reply');
|
||||
click('#reply-control button.create');
|
||||
andThen(() => {
|
||||
exists('#post_12345', 'it inserts the post into the document');
|
||||
});
|
||||
});
|
||||
|
||||
test("Edit the first post", () => {
|
||||
visit("/t/internationalization-localization/280");
|
||||
|
||||
click('.topic-post:eq(0) button[data-action=showMoreActions]');
|
||||
click('.topic-post:eq(0) button[data-action=edit]');
|
||||
andThen(() => {
|
||||
equal(find('#wmd-input').val().indexOf('Any plans to support'), 0, 'it populates the input with the post text');
|
||||
});
|
||||
|
||||
fillIn('#wmd-input', "This is the new text for the post");
|
||||
fillIn('#reply-title', "This is the new text for the title");
|
||||
click('#reply-control button.create');
|
||||
andThen(() => {
|
||||
ok(!exists('#wmd-input'), 'it closes the composer');
|
||||
ok(find('#topic-title h1').text().indexOf('This is the new text for the title') !== -1, 'it shows the new title');
|
||||
ok(find('.topic-post:eq(0) .cooked').text().indexOf('This is the new text for the post') !== -1, 'it updates the post');
|
||||
});
|
||||
});
|
|
@ -1,10 +1,6 @@
|
|||
import { acceptance } from "helpers/qunit-helpers";
|
||||
|
||||
acceptance("Header (Staff)", {
|
||||
user: { username: 'test',
|
||||
staff: true,
|
||||
site_flagged_posts_count: 1 }
|
||||
});
|
||||
acceptance("Header (Staff)", { loggedIn: true });
|
||||
|
||||
test("header", () => {
|
||||
visit("/");
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
export default {
|
||||
"/posts/398": {"id":398,"name":"Uwe Keim","username":"uwe_keim","avatar_template":"/user_avatar/meta.discourse.org/uwe_keim/{size}/5697.png","uploaded_avatar_id":5697,"created_at":"2013-02-05T21:29:00.280Z","cooked":"<p>Any plans to support localization of UI elements, so that I (for example) could set up a completely German speaking forum?</p>","post_number":1,"post_type":1,"updated_at":"2013-02-05T21:29:00.280Z","like_count":0,"reply_count":1,"reply_to_post_number":null,"quote_count":0,"avg_time":25,"incoming_link_count":314,"reads":475,"score":1702.25,"yours":false,"topic_id":280,"topic_slug":"internationalization-localization","display_username":"Uwe Keim","primary_group_name":null,"version":2,"can_edit":true,"can_delete":false,"can_recover":true,"user_title":null,"raw":"Any plans to support localization of UI elements, so that I (for example) could set up a completely German speaking forum?","actions_summary":[{"id":2,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":3,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":4,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":5,"count":0,"hidden":true,"can_act":true,"can_defer_flags":false},{"id":6,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":7,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false},{"id":8,"count":0,"hidden":false,"can_act":true,"can_defer_flags":false}],"moderator":false,"admin":false,"staff":false,"user_id":255,"hidden":false,"hidden_reason_id":null,"trust_level":2,"deleted_at":null,"user_deleted":false,"edit_reason":null,"can_view_edit_history":true,"wiki":false}
|
||||
};
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
export default {
|
||||
"/session/current.json": {"current_user":{"id":19,"username":"eviltrout","uploaded_avatar_id":5275,"avatar_template":"/user_avatar/localhost/eviltrout/{size}/5275.png","name":"Robin Ward","total_unread_notifications":205,"unread_notifications":0,"unread_private_messages":0,"admin":true,"notification_channel_position":null,"site_flagged_posts_count":1,"moderator":true,"staff":true,"title":"co-founder","reply_count":859,"topic_count":36,"enable_quoting":true,"external_links_in_new_tab":false,"dynamic_favicon":true,"trust_level":4,"can_edit":true,"can_invite_to_forum":true,"should_be_redirected_to_top":false,"disable_jump_reply":false,"custom_fields":{},"muted_category_ids":[],"dismissed_banner_key":null,"akismet_review_count":0}}
|
||||
};
|
||||
|
File diff suppressed because one or more lines are too long
|
@ -2,7 +2,7 @@ function parsePostData(query) {
|
|||
const result = {};
|
||||
query.split("&").forEach(function(part) {
|
||||
const item = part.split("=");
|
||||
result[item[0]] = decodeURIComponent(item[1]);
|
||||
result[item[0]] = decodeURIComponent(item[1]).replace(/\+/g, ' ');
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
@ -33,9 +33,16 @@ const _moreWidgets = [
|
|||
{id: 224, name: 'Good Repellant'}
|
||||
];
|
||||
|
||||
function loggedIn() {
|
||||
return !!Discourse.User.current();
|
||||
}
|
||||
|
||||
export default function() {
|
||||
|
||||
const server = new Pretender(function() {
|
||||
|
||||
const fixturesByUrl = {};
|
||||
|
||||
// Load any fixtures automatically
|
||||
const self = this;
|
||||
Ember.keys(require._eak_seen).forEach(function(entry) {
|
||||
|
@ -44,6 +51,7 @@ export default function() {
|
|||
if (fixture && fixture.default) {
|
||||
const obj = fixture.default;
|
||||
Ember.keys(obj).forEach(function(url) {
|
||||
fixturesByUrl[url] = obj[url];
|
||||
self.get(url, function() {
|
||||
return response(obj[url]);
|
||||
});
|
||||
|
@ -52,6 +60,20 @@ export default function() {
|
|||
}
|
||||
});
|
||||
|
||||
this.get('/composer-messages', () => { return response([]); });
|
||||
|
||||
this.get("/latest.json", () => {
|
||||
const json = fixturesByUrl['/latest.json'];
|
||||
|
||||
if (loggedIn()) {
|
||||
// Stuff to let us post
|
||||
json.topic_list.can_create_topic = true;
|
||||
json.topic_list.draft_key = "new_topic";
|
||||
json.topic_list.draft_sequence = 1;
|
||||
}
|
||||
return response(json);
|
||||
});
|
||||
|
||||
this.get("/t/id_for/:slug", function() {
|
||||
return response({id: 280, slug: "internationalization-localization", url: "/t/internationalization-localization/280"});
|
||||
});
|
||||
|
@ -99,6 +121,33 @@ export default function() {
|
|||
this.delete('/posts/:post_id', success);
|
||||
this.put('/posts/:post_id/recover', success);
|
||||
|
||||
this.put('/posts/:post_id', (request) => {
|
||||
return response({ post: {id: request.params.post_id, version: 2 } });
|
||||
});
|
||||
|
||||
this.put('/t/:slug/:id', (request) => {
|
||||
const data = parsePostData(request.requestBody);
|
||||
|
||||
return response(200, { basic_topic: {id: request.params.id,
|
||||
title: data.title,
|
||||
fancy_title: data.title,
|
||||
slug: request.params.slug } })
|
||||
});
|
||||
|
||||
this.post('/posts', function(request) {
|
||||
const data = parsePostData(request.requestBody);
|
||||
|
||||
if (data.title === "this title triggers an error") {
|
||||
return response(422, {errors: ['That title has already been taken']});
|
||||
} else {
|
||||
return response(200, {
|
||||
success: true,
|
||||
action: 'create_post',
|
||||
post: {id: 12345, topic_id: 280, topic_slug: 'internationalization-localization'}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.get('/widgets/:widget_id', function(request) {
|
||||
const w = _widgets.findBy('id', parseInt(request.params.widget_id));
|
||||
if (w) {
|
||||
|
@ -130,8 +179,11 @@ export default function() {
|
|||
});
|
||||
|
||||
this.delete('/widgets/:widget_id', success);
|
||||
});
|
||||
|
||||
this.post('/topics/timings', function() {
|
||||
return response(200, {});
|
||||
});
|
||||
});
|
||||
|
||||
server.prepareBody = function(body){
|
||||
if (body && typeof body === "object") {
|
||||
|
|
|
@ -1,20 +1,59 @@
|
|||
/* global asyncTest */
|
||||
|
||||
import sessionFixtures from 'fixtures/session-fixtures';
|
||||
import siteFixtures from 'fixtures/site_fixtures';
|
||||
import HeaderView from 'discourse/views/header';
|
||||
|
||||
function currentUser() {
|
||||
return Discourse.User.create(sessionFixtures['/session/current.json'].current_user);
|
||||
}
|
||||
|
||||
function logIn() {
|
||||
Discourse.User.resetCurrent(currentUser());
|
||||
}
|
||||
|
||||
const Plugin = $.fn.modal;
|
||||
const Modal = Plugin.Constructor;
|
||||
|
||||
function AcceptanceModal(option, _relatedTarget) {
|
||||
return this.each(function () {
|
||||
var $this = $(this);
|
||||
var data = $this.data('bs.modal');
|
||||
var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option === 'object' && option);
|
||||
|
||||
if (!data) $this.data('bs.modal', (data = new Modal(this, options)));
|
||||
data.$body = $('#ember-testing');
|
||||
|
||||
if (typeof option === 'string') data[option](_relatedTarget);
|
||||
else if (options.show) data.show(_relatedTarget);
|
||||
});
|
||||
}
|
||||
|
||||
window.bootbox.$body = $('#ember-testing');
|
||||
$.fn.modal = AcceptanceModal;
|
||||
|
||||
var oldAvatar = Discourse.Utilities.avatarImg;
|
||||
|
||||
function acceptance(name, options) {
|
||||
module("Acceptance: " + name, {
|
||||
setup: function() {
|
||||
Ember.run(Discourse, Discourse.advanceReadiness);
|
||||
|
||||
// Don't render avatars in acceptance tests, it's faster and no 404s
|
||||
Discourse.Utilities.avatarImg = () => "";
|
||||
|
||||
// For now don't do scrolling stuff in Test Mode
|
||||
Ember.CloakedCollectionView.scrolled = Ember.K;
|
||||
HeaderView.reopen({examineDockHeader: Ember.K});
|
||||
|
||||
var siteJson = siteFixtures['site.json'].site;
|
||||
if (options) {
|
||||
if (options.setup) {
|
||||
options.setup.call(this);
|
||||
}
|
||||
|
||||
if (options.user) {
|
||||
Discourse.User.resetCurrent(Discourse.User.create(options.user));
|
||||
if (options.loggedIn) {
|
||||
logIn();
|
||||
}
|
||||
|
||||
if (options.settings) {
|
||||
|
@ -34,6 +73,7 @@ function acceptance(name, options) {
|
|||
options.teardown.call(this);
|
||||
}
|
||||
|
||||
Discourse.Utilities.avatarImg = oldAvatar;
|
||||
Discourse.reset();
|
||||
}
|
||||
});
|
||||
|
@ -61,4 +101,4 @@ function fixture(selector) {
|
|||
return $("#qunit-fixture");
|
||||
}
|
||||
|
||||
export { acceptance, controllerFor, asyncTestDiscourse, fixture };
|
||||
export { acceptance, controllerFor, asyncTestDiscourse, fixture, logIn, currentUser };
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
module("Discourse.Composer", {
|
||||
setup: function() {
|
||||
sandbox.stub(Discourse.User, 'currentProp').withArgs('admin').returns(false);
|
||||
},
|
||||
import { currentUser } from 'helpers/qunit-helpers';
|
||||
|
||||
teardown: function() {
|
||||
Discourse.User.currentProp.restore();
|
||||
}
|
||||
});
|
||||
module("model:composer");
|
||||
|
||||
function createComposer(opts) {
|
||||
opts = opts || {};
|
||||
opts.user = opts.user || currentUser();
|
||||
opts.site = Discourse.Site.current();
|
||||
opts.siteSettings = Discourse.SiteSettings;
|
||||
return Discourse.Composer.create(opts);
|
||||
}
|
||||
|
||||
test('replyLength', function() {
|
||||
var replyLength = function(val, expectedLength) {
|
||||
var composer = Discourse.Composer.create({ reply: val });
|
||||
const replyLength = function(val, expectedLength) {
|
||||
const composer = createComposer({ reply: val });
|
||||
equal(composer.get('replyLength'), expectedLength);
|
||||
};
|
||||
|
||||
|
@ -23,8 +25,8 @@ test('replyLength', function() {
|
|||
|
||||
test('missingReplyCharacters', function() {
|
||||
Discourse.SiteSettings.min_first_post_length = 40;
|
||||
var missingReplyCharacters = function(val, isPM, isFirstPost, expected, message) {
|
||||
var composer = Discourse.Composer.create({ reply: val, creatingPrivateMessage: isPM, creatingTopic: isFirstPost });
|
||||
const missingReplyCharacters = function(val, isPM, isFirstPost, expected, message) {
|
||||
const composer = createComposer({ reply: val, creatingPrivateMessage: isPM, creatingTopic: isFirstPost });
|
||||
equal(composer.get('missingReplyCharacters'), expected, message);
|
||||
};
|
||||
|
||||
|
@ -34,8 +36,8 @@ test('missingReplyCharacters', function() {
|
|||
});
|
||||
|
||||
test('missingTitleCharacters', function() {
|
||||
var missingTitleCharacters = function(val, isPM, expected, message) {
|
||||
var composer = Discourse.Composer.create({ title: val, creatingPrivateMessage: isPM });
|
||||
const missingTitleCharacters = function(val, isPM, expected, message) {
|
||||
const composer = createComposer({ title: val, creatingPrivateMessage: isPM });
|
||||
equal(composer.get('missingTitleCharacters'), expected, message);
|
||||
};
|
||||
|
||||
|
@ -44,7 +46,7 @@ test('missingTitleCharacters', function() {
|
|||
});
|
||||
|
||||
test('replyDirty', function() {
|
||||
var composer = Discourse.Composer.create();
|
||||
const composer = createComposer();
|
||||
ok(!composer.get('replyDirty'), "by default it's false");
|
||||
|
||||
composer.setProperties({
|
||||
|
@ -58,7 +60,7 @@ test('replyDirty', function() {
|
|||
});
|
||||
|
||||
test("appendText", function() {
|
||||
var composer = Discourse.Composer.create();
|
||||
const composer = createComposer();
|
||||
|
||||
blank(composer.get('reply'), "the reply is blank by default");
|
||||
|
||||
|
@ -89,7 +91,7 @@ test("appendText", function() {
|
|||
test("Title length for regular topics", function() {
|
||||
Discourse.SiteSettings.min_topic_title_length = 5;
|
||||
Discourse.SiteSettings.max_topic_title_length = 10;
|
||||
var composer = Discourse.Composer.create();
|
||||
const composer = createComposer();
|
||||
|
||||
composer.set('title', 'asdf');
|
||||
ok(!composer.get('titleLengthValid'), "short titles are not valid");
|
||||
|
@ -104,7 +106,7 @@ test("Title length for regular topics", function() {
|
|||
test("Title length for private messages", function() {
|
||||
Discourse.SiteSettings.min_private_message_title_length = 5;
|
||||
Discourse.SiteSettings.max_topic_title_length = 10;
|
||||
var composer = Discourse.Composer.create({action: Discourse.Composer.PRIVATE_MESSAGE});
|
||||
const composer = createComposer({action: Discourse.Composer.PRIVATE_MESSAGE});
|
||||
|
||||
composer.set('title', 'asdf');
|
||||
ok(!composer.get('titleLengthValid'), "short titles are not valid");
|
||||
|
@ -119,7 +121,7 @@ test("Title length for private messages", function() {
|
|||
test("Title length for private messages", function() {
|
||||
Discourse.SiteSettings.min_private_message_title_length = 5;
|
||||
Discourse.SiteSettings.max_topic_title_length = 10;
|
||||
var composer = Discourse.Composer.create({action: Discourse.Composer.PRIVATE_MESSAGE});
|
||||
const composer = createComposer({action: Discourse.Composer.PRIVATE_MESSAGE});
|
||||
|
||||
composer.set('title', 'asdf');
|
||||
ok(!composer.get('titleLengthValid'), "short titles are not valid");
|
||||
|
@ -132,10 +134,10 @@ test("Title length for private messages", function() {
|
|||
});
|
||||
|
||||
test('editingFirstPost', function() {
|
||||
var composer = Discourse.Composer.create();
|
||||
const composer = createComposer();
|
||||
ok(!composer.get('editingFirstPost'), "it's false by default");
|
||||
|
||||
var post = Discourse.Post.create({id: 123, post_number: 2});
|
||||
const post = Discourse.Post.create({id: 123, post_number: 2});
|
||||
composer.setProperties({post: post, action: Discourse.Composer.EDIT });
|
||||
ok(!composer.get('editingFirstPost'), "it's false when not editing the first post");
|
||||
|
||||
|
@ -145,7 +147,7 @@ test('editingFirstPost', function() {
|
|||
});
|
||||
|
||||
test('clearState', function() {
|
||||
var composer = Discourse.Composer.create({
|
||||
const composer = createComposer({
|
||||
originalText: 'asdf',
|
||||
reply: 'asdf2',
|
||||
post: Discourse.Post.create({id: 1}),
|
||||
|
@ -163,61 +165,48 @@ test('clearState', function() {
|
|||
|
||||
test('initial category when uncategorized is allowed', function() {
|
||||
Discourse.SiteSettings.allow_uncategorized_topics = true;
|
||||
var composer = Discourse.Composer.open({action: 'createTopic', draftKey: 'asfd', draftSequence: 1});
|
||||
const composer = Discourse.Composer.open({action: 'createTopic', draftKey: 'asfd', draftSequence: 1});
|
||||
equal(composer.get('categoryId'),undefined,"Uncategorized by default");
|
||||
});
|
||||
|
||||
test('initial category when uncategorized is not allowed', function() {
|
||||
Discourse.SiteSettings.allow_uncategorized_topics = false;
|
||||
var composer = Discourse.Composer.open({action: 'createTopic', draftKey: 'asfd', draftSequence: 1});
|
||||
const composer = Discourse.Composer.open({action: 'createTopic', draftKey: 'asfd', draftSequence: 1});
|
||||
ok(composer.get('categoryId') === undefined, "Uncategorized by default. Must choose a category.");
|
||||
});
|
||||
|
||||
test('showPreview', function() {
|
||||
var new_composer = function() {
|
||||
const newComposer = function() {
|
||||
return Discourse.Composer.open({action: 'createTopic', draftKey: 'asfd', draftSequence: 1});
|
||||
};
|
||||
|
||||
Discourse.Mobile.mobileView = true;
|
||||
equal(new_composer().get('showPreview'), false, "Don't show preview in mobile view");
|
||||
equal(newComposer().get('showPreview'), false, "Don't show preview in mobile view");
|
||||
|
||||
Discourse.KeyValueStore.set({ key: 'composer.showPreview', value: 'true' });
|
||||
equal(new_composer().get('showPreview'), false, "Don't show preview in mobile view even if KeyValueStore wants to");
|
||||
equal(newComposer().get('showPreview'), false, "Don't show preview in mobile view even if KeyValueStore wants to");
|
||||
Discourse.KeyValueStore.remove('composer.showPreview');
|
||||
|
||||
Discourse.Mobile.mobileView = false;
|
||||
equal(new_composer().get('showPreview'), true, "Show preview by default in desktop view");
|
||||
equal(newComposer().get('showPreview'), true, "Show preview by default in desktop view");
|
||||
});
|
||||
|
||||
test('open with a quote', function() {
|
||||
var quote = '[quote="neil, post:5, topic:413"]\nSimmer down you two.\n[/quote]';
|
||||
var new_composer = function() {
|
||||
const quote = '[quote="neil, post:5, topic:413"]\nSimmer down you two.\n[/quote]';
|
||||
const newComposer = function() {
|
||||
return Discourse.Composer.open({action: Discourse.Composer.REPLY, draftKey: 'asfd', draftSequence: 1, quote: quote});
|
||||
};
|
||||
|
||||
equal(new_composer().get('originalText'), quote, "originalText is the quote" );
|
||||
equal(new_composer().get('replyDirty'), false, "replyDirty is initally false with a quote" );
|
||||
});
|
||||
|
||||
|
||||
module("Discourse.Composer as admin", {
|
||||
setup: function() {
|
||||
Discourse.SiteSettings.min_topic_title_length = 5;
|
||||
Discourse.SiteSettings.max_topic_title_length = 10;
|
||||
sandbox.stub(Discourse.User, 'currentProp').withArgs('admin').returns(true);
|
||||
},
|
||||
|
||||
teardown: function() {
|
||||
Discourse.SiteSettings.min_topic_title_length = 15;
|
||||
Discourse.SiteSettings.max_topic_title_length = 255;
|
||||
Discourse.User.currentProp.restore();
|
||||
}
|
||||
equal(newComposer().get('originalText'), quote, "originalText is the quote" );
|
||||
equal(newComposer().get('replyDirty'), false, "replyDirty is initally false with a quote" );
|
||||
});
|
||||
|
||||
test("Title length for static page topics as admin", function() {
|
||||
var composer = Discourse.Composer.create();
|
||||
Discourse.SiteSettings.min_topic_title_length = 5;
|
||||
Discourse.SiteSettings.max_topic_title_length = 10;
|
||||
const composer = createComposer();
|
||||
|
||||
var post = Discourse.Post.create({id: 123, post_number: 2, static_doc: true});
|
||||
const post = Discourse.Post.create({id: 123, post_number: 2, static_doc: true});
|
||||
composer.setProperties({post: post, action: Discourse.Composer.EDIT });
|
||||
|
||||
composer.set('title', 'asdf');
|
||||
|
|
|
@ -59,7 +59,11 @@ sinon.config = {
|
|||
useFakeServer: false
|
||||
};
|
||||
|
||||
window.assetPath = function() { return null; };
|
||||
window.assetPath = function(url) {
|
||||
if (url.indexOf('defer') === 0) {
|
||||
return "/assets/" + url;
|
||||
}
|
||||
};
|
||||
|
||||
// Stop the message bus so we don't get ajax calls
|
||||
window.MessageBus.stop();
|
||||
|
|
|
@ -427,16 +427,17 @@ var bootbox = window.bootbox || (function(document, $) {
|
|||
});
|
||||
|
||||
// well, *if* we have a primary - give the first dom element focus
|
||||
div.on('shown', function() {
|
||||
div.on('shown.bs.modal', function() {
|
||||
div.find("a.btn-primary:first").focus();
|
||||
});
|
||||
|
||||
div.on('hidden', function() {
|
||||
div.on('hidden.bs.modal', function() {
|
||||
div.remove();
|
||||
});
|
||||
|
||||
// wire up button handlers
|
||||
div.on('click', '.modal-footer a', function(e) {
|
||||
Ember.run(function() {
|
||||
|
||||
var handler = $(this).data("handler"),
|
||||
cb = callbacks[handler],
|
||||
|
@ -462,10 +463,11 @@ var bootbox = window.bootbox || (function(document, $) {
|
|||
if (hideModal !== false) {
|
||||
div.modal("hide");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// stick the modal right at the bottom of the main body out of the way
|
||||
$("body").append(div);
|
||||
(that.$body || $("body")).append(div);
|
||||
|
||||
div.modal({
|
||||
// unless explicitly overridden take whatever our default backdrop value is
|
||||
|
|
|
@ -1,218 +1,338 @@
|
|||
/* =========================================================
|
||||
* bootstrap-modal.js v2.0.3
|
||||
* http://twitter.github.com/bootstrap/javascript.html#modals
|
||||
* =========================================================
|
||||
* Copyright 2012 Twitter, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* ========================================================= */
|
||||
/* ========================================================================
|
||||
* Bootstrap: modal.js v3.3.4
|
||||
* http://getbootstrap.com/javascript/#modals
|
||||
* ========================================================================
|
||||
* Copyright 2011-2015 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
* ======================================================================== */
|
||||
|
||||
|
||||
!function ($) {
|
||||
+function ($) {
|
||||
'use strict';
|
||||
|
||||
"use strict"; // jshint ;_;
|
||||
// MODAL CLASS DEFINITION
|
||||
// ======================
|
||||
|
||||
var Modal = function (element, options) {
|
||||
this.options = options
|
||||
this.$body = $(document.body)
|
||||
this.$element = $(element)
|
||||
this.$dialog = this.$element.find('.modal-dialog')
|
||||
this.$backdrop = null
|
||||
this.isShown = null
|
||||
this.originalBodyPad = null
|
||||
this.scrollbarWidth = 0
|
||||
this.ignoreBackdropClick = false
|
||||
|
||||
/* MODAL CLASS DEFINITION
|
||||
* ====================== */
|
||||
|
||||
var Modal = function (content, options) {
|
||||
this.options = options
|
||||
this.$element = $(content)
|
||||
.delegate('[data-dismiss="modal"]', 'click.dismiss.modal', $.proxy(this.hide, this))
|
||||
if (this.options.remote) {
|
||||
this.$element
|
||||
.find('.modal-content')
|
||||
.load(this.options.remote, $.proxy(function () {
|
||||
this.$element.trigger('loaded.bs.modal')
|
||||
}, this))
|
||||
}
|
||||
}
|
||||
|
||||
Modal.prototype = {
|
||||
Modal.VERSION = '3.3.4'
|
||||
|
||||
constructor: Modal
|
||||
|
||||
, toggle: function () {
|
||||
return this[!this.isShown ? 'show' : 'hide']()
|
||||
}
|
||||
|
||||
, show: function () {
|
||||
var that = this
|
||||
, e = $.Event('show')
|
||||
|
||||
this.$element.trigger(e)
|
||||
|
||||
if (this.isShown || e.isDefaultPrevented()) return
|
||||
|
||||
$('body').addClass('modal-open')
|
||||
|
||||
this.isShown = true
|
||||
|
||||
escape.call(this)
|
||||
backdrop.call(this, function () {
|
||||
var transition = $.support.transition && that.$element.hasClass('fade')
|
||||
|
||||
if (!that.$element.parent().length) {
|
||||
that.$element.appendTo(document.body) //don't move modals dom position
|
||||
}
|
||||
|
||||
that.$element
|
||||
.show()
|
||||
|
||||
if (transition) {
|
||||
that.$element[0].offsetWidth // force reflow
|
||||
}
|
||||
|
||||
that.$element.addClass('in')
|
||||
|
||||
transition ?
|
||||
that.$element.one($.support.transition.end, function () { that.$element.trigger('shown') }) :
|
||||
that.$element.trigger('shown')
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
, hide: function (e) {
|
||||
e && e.preventDefault()
|
||||
|
||||
var that = this
|
||||
|
||||
e = $.Event('hide')
|
||||
|
||||
this.$element.trigger(e)
|
||||
|
||||
if (!this.isShown || e.isDefaultPrevented()) return
|
||||
|
||||
this.isShown = false
|
||||
|
||||
$('body').removeClass('modal-open')
|
||||
|
||||
escape.call(this)
|
||||
|
||||
this.$element.removeClass('in')
|
||||
|
||||
$.support.transition && this.$element.hasClass('fade') ?
|
||||
hideWithTransition.call(this) :
|
||||
hideModal.call(this)
|
||||
}
|
||||
Modal.TRANSITION_DURATION = 300
|
||||
Modal.BACKDROP_TRANSITION_DURATION = 150
|
||||
|
||||
Modal.DEFAULTS = {
|
||||
backdrop: true,
|
||||
keyboard: true,
|
||||
show: true
|
||||
}
|
||||
|
||||
Modal.prototype.toggle = function (_relatedTarget) {
|
||||
return this.isShown ? this.hide() : this.show(_relatedTarget)
|
||||
}
|
||||
|
||||
/* MODAL PRIVATE METHODS
|
||||
* ===================== */
|
||||
|
||||
function hideWithTransition() {
|
||||
Modal.prototype.show = function (_relatedTarget) {
|
||||
var that = this
|
||||
, timeout = setTimeout(function () {
|
||||
that.$element.off($.support.transition.end)
|
||||
hideModal.call(that)
|
||||
}, 500)
|
||||
var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })
|
||||
|
||||
this.$element.one($.support.transition.end, function () {
|
||||
clearTimeout(timeout)
|
||||
hideModal.call(that)
|
||||
this.$element.trigger(e)
|
||||
|
||||
if (this.isShown || e.isDefaultPrevented()) return
|
||||
|
||||
this.isShown = true
|
||||
|
||||
this.checkScrollbar()
|
||||
this.setScrollbar()
|
||||
this.$body.addClass('modal-open')
|
||||
|
||||
this.escape()
|
||||
this.resize()
|
||||
|
||||
this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this))
|
||||
|
||||
this.$dialog.on('mousedown.dismiss.bs.modal', function () {
|
||||
that.$element.one('mouseup.dismiss.bs.modal', function (e) {
|
||||
if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true
|
||||
})
|
||||
})
|
||||
|
||||
this.backdrop(function () {
|
||||
var transition = $.support.transition && that.$element.hasClass('fade')
|
||||
|
||||
if (!that.$element.parent().length) {
|
||||
that.$element.appendTo(that.$body) // don't move modals dom position
|
||||
}
|
||||
|
||||
that.$element
|
||||
.show()
|
||||
.scrollTop(0)
|
||||
|
||||
that.adjustDialog()
|
||||
|
||||
if (transition) {
|
||||
that.$element[0].offsetWidth // force reflow
|
||||
}
|
||||
|
||||
that.$element.addClass('in')
|
||||
|
||||
that.enforceFocus()
|
||||
|
||||
var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget })
|
||||
|
||||
transition ?
|
||||
that.$dialog // wait for modal to slide in
|
||||
.one('bsTransitionEnd', function () {
|
||||
that.$element.trigger('focus').trigger(e)
|
||||
})
|
||||
.emulateTransitionEnd(Modal.TRANSITION_DURATION) :
|
||||
that.$element.trigger('focus').trigger(e)
|
||||
})
|
||||
}
|
||||
|
||||
function hideModal(that) {
|
||||
this.$element
|
||||
.hide()
|
||||
.trigger('hidden')
|
||||
Modal.prototype.hide = function (e) {
|
||||
if (e) e.preventDefault()
|
||||
|
||||
backdrop.call(this)
|
||||
e = $.Event('hide.bs.modal')
|
||||
|
||||
this.$element.trigger(e)
|
||||
|
||||
if (!this.isShown || e.isDefaultPrevented()) return
|
||||
|
||||
this.isShown = false
|
||||
|
||||
this.escape()
|
||||
this.resize()
|
||||
|
||||
$(document).off('focusin.bs.modal')
|
||||
|
||||
this.$element
|
||||
.removeClass('in')
|
||||
.off('click.dismiss.bs.modal')
|
||||
.off('mouseup.dismiss.bs.modal')
|
||||
|
||||
this.$dialog.off('mousedown.dismiss.bs.modal')
|
||||
|
||||
$.support.transition && this.$element.hasClass('fade') ?
|
||||
this.$element
|
||||
.one('bsTransitionEnd', $.proxy(this.hideModal, this))
|
||||
.emulateTransitionEnd(Modal.TRANSITION_DURATION) :
|
||||
this.hideModal()
|
||||
}
|
||||
|
||||
function backdrop(callback) {
|
||||
Modal.prototype.enforceFocus = function () {
|
||||
$(document)
|
||||
.off('focusin.bs.modal') // guard against infinite focus loop
|
||||
.on('focusin.bs.modal', $.proxy(function (e) {
|
||||
if (this.$element[0] !== e.target && !this.$element.has(e.target).length) {
|
||||
this.$element.trigger('focus')
|
||||
}
|
||||
}, this))
|
||||
}
|
||||
|
||||
Modal.prototype.escape = function () {
|
||||
if (this.isShown && this.options.keyboard) {
|
||||
this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) {
|
||||
e.which == 27 && this.hide()
|
||||
}, this))
|
||||
} else if (!this.isShown) {
|
||||
this.$element.off('keydown.dismiss.bs.modal')
|
||||
}
|
||||
}
|
||||
|
||||
Modal.prototype.resize = function () {
|
||||
if (this.isShown) {
|
||||
$(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this))
|
||||
} else {
|
||||
$(window).off('resize.bs.modal')
|
||||
}
|
||||
}
|
||||
|
||||
Modal.prototype.hideModal = function () {
|
||||
var that = this
|
||||
, animate = this.$element.hasClass('fade') ? 'fade' : ''
|
||||
this.$element.hide()
|
||||
this.backdrop(function () {
|
||||
that.$body.removeClass('modal-open')
|
||||
that.resetAdjustments()
|
||||
that.resetScrollbar()
|
||||
that.$element.trigger('hidden.bs.modal')
|
||||
})
|
||||
}
|
||||
|
||||
Modal.prototype.removeBackdrop = function () {
|
||||
this.$backdrop && this.$backdrop.remove()
|
||||
this.$backdrop = null
|
||||
}
|
||||
|
||||
Modal.prototype.backdrop = function (callback) {
|
||||
var that = this
|
||||
var animate = this.$element.hasClass('fade') ? 'fade' : ''
|
||||
|
||||
if (this.isShown && this.options.backdrop) {
|
||||
var doAnimate = $.support.transition && animate
|
||||
|
||||
this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />')
|
||||
.appendTo(document.body)
|
||||
this.$backdrop = $(document.createElement('div'))
|
||||
.addClass('modal-backdrop ' + animate)
|
||||
.appendTo(this.$body)
|
||||
|
||||
if (this.options.backdrop != 'static') {
|
||||
this.$backdrop.click($.proxy(this.hide, this))
|
||||
}
|
||||
this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) {
|
||||
if (this.ignoreBackdropClick) {
|
||||
this.ignoreBackdropClick = false
|
||||
return
|
||||
}
|
||||
if (e.target !== e.currentTarget) return
|
||||
this.options.backdrop == 'static'
|
||||
? this.$element[0].focus()
|
||||
: this.hide()
|
||||
}, this))
|
||||
|
||||
if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
|
||||
|
||||
this.$backdrop.addClass('in')
|
||||
|
||||
if (!callback) return
|
||||
|
||||
doAnimate ?
|
||||
this.$backdrop.one($.support.transition.end, callback) :
|
||||
this.$backdrop
|
||||
.one('bsTransitionEnd', callback)
|
||||
.emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :
|
||||
callback()
|
||||
|
||||
} else if (!this.isShown && this.$backdrop) {
|
||||
this.$backdrop.removeClass('in')
|
||||
|
||||
$.support.transition && this.$element.hasClass('fade')?
|
||||
this.$backdrop.one($.support.transition.end, $.proxy(removeBackdrop, this)) :
|
||||
removeBackdrop.call(this)
|
||||
var callbackRemove = function () {
|
||||
that.removeBackdrop()
|
||||
callback && callback()
|
||||
}
|
||||
$.support.transition && this.$element.hasClass('fade') ?
|
||||
this.$backdrop
|
||||
.one('bsTransitionEnd', callbackRemove)
|
||||
.emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :
|
||||
callbackRemove()
|
||||
|
||||
} else if (callback) {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
function removeBackdrop() {
|
||||
this.$backdrop.remove()
|
||||
this.$backdrop = null
|
||||
// these following methods are used to handle overflowing modals
|
||||
|
||||
Modal.prototype.handleUpdate = function () {
|
||||
this.adjustDialog()
|
||||
}
|
||||
|
||||
function escape() {
|
||||
var that = this
|
||||
if (this.isShown && this.options.keyboard) {
|
||||
$(document).on('keyup.dismiss.modal', function ( e ) {
|
||||
e.which == 27 && that.hide()
|
||||
})
|
||||
} else if (!this.isShown) {
|
||||
$(document).off('keyup.dismiss.modal')
|
||||
}
|
||||
}
|
||||
Modal.prototype.adjustDialog = function () {
|
||||
var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight
|
||||
|
||||
|
||||
/* MODAL PLUGIN DEFINITION
|
||||
* ======================= */
|
||||
|
||||
$.fn.modal = function (option) {
|
||||
return this.each(function () {
|
||||
var $this = $(this)
|
||||
, data = $this.data('modal')
|
||||
, options = $.extend({}, $.fn.modal.defaults, $this.data(), typeof option == 'object' && option)
|
||||
if (!data) $this.data('modal', (data = new Modal(this, options)))
|
||||
if (typeof option == 'string') data[option]()
|
||||
else if (options.show) data.show()
|
||||
this.$element.css({
|
||||
paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '',
|
||||
paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : ''
|
||||
})
|
||||
}
|
||||
|
||||
$.fn.modal.defaults = {
|
||||
backdrop: true
|
||||
, keyboard: true
|
||||
, show: true
|
||||
Modal.prototype.resetAdjustments = function () {
|
||||
this.$element.css({
|
||||
paddingLeft: '',
|
||||
paddingRight: ''
|
||||
})
|
||||
}
|
||||
|
||||
Modal.prototype.checkScrollbar = function () {
|
||||
var fullWindowWidth = window.innerWidth
|
||||
if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8
|
||||
var documentElementRect = document.documentElement.getBoundingClientRect()
|
||||
fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left)
|
||||
}
|
||||
this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth
|
||||
this.scrollbarWidth = this.measureScrollbar()
|
||||
}
|
||||
|
||||
Modal.prototype.setScrollbar = function () {
|
||||
var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10)
|
||||
this.originalBodyPad = document.body.style.paddingRight || ''
|
||||
if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth)
|
||||
}
|
||||
|
||||
Modal.prototype.resetScrollbar = function () {
|
||||
this.$body.css('padding-right', this.originalBodyPad)
|
||||
}
|
||||
|
||||
Modal.prototype.measureScrollbar = function () { // thx walsh
|
||||
var scrollDiv = document.createElement('div')
|
||||
scrollDiv.className = 'modal-scrollbar-measure'
|
||||
this.$body.append(scrollDiv)
|
||||
var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth
|
||||
this.$body[0].removeChild(scrollDiv)
|
||||
return scrollbarWidth
|
||||
}
|
||||
|
||||
|
||||
// MODAL PLUGIN DEFINITION
|
||||
// =======================
|
||||
|
||||
function Plugin(option, _relatedTarget) {
|
||||
return this.each(function () {
|
||||
var $this = $(this)
|
||||
var data = $this.data('bs.modal')
|
||||
var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)
|
||||
|
||||
if (!data) $this.data('bs.modal', (data = new Modal(this, options)))
|
||||
|
||||
if (typeof option == 'string') data[option](_relatedTarget)
|
||||
else if (options.show) data.show(_relatedTarget)
|
||||
})
|
||||
}
|
||||
|
||||
var old = $.fn.modal
|
||||
|
||||
$.fn.modal = Plugin
|
||||
$.fn.modal.Constructor = Modal
|
||||
|
||||
|
||||
/* MODAL DATA-API
|
||||
* ============== */
|
||||
// MODAL NO CONFLICT
|
||||
// =================
|
||||
|
||||
$(function () {
|
||||
$('body').on('click.modal.data-api', '[data-toggle="modal"]', function ( e ) {
|
||||
var $this = $(this), href
|
||||
, $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
|
||||
, option = $target.data('modal') ? 'toggle' : $.extend({}, $target.data(), $this.data())
|
||||
$.fn.modal.noConflict = function () {
|
||||
$.fn.modal = old
|
||||
return this
|
||||
}
|
||||
|
||||
e.preventDefault()
|
||||
$target.modal(option)
|
||||
|
||||
// MODAL DATA-API
|
||||
// ==============
|
||||
|
||||
$(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) {
|
||||
var $this = $(this)
|
||||
var href = $this.attr('href')
|
||||
var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7
|
||||
var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())
|
||||
|
||||
if ($this.is('a')) e.preventDefault()
|
||||
|
||||
$target.one('show.bs.modal', function (showEvent) {
|
||||
if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown
|
||||
$target.one('hidden.bs.modal', function () {
|
||||
$this.is(':visible') && $this.trigger('focus')
|
||||
})
|
||||
})
|
||||
Plugin.call($target, option, this)
|
||||
})
|
||||
|
||||
}(window.jQuery);
|
||||
}(jQuery);
|
||||
|
|
|
@ -148,11 +148,14 @@
|
|||
// Find the bottom view and what's onscreen
|
||||
while (bottomView < childViews.length) {
|
||||
var view = childViews[bottomView],
|
||||
$view = view.$(),
|
||||
// in case of not full-window scrolling
|
||||
scrollOffset = this.get('wrapperTop') || 0,
|
||||
viewTop = $view.offset().top + scrollOffset,
|
||||
viewBottom = viewTop + $view.height();
|
||||
$view = view.$();
|
||||
|
||||
if (!$view) { break; }
|
||||
|
||||
// in case of not full-window scrolling
|
||||
var scrollOffset = this.get('wrapperTop') || 0,
|
||||
viewTop = $view.offset().top + scrollOffset,
|
||||
viewBottom = viewTop + $view.height();
|
||||
|
||||
if (viewTop > viewportBottom) { break; }
|
||||
toUncloak.push(view);
|
||||
|
|
Loading…
Reference in New Issue