diff --git a/assets/javascripts/discourse/components/solved-accept-answer-button.gjs b/assets/javascripts/discourse/components/solved-accept-answer-button.gjs
new file mode 100644
index 00000000..5424a283
--- /dev/null
+++ b/assets/javascripts/discourse/components/solved-accept-answer-button.gjs
@@ -0,0 +1,85 @@
+import Component from "@glimmer/component";
+import { action } from "@ember/object";
+import { inject as service } from "@ember/service";
+import DButton from "discourse/components/d-button";
+import { ajax } from "discourse/lib/ajax";
+import { popupAjaxError } from "discourse/lib/ajax-error";
+
+export default class SolvedAcceptAnswerButton extends Component {
+ static hidden(args) {
+ return args.post.topic_accepted_answer;
+ }
+
+ @service appEvents;
+ @service currentUser;
+
+ get showLabel() {
+ return this.currentUser?.id === this.args.post.topicCreatedById;
+ }
+
+ @action
+ acceptAnswer() {
+ acceptAnswer(this.args.post, this.appEvents);
+ }
+
+
+
+
+}
+
+export function acceptAnswer(post, appEvents) {
+ // TODO (glimmer-post-menu): Remove this exported function and move the code into the button action after the widget code is removed
+ acceptPost(post);
+
+ appEvents.trigger("discourse-solved:solution-toggled", post);
+
+ post.get("topic.postStream.posts").forEach((p) => {
+ p.set("topic_accepted_answer", true);
+ appEvents.trigger("post-stream:refresh", { id: p.id });
+ });
+}
+
+function acceptPost(post) {
+ const topic = post.topic;
+
+ clearAccepted(topic);
+
+ post.setProperties({
+ can_unaccept_answer: true,
+ can_accept_answer: false,
+ accepted_answer: true,
+ });
+
+ topic.set("accepted_answer", {
+ username: post.username,
+ name: post.name,
+ post_number: post.post_number,
+ excerpt: post.cooked,
+ });
+
+ ajax("/solution/accept", {
+ type: "POST",
+ data: { id: post.id },
+ }).catch(popupAjaxError);
+}
+
+function clearAccepted(topic) {
+ const posts = topic.get("postStream.posts");
+ posts.forEach((post) => {
+ if (post.get("post_number") > 1) {
+ post.setProperties({
+ accepted_answer: false,
+ can_accept_answer: true,
+ can_unaccept_answer: false,
+ topic_accepted_answer: false,
+ });
+ }
+ });
+}
diff --git a/assets/javascripts/discourse/components/solved-unaccept-answer-button.gjs b/assets/javascripts/discourse/components/solved-unaccept-answer-button.gjs
new file mode 100644
index 00000000..35c8eae9
--- /dev/null
+++ b/assets/javascripts/discourse/components/solved-unaccept-answer-button.gjs
@@ -0,0 +1,74 @@
+import Component from "@glimmer/component";
+import { action } from "@ember/object";
+import { inject as service } from "@ember/service";
+import DButton from "discourse/components/d-button";
+import { ajax } from "discourse/lib/ajax";
+import { popupAjaxError } from "discourse/lib/ajax-error";
+import dIcon from "discourse-common/helpers/d-icon";
+import i18n from "discourse-common/helpers/i18n";
+
+export default class SolvedUnacceptAnswerButton extends Component {
+ @service appEvents;
+
+ @action
+ unacceptAnswer() {
+ unacceptAnswer(this.args.post, this.appEvents);
+ }
+
+
+
+
+}
+
+export function unacceptAnswer(post, appEvents) {
+ // TODO (glimmer-post-menu): Remove this exported function and move the code into the button action after the widget code is removed
+ unacceptPost(post);
+
+ appEvents.trigger("discourse-solved:solution-toggled", post);
+
+ post.get("topic.postStream.posts").forEach((p) => {
+ p.set("topic_accepted_answer", false);
+ appEvents.trigger("post-stream:refresh", { id: p.id });
+ });
+}
+
+function unacceptPost(post) {
+ if (!post.can_unaccept_answer) {
+ return;
+ }
+ const topic = post.topic;
+
+ post.setProperties({
+ can_accept_answer: true,
+ can_unaccept_answer: false,
+ accepted_answer: false,
+ });
+
+ topic.set("accepted_answer", undefined);
+
+ ajax("/solution/unaccept", {
+ type: "POST",
+ data: { id: post.id },
+ }).catch(popupAjaxError);
+}
diff --git a/assets/javascripts/discourse/initializers/extend-for-solved-button.js b/assets/javascripts/discourse/initializers/extend-for-solved-button.js
index ad6afa1f..0a711d34 100644
--- a/assets/javascripts/discourse/initializers/extend-for-solved-button.js
+++ b/assets/javascripts/discourse/initializers/extend-for-solved-button.js
@@ -1,75 +1,23 @@
import { computed } from "@ember/object";
import TopicStatusIcons from "discourse/helpers/topic-status-icons";
-import { ajax } from "discourse/lib/ajax";
-import { popupAjaxError } from "discourse/lib/ajax-error";
import { withPluginApi } from "discourse/lib/plugin-api";
import { formatUsername } from "discourse/lib/utilities";
import Topic from "discourse/models/topic";
import User from "discourse/models/user";
import TopicStatus from "discourse/raw-views/topic-status";
import PostCooked from "discourse/widgets/post-cooked";
+import { withSilencedDeprecations } from "discourse-common/lib/deprecated";
import { iconHTML, iconNode } from "discourse-common/lib/icon-library";
import I18n from "I18n";
-
-function clearAccepted(topic) {
- const posts = topic.get("postStream.posts");
- posts.forEach((post) => {
- if (post.get("post_number") > 1) {
- post.setProperties({
- accepted_answer: false,
- can_accept_answer: true,
- can_unaccept_answer: false,
- topic_accepted_answer: false,
- });
- }
- });
-}
-
-function unacceptPost(post) {
- if (!post.can_unaccept_answer) {
- return;
- }
- const topic = post.topic;
-
- post.setProperties({
- can_accept_answer: true,
- can_unaccept_answer: false,
- accepted_answer: false,
- });
- topic.set("accepted_answer", undefined);
-
- ajax("/solution/unaccept", {
- type: "POST",
- data: { id: post.id },
- }).catch(popupAjaxError);
-}
-
-function acceptPost(post) {
- const topic = post.topic;
-
- clearAccepted(topic);
-
- post.setProperties({
- can_unaccept_answer: true,
- can_accept_answer: false,
- accepted_answer: true,
- });
-
- topic.set("accepted_answer", {
- username: post.username,
- name: post.name,
- post_number: post.post_number,
- excerpt: post.cooked,
- });
-
- ajax("/solution/accept", {
- type: "POST",
- data: { id: post.id },
- }).catch(popupAjaxError);
-}
+import SolvedAcceptAnswerButton, {
+ acceptAnswer,
+} from "../components/solved-accept-answer-button";
+import SolvedUnacceptAnswerButton, {
+ unacceptAnswer,
+} from "../components/solved-unaccept-answer-button";
function initializeWithApi(api) {
- const currentUser = api.getCurrentUser();
+ customizePostMenu(api);
TopicStatusIcons.addObject([
"has_accepted_answer",
@@ -88,50 +36,6 @@ function initializeWithApi(api) {
api.addDiscoveryQueryParam("solved", { replace: true, refreshModel: true });
}
- api.addPostMenuButton("solved", (attrs) => {
- if (attrs.can_accept_answer) {
- const isOp = currentUser?.id === attrs.topicCreatedById;
-
- return {
- action: "acceptAnswer",
- icon: "far-check-square",
- className: "unaccepted",
- title: "solved.accept_answer",
- label: isOp ? "solved.solution" : null,
- position: attrs.topic_accepted_answer ? "second-last-hidden" : "first",
- };
- } else if (attrs.accepted_answer) {
- if (attrs.can_unaccept_answer) {
- return {
- action: "unacceptAnswer",
- icon: "check-square",
- title: "solved.unaccept_answer",
- className: "accepted fade-out",
- position: "first",
- label: "solved.solution",
- };
- } else {
- return {
- className: "hidden",
- disabled: "true",
- position: "first",
- beforeButton(h) {
- return h(
- "span.accepted-text",
- {
- title: I18n.t("solved.accepted_description"),
- },
- [
- h("span", iconNode("check")),
- h("span.accepted-label", I18n.t("solved.solution")),
- ]
- );
- },
- };
- }
- }
- });
-
api.decorateWidget("post-contents:after-cooked", (dec) => {
if (dec.attrs.post_number === 1) {
const postModel = dec.getModel();
@@ -170,27 +74,104 @@ function initializeWithApi(api) {
});
api.attachWidgetAction("post", "acceptAnswer", function () {
- const post = this.model;
- acceptPost(post);
-
- this.appEvents.trigger("discourse-solved:solution-toggled", post);
-
- post.get("topic.postStream.posts").forEach((p) => {
- p.set("topic_accepted_answer", true);
- this.appEvents.trigger("post-stream:refresh", { id: p.id });
- });
+ acceptAnswer(this.model, this.appEvents);
});
api.attachWidgetAction("post", "unacceptAnswer", function () {
- const post = this.model;
- unacceptPost(post);
+ unacceptAnswer(this.model, this.appEvents);
+ });
+}
- this.appEvents.trigger("discourse-solved:solution-toggled", post);
+function customizePostMenu(api) {
+ const transformerRegistered = api.registerValueTransformer(
+ "post-menu-buttons",
+ ({
+ value: dag,
+ context: {
+ post,
+ firstButtonKey,
+ secondLastHiddenButtonKey,
+ lastHiddenButtonKey,
+ },
+ }) => {
+ let solvedButton;
+
+ if (post.can_accept_answer) {
+ solvedButton = SolvedAcceptAnswerButton;
+ } else if (post.accepted_answer) {
+ solvedButton = SolvedUnacceptAnswerButton;
+ }
- post.get("topic.postStream.posts").forEach((p) => {
- p.set("topic_accepted_answer", false);
- this.appEvents.trigger("post-stream:refresh", { id: p.id });
- });
+ solvedButton &&
+ dag.add(
+ "solved",
+ solvedButton,
+ post.topic_accepted_answer && !post.accepted_answer
+ ? {
+ before: lastHiddenButtonKey,
+ after: secondLastHiddenButtonKey,
+ }
+ : {
+ before: [
+ "assign", // button added by the assign plugin
+ firstButtonKey,
+ ],
+ }
+ );
+ }
+ );
+
+ const silencedKey =
+ transformerRegistered && "discourse.post-menu-widget-overrides";
+
+ withSilencedDeprecations(silencedKey, () => customizeWidgetPostMenu(api));
+}
+
+function customizeWidgetPostMenu(api) {
+ const currentUser = api.getCurrentUser();
+
+ api.addPostMenuButton("solved", (attrs) => {
+ if (attrs.can_accept_answer) {
+ const isOp = currentUser?.id === attrs.topicCreatedById;
+
+ return {
+ action: "acceptAnswer",
+ icon: "far-check-square",
+ className: "unaccepted",
+ title: "solved.accept_answer",
+ label: isOp ? "solved.solution" : null,
+ position: attrs.topic_accepted_answer ? "second-last-hidden" : "first",
+ };
+ } else if (attrs.accepted_answer) {
+ if (attrs.can_unaccept_answer) {
+ return {
+ action: "unacceptAnswer",
+ icon: "check-square",
+ title: "solved.unaccept_answer",
+ className: "accepted fade-out",
+ position: "first",
+ label: "solved.solution",
+ };
+ } else {
+ return {
+ className: "hidden",
+ disabled: "true",
+ position: "first",
+ beforeButton(h) {
+ return h(
+ "span.accepted-text",
+ {
+ title: I18n.t("solved.accepted_description"),
+ },
+ [
+ h("span", iconNode("check")),
+ h("span.accepted-label", I18n.t("solved.solution")),
+ ]
+ );
+ },
+ };
+ }
+ }
});
}
@@ -252,7 +233,7 @@ export default {
}),
});
- withPluginApi("0.1", initializeWithApi);
+ withPluginApi("1.34.0", initializeWithApi);
withPluginApi("0.8.10", (api) => {
api.replaceIcon(
diff --git a/assets/stylesheets/solutions.scss b/assets/stylesheets/solutions.scss
index 11d183f8..b4d0ef02 100644
--- a/assets/stylesheets/solutions.scss
+++ b/assets/stylesheets/solutions.scss
@@ -14,15 +14,6 @@ $solved-color: green;
color: $solved-color;
}
-.post-controls {
- .accepted,
- .unaccepted {
- .d-button-label {
- margin-left: 7px;
- }
- }
-}
-
.post-controls .extra-buttons {
// anon text
.accepted-text {
diff --git a/test/javascripts/acceptance/discourse-solved-post-menu-test.js b/test/javascripts/acceptance/discourse-solved-post-menu-test.js
new file mode 100644
index 00000000..60df5ba9
--- /dev/null
+++ b/test/javascripts/acceptance/discourse-solved-post-menu-test.js
@@ -0,0 +1,52 @@
+import { click, visit } from "@ember/test-helpers";
+import { test } from "qunit";
+import { acceptance } from "discourse/tests/helpers/qunit-helpers";
+import I18n from "I18n";
+import { postStreamWithAcceptedAnswerExcerpt } from "../helpers/discourse-solved-helpers";
+
+acceptance(
+ "Discourse Solved | Post Menu | Accept and Unaccept",
+ function (needs) {
+ needs.user({
+ admin: true,
+ });
+
+ needs.settings({
+ glimmer_post_menu_mode: "enabled",
+ solved_enabled: true,
+ allow_solved_on_all_topics: true,
+ });
+
+ needs.pretender((server, helper) => {
+ server.post("/solution/accept", () => helper.response({ success: "OK" }));
+ server.post("/solution/unaccept", () =>
+ helper.response({ success: "OK" })
+ );
+
+ server.get("/t/12.json", () => {
+ return helper.response(postStreamWithAcceptedAnswerExcerpt(null));
+ });
+ });
+
+ test("accepting and unaccepting a post works", async function (assert) {
+ await visit("/t/without-excerpt/12");
+
+ assert
+ .dom("#post_2 .post-action-menu__solved-accepted")
+ .exists("Unaccept button is visible")
+ .hasText(I18n.t("solved.solution"), "Unaccept button has correct text");
+
+ await click("#post_2 .post-action-menu__solved-accepted");
+
+ assert
+ .dom("#post_2 .post-action-menu__solved-unaccepted")
+ .exists("Accept button is visible");
+
+ await click("#post_2 .post-action-menu__solved-unaccepted");
+
+ assert
+ .dom("#post_2 .post-action-menu__solved-accepted")
+ .exists("Unccept button is visible again");
+ });
+ }
+);
diff --git a/test/javascripts/acceptance/discourse-solved-test.js b/test/javascripts/acceptance/discourse-solved-test.js
index 0a9e5eac..86095806 100644
--- a/test/javascripts/acceptance/discourse-solved-test.js
+++ b/test/javascripts/acceptance/discourse-solved-test.js
@@ -3,221 +3,12 @@ import { test } from "qunit";
import { fixturesByUrl } from "discourse/tests/helpers/create-pretender";
import { acceptance, queryAll } from "discourse/tests/helpers/qunit-helpers";
import { cloneJSON } from "discourse-common/lib/object";
+import { postStreamWithAcceptedAnswerExcerpt } from "../helpers/discourse-solved-helpers";
acceptance("Discourse Solved Plugin", function (needs) {
needs.user();
needs.pretender((server, helper) => {
- const postStreamWithAcceptedAnswerExcerpt = (excerpt) => {
- return {
- post_stream: {
- posts: [
- {
- id: 21,
- name: null,
- username: "kzh",
- avatar_template:
- "/letter_avatar_proxy/v2/letter/k/ac91a4/{size}.png",
- created_at: "2017-08-08T20:11:32.542Z",
- cooked: "
How do I declare a variable in ruby?
",
- post_number: 1,
- post_type: 1,
- updated_at: "2017-08-08T21:03:30.521Z",
- reply_count: 0,
- reply_to_post_number: null,
- quote_count: 0,
- avg_time: null,
- incoming_link_count: 0,
- reads: 1,
- score: 0,
- yours: true,
- topic_id: 23,
- topic_slug: "test-solved",
- display_username: null,
- primary_group_name: null,
- primary_group_flair_url: null,
- primary_group_flair_bg_color: null,
- primary_group_flair_color: null,
- version: 2,
- can_edit: true,
- can_delete: false,
- can_recover: null,
- can_wiki: true,
- read: true,
- user_title: null,
- actions_summary: [
- { id: 3, can_act: true },
- { id: 4, can_act: true },
- { id: 5, hidden: true, can_act: true },
- { id: 7, can_act: true },
- { id: 8, can_act: true },
- ],
- moderator: false,
- admin: true,
- staff: true,
- user_id: 1,
- hidden: false,
- hidden_reason_id: null,
- trust_level: 4,
- deleted_at: null,
- user_deleted: false,
- edit_reason: null,
- can_view_edit_history: true,
- wiki: false,
- can_accept_answer: false,
- can_unaccept_answer: false,
- accepted_answer: false,
- },
- {
- id: 22,
- name: null,
- username: "kzh",
- avatar_template:
- "/letter_avatar_proxy/v2/letter/k/ac91a4/{size}.png",
- created_at: "2017-08-08T20:12:04.657Z",
- cooked:
- "this is a long answer that potentially solves the question
",
- post_number: 2,
- post_type: 1,
- updated_at: "2017-08-08T21:20:24.417Z",
- reply_count: 0,
- reply_to_post_number: null,
- quote_count: 0,
- avg_time: null,
- incoming_link_count: 0,
- reads: 1,
- score: 0,
- yours: true,
- topic_id: 23,
- topic_slug: "test-solved",
- display_username: null,
- primary_group_name: null,
- primary_group_flair_url: null,
- primary_group_flair_bg_color: null,
- primary_group_flair_color: null,
- version: 2,
- can_edit: true,
- can_delete: true,
- can_recover: null,
- can_wiki: true,
- read: true,
- user_title: null,
- actions_summary: [
- { id: 3, can_act: true },
- { id: 4, can_act: true },
- { id: 5, hidden: true, can_act: true },
- { id: 7, can_act: true },
- { id: 8, can_act: true },
- ],
- moderator: false,
- admin: true,
- staff: true,
- user_id: 1,
- hidden: false,
- hidden_reason_id: null,
- trust_level: 4,
- deleted_at: null,
- user_deleted: false,
- edit_reason: null,
- can_view_edit_history: true,
- wiki: false,
- can_accept_answer: false,
- can_unaccept_answer: true,
- accepted_answer: true,
- },
- ],
- stream: [21, 22],
- },
- timeline_lookup: [[1, 0]],
- id: 23,
- title: "Test solved",
- fancy_title: "Test solved",
- posts_count: 2,
- created_at: "2017-08-08T20:11:32.098Z",
- views: 6,
- reply_count: 0,
- participant_count: 1,
- like_count: 0,
- last_posted_at: "2017-08-08T20:12:04.657Z",
- visible: true,
- closed: false,
- archived: false,
- has_summary: false,
- archetype: "regular",
- slug: "test-solved",
- category_id: 1,
- word_count: 18,
- deleted_at: null,
- pending_posts_count: 0,
- user_id: 1,
- pm_with_non_human_user: false,
- draft: null,
- draft_key: "topic_23",
- draft_sequence: 6,
- posted: true,
- unpinned: null,
- pinned_globally: false,
- pinned: false,
- pinned_at: null,
- pinned_until: null,
- details: {
- created_by: {
- id: 1,
- username: "kzh",
- avatar_template:
- "/letter_avatar_proxy/v2/letter/k/ac91a4/{size}.png",
- },
- last_poster: {
- id: 1,
- username: "kzh",
- avatar_template:
- "/letter_avatar_proxy/v2/letter/k/ac91a4/{size}.png",
- },
- participants: [
- {
- id: 1,
- username: "kzh",
- avatar_template:
- "/letter_avatar_proxy/v2/letter/k/ac91a4/{size}.png",
- post_count: 2,
- primary_group_name: null,
- primary_group_flair_url: null,
- primary_group_flair_color: null,
- primary_group_flair_bg_color: null,
- },
- ],
- notification_level: 3,
- notifications_reason_id: 1,
- can_move_posts: true,
- can_edit: true,
- can_delete: true,
- can_remove_allowed_users: true,
- can_invite_to: true,
- can_invite_via_email: true,
- can_create_post: true,
- can_reply_as_new_topic: true,
- can_flag_topic: true,
- },
- highest_post_number: 2,
- last_read_post_number: 2,
- last_read_post_id: 22,
- deleted_by: null,
- has_deleted: false,
- actions_summary: [
- { id: 4, count: 0, hidden: false, can_act: true },
- { id: 7, count: 0, hidden: false, can_act: true },
- { id: 8, count: 0, hidden: false, can_act: true },
- ],
- chunk_size: 20,
- bookmarked: false,
- tags: [],
- featured_link: null,
- topic_timer: null,
- message_bus_last_id: 0,
- accepted_answer: { post_number: 2, username: "kzh", excerpt },
- };
- };
-
server.get("/t/11.json", () => {
return helper.response(
postStreamWithAcceptedAnswerExcerpt("this is an excerpt")
diff --git a/test/javascripts/acceptance/discourse-solved-widget-post-menu-test.js b/test/javascripts/acceptance/discourse-solved-widget-post-menu-test.js
new file mode 100644
index 00000000..e390d2fb
--- /dev/null
+++ b/test/javascripts/acceptance/discourse-solved-widget-post-menu-test.js
@@ -0,0 +1,48 @@
+import { click, visit } from "@ember/test-helpers";
+import { test } from "qunit";
+import { acceptance } from "discourse/tests/helpers/qunit-helpers";
+import I18n from "I18n";
+import { postStreamWithAcceptedAnswerExcerpt } from "../helpers/discourse-solved-helpers";
+
+acceptance(
+ "Discourse Solved | Widget Post Menu |Accept and Unaccept",
+ function (needs) {
+ needs.user({
+ admin: true,
+ });
+
+ needs.settings({
+ glimmer_post_menu_mode: "disabled",
+ solved_enabled: true,
+ allow_solved_on_all_topics: true,
+ });
+
+ needs.pretender((server, helper) => {
+ server.post("/solution/accept", () => helper.response({ success: "OK" }));
+ server.post("/solution/unaccept", () =>
+ helper.response({ success: "OK" })
+ );
+
+ server.get("/t/12.json", () => {
+ return helper.response(postStreamWithAcceptedAnswerExcerpt(null));
+ });
+ });
+
+ test("accepting and unaccepting a post works", async function (assert) {
+ await visit("/t/without-excerpt/12");
+
+ assert
+ .dom("#post_2 .accepted")
+ .exists("Unaccept button is visible")
+ .hasText(I18n.t("solved.solution"), "Unaccept button has correct text");
+
+ await click("#post_2 .accepted");
+
+ assert.dom("#post_2 .unaccepted").exists("Accept button is visible");
+
+ await click("#post_2 .unaccepted");
+
+ assert.dom("#post_2 .accepted").exists("Unccept button is visible again");
+ });
+ }
+);
diff --git a/test/javascripts/helpers/discourse-solved-helpers.js b/test/javascripts/helpers/discourse-solved-helpers.js
new file mode 100644
index 00000000..2ee351ed
--- /dev/null
+++ b/test/javascripts/helpers/discourse-solved-helpers.js
@@ -0,0 +1,204 @@
+export const postStreamWithAcceptedAnswerExcerpt = (excerpt) => {
+ return {
+ post_stream: {
+ posts: [
+ {
+ id: 21,
+ name: null,
+ username: "kzh",
+ avatar_template: "/letter_avatar_proxy/v2/letter/k/ac91a4/{size}.png",
+ created_at: "2017-08-08T20:11:32.542Z",
+ cooked: "How do I declare a variable in ruby?
",
+ post_number: 1,
+ post_type: 1,
+ updated_at: "2017-08-08T21:03:30.521Z",
+ reply_count: 0,
+ reply_to_post_number: null,
+ quote_count: 0,
+ avg_time: null,
+ incoming_link_count: 0,
+ reads: 1,
+ score: 0,
+ yours: true,
+ topic_id: 23,
+ topic_slug: "test-solved",
+ display_username: null,
+ primary_group_name: null,
+ primary_group_flair_url: null,
+ primary_group_flair_bg_color: null,
+ primary_group_flair_color: null,
+ version: 2,
+ can_edit: true,
+ can_delete: false,
+ can_recover: null,
+ can_wiki: true,
+ read: true,
+ user_title: null,
+ actions_summary: [
+ { id: 3, can_act: true },
+ { id: 4, can_act: true },
+ { id: 5, hidden: true, can_act: true },
+ { id: 7, can_act: true },
+ { id: 8, can_act: true },
+ ],
+ moderator: false,
+ admin: true,
+ staff: true,
+ user_id: 1,
+ hidden: false,
+ hidden_reason_id: null,
+ trust_level: 4,
+ deleted_at: null,
+ user_deleted: false,
+ edit_reason: null,
+ can_view_edit_history: true,
+ wiki: false,
+ can_accept_answer: false,
+ can_unaccept_answer: false,
+ accepted_answer: false,
+ },
+ {
+ id: 22,
+ name: null,
+ username: "kzh",
+ avatar_template: "/letter_avatar_proxy/v2/letter/k/ac91a4/{size}.png",
+ created_at: "2017-08-08T20:12:04.657Z",
+ cooked:
+ "this is a long answer that potentially solves the question
",
+ post_number: 2,
+ post_type: 1,
+ updated_at: "2017-08-08T21:20:24.417Z",
+ reply_count: 0,
+ reply_to_post_number: null,
+ quote_count: 0,
+ avg_time: null,
+ incoming_link_count: 0,
+ reads: 1,
+ score: 0,
+ yours: true,
+ topic_id: 23,
+ topic_slug: "test-solved",
+ display_username: null,
+ primary_group_name: null,
+ primary_group_flair_url: null,
+ primary_group_flair_bg_color: null,
+ primary_group_flair_color: null,
+ version: 2,
+ can_edit: true,
+ can_delete: true,
+ can_recover: null,
+ can_wiki: true,
+ read: true,
+ user_title: null,
+ actions_summary: [
+ { id: 3, can_act: true },
+ { id: 4, can_act: true },
+ { id: 5, hidden: true, can_act: true },
+ { id: 7, can_act: true },
+ { id: 8, can_act: true },
+ ],
+ moderator: false,
+ admin: true,
+ staff: true,
+ user_id: 1,
+ hidden: false,
+ hidden_reason_id: null,
+ trust_level: 4,
+ deleted_at: null,
+ user_deleted: false,
+ edit_reason: null,
+ can_view_edit_history: true,
+ wiki: false,
+ can_accept_answer: false,
+ can_unaccept_answer: true,
+ accepted_answer: true,
+ },
+ ],
+ stream: [21, 22],
+ },
+ timeline_lookup: [[1, 0]],
+ id: 23,
+ title: "Test solved",
+ fancy_title: "Test solved",
+ posts_count: 2,
+ created_at: "2017-08-08T20:11:32.098Z",
+ views: 6,
+ reply_count: 0,
+ participant_count: 1,
+ like_count: 0,
+ last_posted_at: "2017-08-08T20:12:04.657Z",
+ visible: true,
+ closed: false,
+ archived: false,
+ has_summary: false,
+ archetype: "regular",
+ slug: "test-solved",
+ category_id: 1,
+ word_count: 18,
+ deleted_at: null,
+ pending_posts_count: 0,
+ user_id: 1,
+ pm_with_non_human_user: false,
+ draft: null,
+ draft_key: "topic_23",
+ draft_sequence: 6,
+ posted: true,
+ unpinned: null,
+ pinned_globally: false,
+ pinned: false,
+ pinned_at: null,
+ pinned_until: null,
+ details: {
+ created_by: {
+ id: 1,
+ username: "kzh",
+ avatar_template: "/letter_avatar_proxy/v2/letter/k/ac91a4/{size}.png",
+ },
+ last_poster: {
+ id: 1,
+ username: "kzh",
+ avatar_template: "/letter_avatar_proxy/v2/letter/k/ac91a4/{size}.png",
+ },
+ participants: [
+ {
+ id: 1,
+ username: "kzh",
+ avatar_template: "/letter_avatar_proxy/v2/letter/k/ac91a4/{size}.png",
+ post_count: 2,
+ primary_group_name: null,
+ primary_group_flair_url: null,
+ primary_group_flair_color: null,
+ primary_group_flair_bg_color: null,
+ },
+ ],
+ notification_level: 3,
+ notifications_reason_id: 1,
+ can_move_posts: true,
+ can_edit: true,
+ can_delete: true,
+ can_remove_allowed_users: true,
+ can_invite_to: true,
+ can_invite_via_email: true,
+ can_create_post: true,
+ can_reply_as_new_topic: true,
+ can_flag_topic: true,
+ },
+ highest_post_number: 2,
+ last_read_post_number: 2,
+ last_read_post_id: 22,
+ deleted_by: null,
+ has_deleted: false,
+ actions_summary: [
+ { id: 4, count: 0, hidden: false, can_act: true },
+ { id: 7, count: 0, hidden: false, can_act: true },
+ { id: 8, count: 0, hidden: false, can_act: true },
+ ],
+ chunk_size: 20,
+ bookmarked: false,
+ tags: [],
+ featured_link: null,
+ topic_timer: null,
+ message_bus_last_id: 0,
+ accepted_answer: { post_number: 2, username: "kzh", excerpt },
+ };
+};