REFACTOR: Remove `_.merge`

REFACTOR: Remove _.merge

diff --git a/app/assets/javascripts/discourse-common/addon/lib/object.js b/app/assets/javascripts/discourse-common/addon/lib/object.js
new file mode 100644
index 0000000..c9f0e40
--- /dev/null
+++ b/app/assets/javascripts/discourse-common/addon/lib/object.js
@@ -0,0 +1,37 @@
+// a fairly simple deep merge based on: https://gist.github.com/ahtcx/0cd94e62691f539160b32ecda18af3d6
+export function merge(...objects) {
+  const isObject = obj => obj && typeof obj === "object";
+
+  function deepMergeInner(target, source) {
+    Object.keys(source).forEach(key => {
+      const targetValue = target[key];
+      const sourceValue = source[key];
+
+      if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {
+        target[key] = targetValue.concat(sourceValue);
+      } else if (isObject(targetValue) && isObject(sourceValue)) {
+        target[key] = deepMergeInner(
+          Object.assign({}, targetValue),
+          sourceValue
+        );
+      } else {
+        target[key] = sourceValue;
+      }
+    });
+
+    return target;
+  }
+
+  if (objects.some(object => object && !isObject(object))) {
+    throw new Error('deepMerge: all values should be of type "object"');
+  }
+
+  const target = objects.shift();
+  let source;
+
+  while ((source = objects.shift())) {
+    deepMergeInner(target, source || {});
+  }
+
+  return target;
+}
diff --git a/app/assets/javascripts/discourse/app/controllers/topic.js b/app/assets/javascripts/discourse/app/controllers/topic.js
index 5aaeb4a..be9b7e9 100644
--- a/app/assets/javascripts/discourse/app/controllers/topic.js
+++ b/app/assets/javascripts/discourse/app/controllers/topic.js
@@ -26,6 +26,7 @@ import { escapeExpression } from "discourse/lib/utilities";
 import { AUTO_DELETE_PREFERENCES } from "discourse/models/bookmark";
 import { inject as service } from "@ember/service";
 import bootbox from "bootbox";
+import { merge } from "discourse-common/lib/object";
 
 let customPostMessageCallbacks = {};
 
@@ -249,7 +250,7 @@ export default Controller.extend(bufferedProperty("model"), {
     this.set("loadingPostIds", true);
 
     return ajax(url, {
-      data: _.merge(
+      data: merge(
         { post_number: post.get("post_number") },
         postStream.get("streamFilters")
       )
diff --git a/app/assets/javascripts/discourse/app/lib/search.js b/app/assets/javascripts/discourse/app/lib/search.js
index 02b7843..c91677b 100644
--- a/app/assets/javascripts/discourse/app/lib/search.js
+++ b/app/assets/javascripts/discourse/app/lib/search.js
@@ -13,6 +13,7 @@ import User from "discourse/models/user";
 import Post from "discourse/models/post";
 import Topic from "discourse/models/topic";
 import { escapeExpression } from "discourse/lib/utilities";
+import { merge } from "discourse-common/lib/object";
 
 export function translateResults(results, opts) {
   opts = opts || {};
@@ -206,7 +207,7 @@ export function applySearchAutocomplete(
   };
 
   $input.autocomplete(
-    _.merge(
+    merge(
       {
         template: findRawTemplate("category-tag-autocomplete"),
         key: "#",
@@ -226,7 +227,7 @@ export function applySearchAutocomplete(
 
   if (siteSettings.enable_mentions) {
     $input.autocomplete(
-      _.merge(
+      merge(
         {
           template: findRawTemplate("user-selector-autocomplete"),
           key: "@",
diff --git a/app/assets/javascripts/discourse/app/lib/utilities.js b/app/assets/javascripts/discourse/app/lib/utilities.js
index 965d709..946e12f 100644
--- a/app/assets/javascripts/discourse/app/lib/utilities.js
+++ b/app/assets/javascripts/discourse/app/lib/utilities.js
@@ -4,6 +4,7 @@ import toMarkdown from "discourse/lib/to-markdown";
 import Handlebars from "handlebars";
 import { default as getURL, getURLWithCDN } from "discourse-common/lib/get-url";
 import { helperContext } from "discourse-common/lib/helpers";
+import { merge } from "discourse-common/lib/object";
 
 let _defaultHomepage;
 
@@ -85,7 +86,7 @@ export function avatarImg(options, customGetURL) {
 
 export function tinyAvatar(avatarTemplate, options) {
   return avatarImg(
-    _.merge({ avatarTemplate: avatarTemplate, size: "tiny" }, options)
+    merge({ avatarTemplate: avatarTemplate, size: "tiny" }, options)
   );
 }
 
diff --git a/app/assets/javascripts/discourse/app/mixins/upload.js b/app/assets/javascripts/discourse/app/mixins/upload.js
index 69b36bf..2846ce3 100644
--- a/app/assets/javascripts/discourse/app/mixins/upload.js
+++ b/app/assets/javascripts/discourse/app/mixins/upload.js
@@ -8,6 +8,7 @@ import getUrl from "discourse-common/lib/get-url";
 import { on } from "@ember/object/evented";
 import Mixin from "@ember/object/mixin";
 import bootbox from "bootbox";
+import { merge } from "discourse-common/lib/object";
 
 export default Mixin.create({
   uploading: false,
@@ -56,7 +57,7 @@ export default Mixin.create({
     });
 
     $upload.fileupload(
-      _.merge(
+      merge(
         {
           url: this.calculateUploadUrl(),
           dataType: "json",
@@ -82,7 +83,7 @@ export default Mixin.create({
     });
 
     $upload.on("fileuploadsubmit", (e, data) => {
-      const opts = _.merge(
+      const opts = merge(
         {
           bypassNewUserRestriction: true,
           user: this.currentUser,
diff --git a/app/assets/javascripts/discourse/app/models/nav-item.js b/app/assets/javascripts/discourse/app/models/nav-item.js
index 6597ae9..8c8bb5b 100644
--- a/app/assets/javascripts/discourse/app/models/nav-item.js
+++ b/app/assets/javascripts/discourse/app/models/nav-item.js
@@ -9,6 +9,7 @@ import deprecated from "discourse-common/lib/deprecated";
 import Site from "discourse/models/site";
 import User from "discourse/models/user";
 import { getOwner } from "discourse-common/lib/get-owner";
+import { merge } from "discourse-common/lib/object";
 
 const NavItem = EmberObject.extend({
   @discourseComputed("name")
@@ -180,7 +181,7 @@ NavItem.reopenClass({
       args.noSubcategories = true;
     }
     NavItem.extraArgsCallbacks.forEach(cb =>
-      _.merge(args, cb.call(this, filterType, opts))
+      merge(args, cb.call(this, filterType, opts))
     );
 
     let store = getOwner(this).lookup("service:store");
@@ -222,7 +223,7 @@ NavItem.reopenClass({
     };
 
     const extraItems = NavItem.extraNavItemDescriptors
-      .map(descriptor => ExtraNavItem.create(_.merge({}, context, descriptor)))
+      .map(descriptor => ExtraNavItem.create(merge({}, context, descriptor)))
       .filter(item => {
         if (!item.customFilter) return true;
         return item.customFilter(category, args);
diff --git a/app/assets/javascripts/discourse/app/models/post-stream.js b/app/assets/javascripts/discourse/app/models/post-stream.js
index 4a9a245..f2036ff 100644
--- a/app/assets/javascripts/discourse/app/models/post-stream.js
+++ b/app/assets/javascripts/discourse/app/models/post-stream.js
@@ -10,6 +10,7 @@ import discourseComputed from "discourse-common/utils/decorators";
 import { loadTopicView } from "discourse/models/topic";
 import { Promise } from "rsvp";
 import User from "discourse/models/user";
+import { merge } from "discourse-common/lib/object";
 
 export default RestModel.extend({
   _identityMap: null,
@@ -274,7 +275,7 @@ export default RestModel.extend({
     this.set("loadingFilter", true);
     this.set("loadingNearPost", opts.nearPost);
 
-    opts = _.merge(opts, this.streamFilters);
+    opts = merge(opts, this.streamFilters);
 
     // Request a topicView
     return loadTopicView(topic, opts)
@@ -943,7 +944,7 @@ export default RestModel.extend({
       include_suggested: includeSuggested
     };
 
-    data = _.merge(data, this.streamFilters);
+    data = merge(data, this.streamFilters);
     const store = this.store;
 
     return ajax(url, { data }).then(result => {
diff --git a/app/assets/javascripts/discourse/app/models/topic.js b/app/assets/javascripts/discourse/app/models/topic.js
index 5cbaf14..56d1cce 100644

[... diff too long, it was truncated ...]

GitHub sha: c4079780

This commit appears in #10566 which was merged by eviltrout.