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,
on
} from "ember-addons/ember-computed-decorators";
import InputValidation from "discourse/models/input-validation";
import { getOwner } from "discourse-common/lib/get-owner";
import {
escapeExpression,
uploadIcon,
authorizesOneOrMoreExtensions
} from "discourse/lib/utilities";
import { emojiUnescape } from "discourse/lib/text";
import { shortDate } from "discourse/lib/formatter";
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,
whisper: draft.whisper,
tags: draft.tags
});
return composer;
}
}
const _popupMenuOptionsCallbacks = [];
export function clearPopupMenuOptionsCallback() {
_popupMenuOptionsCallbacks.length = 0;
}
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
),
replyAsNewPrivateMessageDraft: Em.computed.equal(
"model.draftKey",
Composer.REPLY_AS_NEW_PRIVATE_MESSAGE_KEY
),
checkedMessages: false,
messageCount: null,
showEditReason: false,
editReason: null,
scopedCategoryId: null,
lastValidatedAt: null,
isUploading: false,
allowUpload: false,
uploadIcon: "upload",
topic: null,
linkLookup: null,
showPreview: true,
forcePreview: Ember.computed.and("site.mobileView", "showPreview"),
whisperOrUnlistTopic: Ember.computed.or("model.whisper", "model.unlistTopic"),
categories: Ember.computed.alias("site.categoriesList"),
@on("init")
_setupPreview() {
const val = this.site.mobileView
? false
: this.keyValueStore.get("composer.showPreview") || "true";
this.set("showPreview", val === "true");
},
@computed("showPreview")
toggleText: function(showPreview) {
return showPreview
? I18n.t("composer.hide_preview")
: I18n.t("composer.show_preview");
},
@observes("showPreview")
showPreviewChanged() {
if (!this.site.mobileView) {
this.keyValueStore.set({
key: "composer.showPreview",
value: this.get("showPreview")
});
}
},
@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 = getOwner(this).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 = getOwner(this).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.get("can_tag_topics") &&
canEditTitle &&
!creatingPrivateMessage &&
(!this.get("model.topic.isPrivateMessage") ||
this.site.get("can_tag_pms"))
);
},
@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
);
},
_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"),
@computed("model.topic")
draftTitle(topic) {
return emojiUnescape(escapeExpression(topic.get("title")));
},
@computed
allowUpload() {
return authorizesOneOrMoreExtensions();
},
@computed() uploadIcon: () => uploadIcon(),
actions: {
cancelUpload() {
this.set("model.uploadCancelled", true);
},
onPopupMenuAction(action) {
this.send(action);
},
storeToolbarState(toolbarEvent) {
this.set("toolbarEvent", toolbarEvent);
},
togglePreview() {
this.toggleProperty("showPreview");
},
typed() {
this.checkReplyLength();
this.get("model").typing();
},
cancelled() {
this.send("hitEsc");
},
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");
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: shortDate(info.posted_at)
});
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");
},
// Toggle the reply view
toggle() {
this.closeAutocomplete();
if (
Ember.isEmpty(this.get("model.reply")) &&
Ember.isEmpty(this.get("model.title"))
) {
this.close();
} else {
if (this.get("model.composeState") === Composer.OPEN) {
this.shrink();
} else {
this.cancelComposer();
}
}
return false;
},
// 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 (Ember.$(".emoji-picker-modal.fadeIn").length === 1) {
this.appEvents.trigger("emoji-picker:close");
return;
}
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 => {
let body;
if (group.max_mentions < group.user_count) {
body = I18n.t("composer.group_mentioned_limit", {
group: "@" + group.name,
max: group.max_mentions,
group_link: Discourse.getURL(`/groups/${group.name}/members`)
});
} else {
body = I18n.t("composer.group_mentioned", {
group: "@" + group.name,
count: group.user_count,
group_link: Discourse.getURL(`/groups/${group.name}/members`)
});
}
this.appEvents.trigger("composer-messages:create", {
extraClass: "custom-body",
templateName: "custom-body",
body
});
});
}
},
cannotSeeMention(mentions) {
mentions.forEach(mention => {
const translation = this.get("model.topic.isPrivateMessage")
? "composer.cannot_see_mention.private"
: "composer.cannot_see_mention.category";
const body = I18n.t(translation, {
username: "@" + mention.name
});
this.appEvents.trigger("composer-messages:create", {
extraClass: "custom-body",
templateName: "custom-body",
body
});
});
}
},
disableSubmit: Ember.computed.or("model.loading", "isUploading"),
save(force) {
if (this.get("disableSubmit")) return;
// Clear the warning state if we're not showing the checkbox anymore
if (!this.get("showWarning")) {
this.set("model.isWarning", false);
}
const composer = this.get("model");
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) {
this.save(true);
return;
}
if (currentTopic.get("id") !== composer.get("topic.id")) {
const message = I18n.t("composer.posting_not_on_topic");
let buttons = [
{
label: I18n.t("composer.cancel"),
class: "d-modal-cancel",
link: true
}
];
buttons.push({
label:
I18n.t("composer.reply_here") +
"