diff --git a/app/assets/javascripts/discourse/app/components/modal/history.hbs b/app/assets/javascripts/discourse/app/components/modal/history.hbs new file mode 100644 index 00000000000..30ffa5425fa --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/modal/history.hbs @@ -0,0 +1,63 @@ + + <:body> + + + + <:footer> + {{#if @model.editPost}} + + {{/if}} + + \ No newline at end of file diff --git a/app/assets/javascripts/discourse/app/components/modal/history.js b/app/assets/javascripts/discourse/app/components/modal/history.js new file mode 100644 index 00000000000..4bfe4a30172 --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/modal/history.js @@ -0,0 +1,339 @@ +import { action } from "@ember/object"; +import { tracked } from "@glimmer/tracking"; +import Category from "discourse/models/category"; +import Component from "@glimmer/component"; +import I18n from "I18n"; +import Post from "discourse/models/post"; +import { categoryBadgeHTML } from "discourse/helpers/category-link"; +import { iconHTML } from "discourse-common/lib/icon-library"; +import { sanitizeAsync } from "discourse/lib/text"; +import { inject as service } from "@ember/service"; + +function customTagArray(val) { + if (!val) { + return []; + } + if (!Array.isArray(val)) { + val = [val]; + } + return val; +} + +export default class History extends Component { + @service dialog; + @service site; + @service currentUser; + @service siteSettings; + + @tracked loading; + @tracked postRevision; + @tracked viewMode = this.site?.mobileView ? "inline" : "side_by_side"; + @tracked bodyDiff; + + constructor() { + super(...arguments); + this.refresh(this.args.model.postId, this.args.model.postVersion); + } + + get loadFirstDisabled() { + return ( + this.loading || + this.postRevision?.current_revision <= this.postRevision?.first_revision + ); + } + + get loadPreviousDisabled() { + return ( + this.loading || + (!this.postRevision.previous_revision && + this.postRevision.current_revision <= + this.postRevision.previous_revision) + ); + } + + get loadNextDisabled() { + return ( + this.loading || + this.postRevision?.current_revision >= this.postRevision?.next_revision + ); + } + + get loadLastDisabled() { + return ( + this.loading || + this.postRevision?.current_revision >= this.postRevision?.next_revision + ); + } + + get displayRevisions() { + return this.postRevision?.version_count > 2; + } + + get modalTitleKey() { + return this.args.model.post.version > 100 + ? "history_capped_revisions" + : "history"; + } + + get previousVersion() { + return this.postRevision?.current_version + ? this.postRevision.current_version - 1 + : null; + } + + get revisionsText() { + return I18n.t( + "post.revisions.controls.comparing_previous_to_current_out_of_total", + { + previous: this.previousVersion, + icon: iconHTML("arrows-alt-h"), + current: this.postRevision?.current_version, + total: this.postRevision?.version_count, + } + ); + } + + get titleDiff() { + let mode = this.viewMode; + if (mode === "side_by_side_markdown") { + mode = "side_by_side"; + } + return this.postRevision?.title_changes?.[mode]; + } + + get bodyDiffHTML() { + return this.postRevision?.body_changes?.[this.viewMode]; + } + + @action + async calculateBodyDiff(_, bodyDiff) { + let html = bodyDiff; + if (this.viewMode !== "side_by_side_markdown") { + const opts = { + features: { editHistory: true, historyOneboxes: true }, + allowListed: { + editHistory: { custom: (tag, attr) => attr === "class" }, + historyOneboxes: ["header", "article", "div[style]"], + }, + }; + html = await sanitizeAsync(html, opts); + } + this.bodyDiff = html; + } + + get previousTagChanges() { + const previousArray = customTagArray( + this.postRevision.tags_changes.previous + ); + const currentSet = new Set( + customTagArray(this.postRevision.tags_changes.current) + ); + + return previousArray.map((name) => ({ + name, + deleted: !currentSet.has(name), + })); + } + + get currentTagChanges() { + const previousSet = new Set( + customTagArray(this.postRevision.tags_changes.previous) + ); + const currentArray = customTagArray(this.postRevision.tags_changes.current); + + return currentArray.map((name) => ({ + name, + inserted: !previousSet.has(name), + })); + } + + get createdAtDate() { + return moment(this.postRevision.created_at).format("LLLL"); + } + + get displayEdit() { + return !!( + this.postRevision?.can_edit && + this.args.model.editPost && + this.postRevision?.last_revision === this.postRevision?.current_revision + ); + } + + get revertToRevisionText() { + return I18n.t("post.revisions.controls.revert", { + revision: this.previousVersion, + }); + } + + refresh(postId, postVersion) { + this.loading = true; + Post.loadRevision(postId, postVersion).then((result) => { + this.loading = false; + this.postRevision = result; + }); + } + + hide(postId, postVersion) { + Post.hideRevision(postId, postVersion).then(() => + this.refresh(postId, postVersion) + ); + } + + show(postId, postVersion) { + Post.showRevision(postId, postVersion).then(() => + this.refresh(postId, postVersion) + ); + } + + revert(post, postVersion) { + post + .revertToRevision(postVersion) + .then((result) => { + this.refresh(post.id, postVersion); + if (result.topic) { + post.set("topic.slug", result.topic.slug); + post.set("topic.title", result.topic.title); + post.set("topic.fancy_title", result.topic.fancy_title); + } + if (result.category_id) { + post.set("topic.category", Category.findById(result.category_id)); + } + this.args.closeModal(); + }) + .catch(function (e) { + if (e.jqXHR.responseJSON?.errors?.[0]) { + this.dialog.alert(e.jqXHR.responseJSON.errors[0]); + } + }); + } + + get editButtonLabel() { + return `post.revisions.controls.${ + this.postRevision.wiki ? "edit_wiki" : "edit_post" + }`; + } + + get hiddenClasses() { + if (this.viewMode === "inline") { + return this.postRevision?.previous_hidden || + this.postRevision?.current_hidden + ? "hidden-revision-either" + : null; + } else { + let result = []; + if (this.postRevision?.previous_hidden) { + result.push("hidden-revision-previous"); + } + if (this.postRevision?.current_hidden) { + result.push("hidden-revision-current"); + } + return result.join(" "); + } + } + + get previousCategory() { + if (this.postRevision?.category_id_changes) { + let category = Category.findById( + this.postRevision.category_id_changes.previous + ); + return categoryBadgeHTML(category, { allowUncategorized: true }); + } + } + + get currentCategory() { + if (this.postRevision?.category_id_changes) { + let category = Category.findById( + this.postRevision.category_id_changes.current + ); + return categoryBadgeHTML(category, { allowUncategorized: true }); + } + } + + get wikiDisabled() { + return !this.postRevision.wiki_changes?.current; + } + + get postTypeDisabled() { + return ( + this.postRevision?.post_type_changes?.current !== + this.site.post_types.moderator_action + ); + } + + @action + displayInline(event) { + event?.preventDefault(); + this.viewMode = "inline"; + } + + @action + displaySideBySide(event) { + event?.preventDefault(); + this.viewMode = "side_by_side"; + } + + @action + displaySideBySideMarkdown(event) { + event?.preventDefault(); + this.viewMode = "side_by_side_markdown"; + } + + @action + loadFirstVersion() { + this.refresh(this.postRevision.post_id, this.postRevision.first_revision); + } + + @action + loadPreviousVersion() { + this.refresh( + this.postRevision.post_id, + this.postRevision.previous_revision + ); + } + + @action + loadNextVersion() { + this.refresh(this.postRevision.post_id, this.postRevision.next_revision); + } + + @action + loadLastVersion() { + return this.refresh( + this.postRevision.post_id, + this.postRevision.last_revision + ); + } + + @action + hideVersion() { + this.hide(this.postRevision.post_id, this.postRevision.current_revision); + } + + @action + permanentlyDeleteVersions() { + this.dialog.yesNoConfirm({ + message: I18n.t("post.revisions.controls.destroy_confirm"), + didConfirm: () => { + Post.permanentlyDeleteRevisions(this.postRevision.post_id).then(() => { + this.args.closeModal(); + }); + }, + }); + } + + @action + showVersion() { + this.show(this.postRevision.post_id, this.postRevision.current_revision); + } + + @action + editPost() { + this.args.model.editPost(this.args.model.post); + this.args.closeModal(); + } + + @action + revertToVersion() { + this.revert(this.args.model.post, this.postRevision.current_revision); + } +} diff --git a/app/assets/javascripts/discourse/app/components/modal/history/revision.hbs b/app/assets/javascripts/discourse/app/components/modal/history/revision.hbs new file mode 100644 index 00000000000..b6233bffaea --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/modal/history/revision.hbs @@ -0,0 +1,101 @@ +
+
+ {{d-icon "pencil-alt"}} + + {{bound-avatar-template @model.avatar_template "small"}} + {{@model.username}} + + + {{bound-date @model.created_at}} + {{#if @model.edit_reason}} + — + {{@model.edit_reason}} + {{/if}} + {{#unless @mobileView}} + {{#if @model.user_changes}} + — + {{bound-avatar-template + @model.user_changes.previous.avatar_template + "small" + }} + {{@model.user_changes.previous.username}} + → + {{bound-avatar-template + @model.user_changes.current.avatar_template + "small" + }} + {{@model.user_changes.current.username}} + {{/if}} + {{#if @model.wiki_changes}} + — + + {{/if}} + {{#if @model.post_type_changes}} + — + + {{/if}} + {{#if @model.category_id_changes}} + — + {{html-safe @previousCategory}} + → + {{html-safe @currentCategory}} + {{/if}} + {{/unless}} +
+ {{#unless @mobileView}} +
+ +
+ {{/unless}} +
\ No newline at end of file diff --git a/app/assets/javascripts/discourse/app/components/modal/history/revisions.hbs b/app/assets/javascripts/discourse/app/components/modal/history/revisions.hbs new file mode 100644 index 00000000000..9d614d76cc3 --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/modal/history/revisions.hbs @@ -0,0 +1,75 @@ +
+ {{#if @model.title_changes}} +
+

{{html-safe @titleDiff}}

+
+ {{/if}} + {{#if @mobileView}} + {{#if @userChanges}} +
+ {{bound-avatar-template + @model.user_changes.previous.avatar_template + "small" + }} + {{@model.user_changes.previous.username}} + → + {{bound-avatar-template + @model.user_changes.current.avatar_template + "small" + }} + {{@model.user_changes.current.username}} +
+ {{/if}} + {{#if @model.wiki_changes}} +
+ +
+ {{/if}} + {{#if @model.post_type_changes}} +
+ +
+ {{/if}} + {{#if @model.category_id_changes}} +
+ {{html-safe @previousCategory}} + → + {{html-safe @currentCategory}} +
+ {{/if}} + {{/if}} + {{#if @model.tags_changes}} +
+ {{i18n "tagging.changed"}} + {{#each @previousTagChanges as |t|}} + {{discourse-tag t.name style=(if t.deleted "diff-del")}} + {{/each}} + →  + {{#each @currentTagChanges as |t|}} + {{discourse-tag t.name style=(if t.inserted "diff-ins")}} + {{/each}} +
+ {{/if}} + {{#if @model.featured_link_changes}} +
+ {{@model.featured_link_changes.previous}} + → + {{@model.featured_link_changes.current}} +
+ {{/if}} + + + + + + + {{html-safe @bodyDiff}} + +
\ No newline at end of file diff --git a/app/assets/javascripts/discourse/app/components/modal/history/topic-footer.hbs b/app/assets/javascripts/discourse/app/components/modal/history/topic-footer.hbs new file mode 100644 index 00000000000..85c13c89a4f --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/modal/history/topic-footer.hbs @@ -0,0 +1,84 @@ +
+ + +
+ + {{html-safe @revisionsText}} + +
+ + +
+ + \ No newline at end of file diff --git a/app/assets/javascripts/discourse/app/components/reviewable-post-edits.js b/app/assets/javascripts/discourse/app/components/reviewable-post-edits.js index 75b8692a49f..297df51b826 100644 --- a/app/assets/javascripts/discourse/app/components/reviewable-post-edits.js +++ b/app/assets/javascripts/discourse/app/components/reviewable-post-edits.js @@ -4,9 +4,12 @@ import { action } from "@ember/object"; import { gt } from "@ember/object/computed"; import { historyHeat } from "discourse/widgets/post-edits-indicator"; import { longDate } from "discourse/lib/formatter"; -import showModal from "discourse/lib/show-modal"; +import { inject as service } from "@ember/service"; +import HistoryModal from "discourse/components/modal/history"; export default Component.extend({ + modal: service(), + hasEdits: gt("reviewable.post_version", 1), @discourseComputed("reviewable.post_updated_at") @@ -24,13 +27,14 @@ export default Component.extend({ event?.preventDefault(); let postId = this.get("reviewable.post_id"); this.store.find("post", postId).then((post) => { - let historyController = showModal("history", { - model: post, - modalClass: "history-modal", + this.modal.show(HistoryModal, { + model: { + post, + postId, + postVersion: "latest", + topicController: null, + }, }); - historyController.refresh(postId, "latest"); - historyController.set("post", post); - historyController.set("topicController", null); }); }, }); diff --git a/app/assets/javascripts/discourse/app/controllers/history.js b/app/assets/javascripts/discourse/app/controllers/history.js deleted file mode 100644 index 0d428b226b0..00000000000 --- a/app/assets/javascripts/discourse/app/controllers/history.js +++ /dev/null @@ -1,392 +0,0 @@ -import { action } from "@ember/object"; -import { alias, equal, gt, not, or } from "@ember/object/computed"; -import discourseComputed, { - observes, - on, -} from "discourse-common/utils/decorators"; -import { propertyGreaterThan, propertyLessThan } from "discourse/lib/computed"; -import Category from "discourse/models/category"; -import Controller from "@ember/controller"; -import I18n from "I18n"; -import ModalFunctionality from "discourse/mixins/modal-functionality"; -import Post from "discourse/models/post"; -import { categoryBadgeHTML } from "discourse/helpers/category-link"; -import { iconHTML } from "discourse-common/lib/icon-library"; -import { sanitizeAsync } from "discourse/lib/text"; -import { inject as service } from "@ember/service"; - -function customTagArray(val) { - if (!val) { - return []; - } - if (!Array.isArray(val)) { - val = [val]; - } - return val; -} - -// This controller handles displaying of history -export default Controller.extend(ModalFunctionality, { - dialog: service(), - loading: true, - viewMode: "side_by_side", - - @on("init") - _changeViewModeOnMobile() { - if (this.site && this.site.mobileView) { - this.set("viewMode", "inline"); - } - }, - - previousFeaturedLink: alias("model.featured_link_changes.previous"), - currentFeaturedLink: alias("model.featured_link_changes.current"), - - @discourseComputed( - "model.tags_changes.previous", - "model.tags_changes.current" - ) - previousTagChanges(previous, current) { - const previousArray = customTagArray(previous); - const currentSet = new Set(customTagArray(current)); - - return previousArray.map((name) => ({ - name, - deleted: !currentSet.has(name), - })); - }, - - @discourseComputed( - "model.tags_changes.previous", - "model.tags_changes.current" - ) - currentTagChanges(previous, current) { - const previousSet = new Set(customTagArray(previous)); - const currentArray = customTagArray(current); - - return currentArray.map((name) => ({ - name, - inserted: !previousSet.has(name), - })); - }, - - @discourseComputed("post.version") - modalTitleKey(version) { - if (version > 100) { - return "history_capped_revisions"; - } else { - return "history"; - } - }, - - @discourseComputed( - "previousVersion", - "model.current_version", - "model.version_count" - ) - revisionsText(previous, current, total) { - return I18n.t( - "post.revisions.controls.comparing_previous_to_current_out_of_total", - { - previous, - icon: iconHTML("arrows-alt-h"), - current, - total, - } - ); - }, - - @discourseComputed("previousVersion") - revertToRevisionText(revision) { - return I18n.t("post.revisions.controls.revert", { revision }); - }, - - refresh(postId, postVersion) { - this.set("loading", true); - - Post.loadRevision(postId, postVersion).then((result) => { - this.setProperties({ loading: false, model: result }); - }); - }, - - hide(postId, postVersion) { - Post.hideRevision(postId, postVersion).then(() => - this.refresh(postId, postVersion) - ); - }, - - permanentlyDeleteRevisions(postId) { - this.dialog.yesNoConfirm({ - message: I18n.t("post.revisions.controls.destroy_confirm"), - didConfirm: () => { - Post.permanentlyDeleteRevisions(postId).then(() => { - this.send("closeModal"); - }); - }, - }); - }, - - show(postId, postVersion) { - Post.showRevision(postId, postVersion).then(() => - this.refresh(postId, postVersion) - ); - }, - - revert(post, postVersion) { - post - .revertToRevision(postVersion) - .then((result) => { - this.refresh(post.get("id"), postVersion); - if (result.topic) { - post.set("topic.slug", result.topic.slug); - post.set("topic.title", result.topic.title); - post.set("topic.fancy_title", result.topic.fancy_title); - } - if (result.category_id) { - post.set("topic.category", Category.findById(result.category_id)); - } - this.send("closeModal"); - }) - .catch(function (e) { - if ( - e.jqXHR.responseJSON && - e.jqXHR.responseJSON.errors && - e.jqXHR.responseJSON.errors[0] - ) { - this.dialog.alert(e.jqXHR.responseJSON.errors[0]); - } - }); - }, - - @discourseComputed("model.created_at") - createdAtDate(createdAt) { - return moment(createdAt).format("LLLL"); - }, - - @discourseComputed("model.current_version") - previousVersion(current) { - return current - 1; - }, - - @discourseComputed("model.current_revision", "model.previous_revision") - displayGoToPrevious(current, prev) { - return prev && current > prev; - }, - - displayRevisions: gt("model.version_count", 2), - - displayGoToFirst: propertyGreaterThan( - "model.current_revision", - "model.first_revision" - ), - displayGoToNext: propertyLessThan( - "model.current_revision", - "model.next_revision" - ), - displayGoToLast: propertyLessThan( - "model.current_revision", - "model.next_revision" - ), - - hideGoToFirst: not("displayGoToFirst"), - hideGoToPrevious: not("displayGoToPrevious"), - hideGoToNext: not("displayGoToNext"), - hideGoToLast: not("displayGoToLast"), - - loadFirstDisabled: or("loading", "hideGoToFirst"), - loadPreviousDisabled: or("loading", "hideGoToPrevious"), - loadNextDisabled: or("loading", "hideGoToNext"), - loadLastDisabled: or("loading", "hideGoToLast"), - - @discourseComputed("model.previous_hidden") - displayShow(prevHidden) { - return prevHidden && this.currentUser && this.currentUser.get("staff"); - }, - - @discourseComputed("model.previous_hidden") - displayHide(prevHidden) { - return !prevHidden && this.currentUser && this.currentUser.get("staff"); - }, - - @discourseComputed( - "model.last_revision", - "model.current_revision", - "model.can_edit", - "topicController" - ) - displayEdit(lastRevision, currentRevision, canEdit, topicController) { - return !!(canEdit && topicController && lastRevision === currentRevision); - }, - - @discourseComputed("model.wiki") - editButtonLabel(wiki) { - return `post.revisions.controls.${wiki ? "edit_wiki" : "edit_post"}`; - }, - - @discourseComputed() - displayRevert() { - return this.currentUser && this.currentUser.get("staff"); - }, - - @discourseComputed("model.previous_hidden") - displayPermanentlyDeleteButton(previousHidden) { - return ( - this.siteSettings.can_permanently_delete && - this.currentUser?.staff && - previousHidden - ); - }, - - isEitherRevisionHidden: or("model.previous_hidden", "model.current_hidden"), - - @discourseComputed( - "model.previous_hidden", - "model.current_hidden", - "displayingInline" - ) - hiddenClasses(prevHidden, currentHidden, displayingInline) { - if (displayingInline) { - return this.isEitherRevisionHidden ? "hidden-revision-either" : null; - } else { - let result = []; - if (prevHidden) { - result.push("hidden-revision-previous"); - } - if (currentHidden) { - result.push("hidden-revision-current"); - } - return result.join(" "); - } - }, - - displayingInline: equal("viewMode", "inline"), - displayingSideBySide: equal("viewMode", "side_by_side"), - displayingSideBySideMarkdown: equal("viewMode", "side_by_side_markdown"), - - @discourseComputed("displayingInline") - inlineClass(displayingInline) { - return displayingInline ? "active" : ""; - }, - - @discourseComputed("displayingSideBySide") - sideBySideClass(displayingSideBySide) { - return displayingSideBySide ? "active" : ""; - }, - - @discourseComputed("displayingSideBySideMarkdown") - sideBySideMarkdownClass(displayingSideBySideMarkdown) { - return displayingSideBySideMarkdown ? "active" : ""; - }, - - @discourseComputed("model.category_id_changes") - previousCategory(changes) { - if (changes) { - let category = Category.findById(changes["previous"]); - return categoryBadgeHTML(category, { allowUncategorized: true }); - } - }, - - @discourseComputed("model.category_id_changes") - currentCategory(changes) { - if (changes) { - let category = Category.findById(changes["current"]); - return categoryBadgeHTML(category, { allowUncategorized: true }); - } - }, - - @discourseComputed("model.wiki_changes") - wikiDisabled(changes) { - return changes && !changes["current"]; - }, - - @discourseComputed("model.post_type_changes") - postTypeDisabled(changes) { - return ( - changes && - changes["current"] !== this.site.get("post_types.moderator_action") - ); - }, - - @discourseComputed("viewMode", "model.title_changes") - titleDiff(viewMode) { - if (viewMode === "side_by_side_markdown") { - viewMode = "side_by_side"; - } - return this.get("model.title_changes." + viewMode); - }, - - @observes("viewMode", "model.body_changes") - bodyDiffChanged() { - const viewMode = this.viewMode; - const html = this.get(`model.body_changes.${viewMode}`); - if (viewMode === "side_by_side_markdown") { - this.set("bodyDiff", html); - } else { - const opts = { - features: { editHistory: true, historyOneboxes: true }, - allowListed: { - editHistory: { custom: (tag, attr) => attr === "class" }, - historyOneboxes: ["header", "article", "div[style]"], - }, - }; - - return sanitizeAsync(html, opts).then((result) => - this.set("bodyDiff", result) - ); - } - }, - - @action - displayInline(event) { - event?.preventDefault(); - this.set("viewMode", "inline"); - }, - - @action - displaySideBySide(event) { - event?.preventDefault(); - this.set("viewMode", "side_by_side"); - }, - - @action - displaySideBySideMarkdown(event) { - event?.preventDefault(); - this.set("viewMode", "side_by_side_markdown"); - }, - - actions: { - loadFirstVersion() { - this.refresh(this.get("model.post_id"), this.get("model.first_revision")); - }, - loadPreviousVersion() { - this.refresh( - this.get("model.post_id"), - this.get("model.previous_revision") - ); - }, - loadNextVersion() { - this.refresh(this.get("model.post_id"), this.get("model.next_revision")); - }, - loadLastVersion() { - this.refresh(this.get("model.post_id"), this.get("model.last_revision")); - }, - - hideVersion() { - this.hide(this.get("model.post_id"), this.get("model.current_revision")); - }, - permanentlyDeleteVersions() { - this.permanentlyDeleteRevisions(this.get("model.post_id")); - }, - showVersion() { - this.show(this.get("model.post_id"), this.get("model.current_revision")); - }, - - editPost() { - this.topicController.send("editPost", this.post); - this.send("closeModal"); - }, - - revertToVersion() { - this.revert(this.post, this.get("model.current_revision")); - }, - }, -}); diff --git a/app/assets/javascripts/discourse/app/routes/topic.js b/app/assets/javascripts/discourse/app/routes/topic.js index 815745ce3ff..ac37d4aaba3 100644 --- a/app/assets/javascripts/discourse/app/routes/topic.js +++ b/app/assets/javascripts/discourse/app/routes/topic.js @@ -10,12 +10,14 @@ import { setTopicId } from "discourse/lib/topic-list-tracker"; import showModal from "discourse/lib/show-modal"; import TopicFlag from "discourse/lib/flag-targets/topic-flag"; import PostFlag from "discourse/lib/flag-targets/post-flag"; +import HistoryModal from "discourse/components/modal/history"; const SCROLL_DELAY = 500; const TopicRoute = DiscourseRoute.extend({ composer: service(), screenTrack: service(), + modal: service(), scheduledReplace: null, lastScrollPos: null, @@ -153,13 +155,14 @@ const TopicRoute = DiscourseRoute.extend({ @action showHistory(model, revision) { - let historyController = showModal("history", { - model, - modalClass: "history-modal", + this.modal.show(HistoryModal, { + model: { + postId: model.id, + postVersion: revision || "latest", + post: model, + editPost: (post) => this.controllerFor("topic").send("editPost", post), + }, }); - historyController.refresh(model.get("id"), revision || "latest"); - historyController.set("post", model); - historyController.set("topicController", this.controllerFor("topic")); }, @action diff --git a/app/assets/javascripts/discourse/app/templates/modal/history.hbs b/app/assets/javascripts/discourse/app/templates/modal/history.hbs deleted file mode 100644 index 0a3d1dc6e29..00000000000 --- a/app/assets/javascripts/discourse/app/templates/modal/history.hbs +++ /dev/null @@ -1,271 +0,0 @@ - -
-
- {{d-icon "pencil-alt"}} - - {{bound-avatar-template this.model.avatar_template "small"}} - {{this.model.username}} - - - {{bound-date this.model.created_at}} - {{#if this.model.edit_reason}} - — - {{this.model.edit_reason}} - {{/if}} - {{#unless this.site.mobileView}} - {{#if this.model.user_changes}} - — - {{bound-avatar-template - this.model.user_changes.previous.avatar_template - "small" - }} - {{this.model.user_changes.previous.username}} - → - {{bound-avatar-template - this.model.user_changes.current.avatar_template - "small" - }} - {{this.model.user_changes.current.username}} - {{/if}} - {{#if this.model.wiki_changes}} - — - - {{/if}} - {{#if this.model.post_type_changes}} - — - - {{/if}} - {{#if this.model.category_id_changes}} - — - {{html-safe this.previousCategory}} - → - {{html-safe this.currentCategory}} - {{/if}} - {{/unless}} -
- {{#unless this.site.mobileView}} - - {{/unless}} -
-
- {{#if this.model.title_changes}} -
-

{{html-safe this.titleDiff}}

-
- {{/if}} - {{#if this.site.mobileView}} - {{#if this.user_changes}} -
- {{bound-avatar-template - this.model.user_changes.previous.avatar_template - "small" - }} - {{this.model.user_changes.previous.username}} - → - {{bound-avatar-template - this.model.user_changes.current.avatar_template - "small" - }} - {{this.model.user_changes.current.username}} -
- {{/if}} - {{#if this.model.wiki_changes}} -
- -
- {{/if}} - {{#if this.model.post_type_changes}} -
- -
- {{/if}} - {{#if this.model.category_id_changes}} -
- {{html-safe this.previousCategory}} - → - {{html-safe this.currentCategory}} -
- {{/if}} - {{/if}} - {{#if this.model.tags_changes}} -
- {{i18n "tagging.changed"}} - {{#each this.previousTagChanges as |t|}} - {{discourse-tag t.name style=(if t.deleted "diff-del")}} - {{/each}} - →  - {{#each this.currentTagChanges as |t|}} - {{discourse-tag t.name style=(if t.inserted "diff-ins")}} - {{/each}} -
- {{/if}} - {{#if this.model.featured_link_changes}} -
- {{this.model.featured_link_changes.previous}} - → - {{this.model.featured_link_changes.current}} -
- {{/if}} - - - - - - - {{html-safe this.bodyDiff}} - -
-
-{{#if this.topicController}} - -{{/if}} \ No newline at end of file diff --git a/app/assets/javascripts/discourse/tests/acceptance/history-test.js b/app/assets/javascripts/discourse/tests/acceptance/history-test.js new file mode 100644 index 00000000000..fdad8e4cf15 --- /dev/null +++ b/app/assets/javascripts/discourse/tests/acceptance/history-test.js @@ -0,0 +1,90 @@ +import { acceptance } from "discourse/tests/helpers/qunit-helpers"; +import { test } from "qunit"; +import { click, visit } from "@ember/test-helpers"; + +const revisionResponse = { + created_at: "2021-11-24T10:59:36.163Z", + post_id: 419, + previous_hidden: false, + current_hidden: false, + first_revision: 1, + previous_revision: 1, + current_revision: 2, + next_revision: null, + last_revision: 2, + current_version: 2, + version_count: 2, + username: "bianca", + display_username: "bianca", + avatar_template: "/letter_avatar_proxy/v4/letter/b/3be4f8/{size}.png", + edit_reason: null, + body_changes: { + inline: '

Welcome to Discourse!', + side_by_side: + '

Welcome to Discourse!

Welcome to Discourse!', + side_by_side_markdown: + '
Welcome to Discourse!Welcome to Discourse!
', + }, + title_changes: { + inline: '

Welcome to Discourse!
', + side_by_side: + '
Welcome to Discourse!
Welcome to Discourse!
', + }, + user_changes: null, + tags_changes: { + previous: ["tag1", "tag2"], + current: ["tag2", "tag3"], + }, + wiki: false, + can_edit: true, +}; + +acceptance("History Modal - authorized", function (needs) { + needs.user(); + needs.pretender((server, helper) => { + server.get("/posts/419/revisions/latest.json", () => { + return helper.response(revisionResponse); + }); + + server.get("/posts/419/revisions/1.json", () => { + return helper.response({ ...revisionResponse, current_revision: 1 }); + }); + }); + + test("edit post button", async function (assert) { + await visit("/t/internationalization-localization/280"); + await click("article[data-post-id='419'] .edits button"); + assert + .dom(".history-modal #revision-footer-buttons .edit-post") + .exists("displays the edit post button on the latest revision"); + }); + + test("edit post button - not last revision", async function (assert) { + await visit("/t/internationalization-localization/280"); + await click("article[data-post-id='419'] .edits button"); + await click(".history-modal .previous-revision"); + assert + .dom(".history-modal #revision-footer-buttons .edit-post") + .doesNotExist( + "hides the edit post button when not on the latest revision" + ); + }); +}); + +acceptance("History Modal - anonymous", function (needs) { + needs.pretender((server, helper) => { + server.get("/posts/419/revisions/latest.json", () => { + return helper.response({ ...revisionResponse, can_edit: false }); + }); + }); + + test("edit post button", async function (assert) { + await visit("/t/internationalization-localization/280"); + await click("article[data-post-id='419'] .edits button"); + assert + .dom(".history-modal #revision-footer-buttons .edit-post") + .doesNotExist( + "it should not display edit button when user cannot edit the post" + ); + }); +}); diff --git a/app/assets/javascripts/discourse/tests/unit/controllers/history-test.js b/app/assets/javascripts/discourse/tests/unit/controllers/history-test.js deleted file mode 100644 index cb3670e43de..00000000000 --- a/app/assets/javascripts/discourse/tests/unit/controllers/history-test.js +++ /dev/null @@ -1,120 +0,0 @@ -import { module, test } from "qunit"; -import { setupTest } from "ember-qunit"; - -module("Unit | Controller | history", function (hooks) { - setupTest(hooks); - - test("displayEdit", async function (assert) { - const controller = this.owner.lookup("controller:history"); - - controller.setProperties({ - model: { last_revision: 3, current_revision: 3, can_edit: false }, - topicController: {}, - }); - - assert.strictEqual( - controller.displayEdit, - false, - "it should not display edit button when user cannot edit the post" - ); - - controller.set("model.can_edit", true); - - assert.strictEqual( - controller.displayEdit, - true, - "it should display edit button when user can edit the post" - ); - - controller.set("topicController", null); - assert.strictEqual( - controller.displayEdit, - false, - "it should not display edit button when there is not topic controller" - ); - controller.set("topicController", {}); - - controller.set("model.current_revision", 2); - assert.strictEqual( - controller.displayEdit, - false, - "it should only display the edit button on the latest revision" - ); - - const html = `
-

" width="276" height="183">

-
- - - - - - - - - - - - - - -
ColumnTest
OsamaTesting
`; - - const expectedOutput = `
-

" width="276" height="183">

-
- - - - - - - - - - - - - - -
ColumnTest
OsamaTesting
`; - - controller.setProperties({ - viewMode: "side_by_side", - model: { - body_changes: { - side_by_side: html, - }, - }, - }); - - await controller.bodyDiffChanged(); - - const output = controller.bodyDiff; - assert.strictEqual( - output, - expectedOutput, - "it keeps HTML safe and doesn't strip onebox tags" - ); - }); -});