import DiscourseURL from 'discourse/lib/url';
import Quote from 'discourse/lib/quote';
import Draft from 'discourse/models/draft';
import Composer from 'discourse/models/composer';
import { default as computed, observes } from 'ember-addons/ember-computed-decorators';
import { relativeAge } from 'discourse/lib/formatter';
import { escapeExpression } from 'discourse/lib/utilities';
import InputValidation from 'discourse/models/input-validation';
function loadDraft(store, opts) {
opts = opts || {};
let draft = opts.draft;
const draftKey = opts.draftKey;
const draftSequence = opts.draftSequence;
try {
if (draft && typeof draft === 'string') {
draft = JSON.parse(draft);
}
} catch (error) {
draft = null;
Draft.clear(draftKey, draftSequence);
}
if (draft && ((draft.title && draft.title !== '') || (draft.reply && draft.reply !== ''))) {
const composer = store.createRecord('composer');
composer.open({
draftKey,
draftSequence,
action: draft.action,
title: draft.title,
categoryId: draft.categoryId || opts.categoryId,
postId: draft.postId,
archetypeId: draft.archetypeId,
reply: draft.reply,
metaData: draft.metaData,
usernames: draft.usernames,
draft: true,
composerState: Composer.DRAFT,
composerTime: draft.composerTime,
typingTime: draft.typingTime
});
return composer;
}
}
const _popupMenuOptionsCallbacks = [];
export function addPopupMenuOptionsCallback(callback) {
_popupMenuOptionsCallbacks.push(callback);
}
export default Ember.Controller.extend({
topicController: Ember.inject.controller('topic'),
application: Ember.inject.controller(),
replyAsNewTopicDraft: Em.computed.equal('model.draftKey', Composer.REPLY_AS_NEW_TOPIC_KEY),
checkedMessages: false,
messageCount: null,
showEditReason: false,
editReason: null,
scopedCategoryId: null,
optionsVisible: false,
lastValidatedAt: null,
isUploading: false,
topic: null,
linkLookup: null,
whisperOrUnlistTopic: Ember.computed.or('model.whisper', 'model.unlistTopic'),
@computed('model.replyingToTopic', 'model.creatingPrivateMessage', 'model.targetUsernames')
focusTarget(replyingToTopic, creatingPM, usernames) {
if (this.capabilities.isIOS) { return "none"; }
// Focus on usernames if it's blank or if it's just you
usernames = usernames || "";
if (creatingPM && usernames.length === 0 || usernames === this.currentUser.get('username')) {
return 'usernames';
}
if (replyingToTopic) {
return 'reply';
}
return 'title';
},
showToolbar: Em.computed({
get(){
const keyValueStore = this.container.lookup('key-value-store:main');
const storedVal = keyValueStore.get("toolbar-enabled");
if (this._toolbarEnabled === undefined && storedVal === undefined) {
// iPhone 6 is 375, anything narrower and toolbar should
// be default disabled.
// That said we should remember the state
this._toolbarEnabled = $(window).width() > 370 && !this.capabilities.isAndroid;
}
return this._toolbarEnabled || storedVal === "true";
},
set(key, val){
const keyValueStore = this.container.lookup('key-value-store:main');
this._toolbarEnabled = val;
keyValueStore.set({key: "toolbar-enabled", value: val ? "true" : "false"});
return val;
}
}),
topicModel: Ember.computed.alias('topicController.model'),
@computed('model.canEditTitle', 'model.creatingPrivateMessage')
canEditTags(canEditTitle, creatingPrivateMessage) {
return !this.site.mobileView &&
this.site.get('can_tag_topics') &&
canEditTitle &&
!creatingPrivateMessage;
},
@computed('model.whisper', 'model.unlistTopic')
whisperOrUnlistTopicText(whisper, unlistTopic) {
if (whisper) {
return I18n.t("composer.whisper");
} else if (unlistTopic) {
return I18n.t("composer.unlist");
}
},
@computed
isStaffUser() {
const currentUser = this.currentUser;
return currentUser && currentUser.get('staff');
},
canUnlistTopic: Em.computed.and('model.creatingTopic', 'isStaffUser'),
@computed('model.action', 'isStaffUser')
canWhisper(action, isStaffUser) {
return isStaffUser && this.siteSettings.enable_whispers && action === Composer.REPLY;
},
@computed("popupMenuOptions")
showPopupMenu(popupMenuOptions) {
return popupMenuOptions ? popupMenuOptions.some(option => option.condition) : false;
},
_setupPopupMenuOption(callback) {
let option = callback();
if (option.condition) {
option.condition = this.get(option.condition);
} else {
option.condition = true;
}
return option;
},
@computed("model.composeState", "model.creatingTopic")
popupMenuOptions(composeState) {
if (composeState === 'open') {
let options = [];
options.push(this._setupPopupMenuOption(() => {
return {
action: 'toggleInvisible',
icon: 'eye-slash',
label: 'composer.toggle_unlisted',
condition: "canUnlistTopic"
};
}));
options.push(this._setupPopupMenuOption(() => {
return {
action: 'toggleWhisper',
icon: 'eye-slash',
label: 'composer.toggle_whisper',
condition: "canWhisper"
};
}));
return options.concat(_popupMenuOptionsCallbacks.map(callback => {
return this._setupPopupMenuOption(callback);
}));
}
},
showWarning: function() {
if (!Discourse.User.currentProp('staff')) { return false; }
var usernames = this.get('model.targetUsernames');
var hasTargetGroups = this.get('model.hasTargetGroups');
// We need exactly one user to issue a warning
if (Ember.isEmpty(usernames) || usernames.split(',').length !== 1 || hasTargetGroups) {
return false;
}
return this.get('model.creatingPrivateMessage');
}.property('model.creatingPrivateMessage', 'model.targetUsernames'),
actions: {
addLinkLookup(linkLookup) {
this.set('linkLookup', linkLookup);
},
afterRefresh($preview) {
const topic = this.get('model.topic');
const linkLookup = this.get('linkLookup');
if (!topic || !linkLookup) { return; }
// Don't check if there's only one post
if (topic.get('posts_count') === 1) { return; }
const post = this.get('model.post');
if (post && post.get('user_id') !== this.currentUser.id) { return; }
const $links = $('a[href]', $preview);
$links.each((idx, l) => {
const href = $(l).prop('href');
if (href && href.length) {
const [warn, info] = linkLookup.check(post, href);
if (warn) {
const body = I18n.t('composer.duplicate_link', {
domain: info.domain,
username: info.username,
post_url: topic.urlForPostNumber(info.post_number),
ago: relativeAge(moment(info.posted_at).toDate(), { format: 'medium' })
});
this.appEvents.trigger('composer-messages:create', {
extraClass: 'custom-body',
templateName: 'custom-body',
body
});
return false;
}
}
return true;
});
},
toggleWhisper() {
this.toggleProperty('model.whisper');
},
toggleInvisible() {
this.toggleProperty('model.unlistTopic');
},
toggleToolbar() {
this.toggleProperty('showToolbar');
},
showOptions(toolbarEvent, loc) {
this.set('toolbarEvent', toolbarEvent);
this.appEvents.trigger('popup-menu:open', loc);
this.set('optionsVisible', true);
},
hideOptions() {
this.set('optionsVisible', false);
},
// Toggle the reply view
toggle() {
this.toggle();
},
togglePreview() {
this.get('model').togglePreview();
},
// Import a quote from the post
importQuote(toolbarEvent) {
const postStream = this.get('topic.postStream');
let postId = this.get('model.post.id');
// If there is no current post, use the first post id from the stream
if (!postId && postStream) {
postId = postStream.get('stream.firstObject');
}
// If we're editing a post, fetch the reply when importing a quote
if (this.get('model.editingPost')) {
const replyToPostNumber = this.get('model.post.reply_to_post_number');
if (replyToPostNumber) {
const replyPost = postStream.get('posts').findBy('post_number', replyToPostNumber);
if (replyPost) {
postId = replyPost.get('id');
}
}
}
if (postId) {
this.set('model.loading', true);
const composer = this;
return this.store.find('post', postId).then(function(post) {
const quote = Quote.build(post, post.get("raw"), {raw: true, full: true});
toolbarEvent.addText(quote);
composer.set('model.loading', false);
});
}
},
cancel() {
this.cancelComposer();
},
save() {
this.save();
},
displayEditReason() {
this.set("showEditReason", true);
},
hitEsc() {
if ((this.get('messageCount') || 0) > 0) {
this.appEvents.trigger('composer-messages:close');
return;
}
if (this.get('model.viewOpen')) {
this.shrink();
}
},
openIfDraft() {
if (this.get('model.viewDraft')) {
this.set('model.composeState', Composer.OPEN);
}
},
groupsMentioned(groups) {
if (!this.get('model.creatingPrivateMessage') && !this.get('model.topic.isPrivateMessage')) {
groups.forEach(group => {
const body = I18n.t('composer.group_mentioned', {
group: "@" + group.name,
count: group.user_count,
group_link: Discourse.getURL(`/group/${group.name}/members`)
});
this.appEvents.trigger('composer-messages:create', {
extraClass: 'custom-body',
templateName: 'custom-body',
body
});
});
}
}
},
categories: function() {
return Discourse.Category.list();
}.property(),
toggle() {
this.closeAutocomplete();
switch (this.get('model.composeState')) {
case Composer.OPEN:
if (Ember.isEmpty(this.get('model.reply')) && Ember.isEmpty(this.get('model.title'))) {
this.close();
} else {
this.shrink();
}
break;
case Composer.DRAFT:
this.set('model.composeState', Composer.OPEN);
break;
case Composer.SAVING:
this.close();
}
return false;
},
disableSubmit: Ember.computed.or("model.loading", "isUploading"),
save(force) {
const composer = this.get('model');
const self = this;
// Clear the warning state if we're not showing the checkbox anymore
if (!this.get('showWarning')) {
this.set('model.isWarning', false);
}
if (composer.get('cantSubmitPost')) {
this.set('lastValidatedAt', Date.now());
return;
}
composer.set('disableDrafts', true);
// for now handle a very narrow use case
// if we are replying to a topic AND not on the topic pop the window up
if (!force && composer.get('replyingToTopic')) {
const currentTopic = this.get('topicModel');
if (!currentTopic || currentTopic.get('id') !== composer.get('topic.id'))
{
const message = I18n.t("composer.posting_not_on_topic");
let buttons = [{
"label": I18n.t("composer.cancel"),
"class": "cancel",
"link": true
}];
if (currentTopic) {
buttons.push({
"label": I18n.t("composer.reply_here") + "