Skip to content

Commit

Permalink
FEATURE: Make it possible to reassign posts on the topic level assign…
Browse files Browse the repository at this point in the history
… modal

This adds a new dropdown to the topic level assign modal. A topic may contain several assignments, the topic itself may be assigned and also some of the replies may be assigned. With this new dropdown, it's possible now to edit all the assignments in one go.
  • Loading branch information
jjaffeux committed Apr 16, 2024
1 parent 6070678 commit 0f22e6e
Show file tree
Hide file tree
Showing 18 changed files with 637 additions and 192 deletions.
36 changes: 36 additions & 0 deletions assets/javascripts/discourse/components/assign-user-form.gjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";
import Assignment from "./assignment";

export default class AssignUserForm extends Component {
@tracked showValidationErrors = false;

constructor() {
super(...arguments);

this.args.formApi.submit = this.assign;
}

get assigneeIsEmpty() {
return !this.args.model.username && !this.args.model.group_name;
}

@action
async assign() {
if (this.assigneeIsEmpty) {
this.showValidationErrors = true;
return;
}

await this.args.onSubmit();
}

<template>
<Assignment
@assignment={{@model}}
@onSubmit={{this.assign}}
@showValidationErrors={{this.showValidationErrors}}
/>
</template>
}
56 changes: 0 additions & 56 deletions assets/javascripts/discourse/components/assign-user-form.hbs

This file was deleted.

63 changes: 0 additions & 63 deletions assets/javascripts/discourse/components/assign-user-form.js

This file was deleted.

134 changes: 134 additions & 0 deletions assets/javascripts/discourse/components/assignment.gjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import Component from "@glimmer/component";
import { hash } from "@ember/helper";
import { TextArea } from "@ember/legacy-built-in-components";
import { on } from "@ember/modifier";
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import icon from "discourse-common/helpers/d-icon";
import i18n from "discourse-common/helpers/i18n";
import ComboBox from "select-kit/components/combo-box";
import not from "truth-helpers/helpers/not";
import AssigneeChooser from "./assignee-chooser";

export default class Assignment extends Component {
@service siteSettings;
@service taskActions;

get assignee() {
return this.args.assignment.username || this.args.assignment.group_name;
}

get status() {
return this.args.assignment.status || this.assignStatuses[0];
}

get assignStatuses() {
return this.siteSettings.assign_statuses.split("|").filter(Boolean);
}

get assignStatusOptions() {
return this.assignStatuses.map((status) => ({ id: status, name: status }));
}

get assigneeIsEmpty() {
return !this.args.assignment.username && !this.args.assignment.group_name;
}

get showAssigneeIeEmptyError() {
return this.assigneeIsEmpty && this.args.showValidationErrors;
}

@action
handleTextAreaKeydown(event) {
if ((event.ctrlKey || event.metaKey) && event.key === "Enter") {
this.args.onSubmit();
}
}

@action
markAsEdited() {
this.args.assignment.isEdited = true;
}

@action
setAssignee([newAssignee]) {
if (this.taskActions.allowedGroupsForAssignment.includes(newAssignee)) {
this.args.assignment.username = null;
this.args.assignment.group_name = newAssignee;
} else {
this.args.assignment.username = newAssignee;
this.args.assignment.group_name = null;
}
this.markAsEdited();
}

@action
setStatus(status) {
this.args.assignment.status = status;
this.markAsEdited();
}

<template>
<div
class="control-group
{{if this.showAssigneeIeEmptyError 'assignee-error'}}"
>
<label>{{i18n "discourse_assign.assign_modal.assignee_label"}}</label>
<AssigneeChooser
autocomplete="off"
@id="assignee-chooser"
@value={{this.assignee}}
@onChange={{this.setAssignee}}
@showUserStatus={{true}}
@options={{hash
mobilePlacementStrategy="absolute"
includeGroups=true
customSearchOptions=(hash
assignableGroups=true
defaultSearchResults=this.taskActions.suggestions
)
groupMembersOf=this.taskActions.allowedGroups
maximum=1
tabindex=1
expandedOnInsert=(not this.assignee)
caretUpIcon="search"
caretDownIcon="search"
}}
/>

{{#if this.showAssigneeIeEmptyError}}
<span class="error-label">
{{icon "exclamation-triangle"}}
{{i18n "discourse_assign.assign_modal.choose_assignee"}}
</span>
{{/if}}
</div>

{{#if this.siteSettings.enable_assign_status}}
<div class="control-group assign-status">
<label>{{i18n "discourse_assign.assign_modal.status_label"}}</label>
<ComboBox
@id="assign-status"
@content={{this.assignStatusOptions}}
@value={{this.status}}
@onChange={{this.setStatus}}
/>
</div>
{{/if}}

<div class="control-group assign-status">
<label>
{{i18n "discourse_assign.assign_modal.note_label"}}&nbsp;<span
class="label-optional"
>{{i18n "discourse_assign.assign_modal.optional_label"}}</span>
</label>

<TextArea
id="assign-modal-note"
@value={{@assignment.note}}
{{on "keydown" this.handleTextAreaKeydown}}
{{on "input" this.markAsEdited}}
/>
</div>
</template>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import DButton from "discourse/components/d-button";
import DModal from "discourse/components/d-modal";
import DModalCancel from "discourse/components/d-modal-cancel";
import { popupAjaxError } from "discourse/lib/ajax-error";
import I18n from "I18n";
import TopicAssignments from "../topic-assignments";

export default class EditTopicAssignments extends Component {
@service taskActions;

@tracked assignments = this.topic.assignments();

get title() {
if (this.topic.isAssigned() || this.topic.hasAssignedPosts()) {
return I18n.t("edit_assignments_modal.title");
} else {
return I18n.t("discourse_assign.assign_modal.title");
}
}

get topic() {
return this.args.model.topic;
}

@action
async submit() {
this.args.closeModal();
try {
await this.#assign();
} catch (error) {
popupAjaxError(error);
}
}

async #assign() {
for (const assignment of this.assignments) {
if (assignment.isEdited) {
await this.taskActions.putAssignment(assignment);
}
}
}

<template>
<DModal class="assign" @title={{this.title}} @closeModal={{@closeModal}}>
<:body>
<TopicAssignments @assignments={{this.assignments}} />
</:body>
<:footer>
<DButton
class="btn-primary"
@action={{this.submit}}
@label={{if
this.model.reassign
"discourse_assign.reassign.title"
"discourse_assign.assign_modal.assign"
}}
/>

<DModalCancel @close={{@closeModal}} />
</:footer>
</DModal>
</template>
}
Loading

0 comments on commit 0f22e6e

Please sign in to comment.