utils.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. import $ from "autocomplete.js/zepto";
  2. const utils = {
  3. /*
  4. * Move the content of an object key one level higher.
  5. * eg.
  6. * {
  7. * name: 'My name',
  8. * hierarchy: {
  9. * lvl0: 'Foo',
  10. * lvl1: 'Bar'
  11. * }
  12. * }
  13. * Will be converted to
  14. * {
  15. * name: 'My name',
  16. * lvl0: 'Foo',
  17. * lvl1: 'Bar'
  18. * }
  19. * @param {Object} object Main object
  20. * @param {String} property Main object key to move up
  21. * @return {Object}
  22. * @throws Error when key is not an attribute of Object or is not an object itself
  23. */
  24. mergeKeyWithParent(object, property) {
  25. if (object[property] === undefined) {
  26. return object;
  27. }
  28. if (typeof object[property] !== 'object') {
  29. return object;
  30. }
  31. const newObject = $.extend({}, object, object[property]);
  32. delete newObject[property];
  33. return newObject;
  34. },
  35. /*
  36. * Group all objects of a collection by the value of the specified attribute
  37. * If the attribute is a string, use the lowercase form.
  38. *
  39. * eg.
  40. * groupBy([
  41. * {name: 'Tim', category: 'dev'},
  42. * {name: 'Vincent', category: 'dev'},
  43. * {name: 'Ben', category: 'sales'},
  44. * {name: 'Jeremy', category: 'sales'},
  45. * {name: 'AlexS', category: 'dev'},
  46. * {name: 'AlexK', category: 'sales'}
  47. * ], 'category');
  48. * =>
  49. * {
  50. * 'devs': [
  51. * {name: 'Tim', category: 'dev'},
  52. * {name: 'Vincent', category: 'dev'},
  53. * {name: 'AlexS', category: 'dev'}
  54. * ],
  55. * 'sales': [
  56. * {name: 'Ben', category: 'sales'},
  57. * {name: 'Jeremy', category: 'sales'},
  58. * {name: 'AlexK', category: 'sales'}
  59. * ]
  60. * }
  61. * @param {array} collection Array of objects to group
  62. * @param {String} property The attribute on which apply the grouping
  63. * @return {array}
  64. * @throws Error when one of the element does not have the specified property
  65. */
  66. groupBy(collection, property) {
  67. const newCollection = {};
  68. $.each(collection, (index, item) => {
  69. if (item[property] === undefined) {
  70. throw new Error(`[groupBy]: Object has no key ${property}`);
  71. }
  72. let key = item[property];
  73. if (typeof key === 'string') {
  74. key = key.toLowerCase();
  75. }
  76. // fix #171 the given data type of docsearch hits might be conflict with the properties of the native Object,
  77. // such as the constructor, so we need to do this check.
  78. if (!Object.prototype.hasOwnProperty.call(newCollection, key)) {
  79. newCollection[key] = [];
  80. }
  81. newCollection[key].push(item);
  82. });
  83. return newCollection;
  84. },
  85. /*
  86. * Return an array of all the values of the specified object
  87. * eg.
  88. * values({
  89. * foo: 42,
  90. * bar: true,
  91. * baz: 'yep'
  92. * })
  93. * =>
  94. * [42, true, yep]
  95. * @param {object} object Object to extract values from
  96. * @return {array}
  97. */
  98. values(object) {
  99. return Object.keys(object).map(key => object[key]);
  100. },
  101. /*
  102. * Flattens an array
  103. * eg.
  104. * flatten([1, 2, [3, 4], [5, 6]])
  105. * =>
  106. * [1, 2, 3, 4, 5, 6]
  107. * @param {array} array Array to flatten
  108. * @return {array}
  109. */
  110. flatten(array) {
  111. const results = [];
  112. array.forEach(value => {
  113. if (!Array.isArray(value)) {
  114. results.push(value);
  115. return;
  116. }
  117. value.forEach(subvalue => {
  118. results.push(subvalue);
  119. });
  120. });
  121. return results;
  122. },
  123. /*
  124. * Flatten all values of an object into an array, marking each first element of
  125. * each group with a specific flag
  126. * eg.
  127. * flattenAndFlagFirst({
  128. * 'devs': [
  129. * {name: 'Tim', category: 'dev'},
  130. * {name: 'Vincent', category: 'dev'},
  131. * {name: 'AlexS', category: 'dev'}
  132. * ],
  133. * 'sales': [
  134. * {name: 'Ben', category: 'sales'},
  135. * {name: 'Jeremy', category: 'sales'},
  136. * {name: 'AlexK', category: 'sales'}
  137. * ]
  138. * , 'isTop');
  139. * =>
  140. * [
  141. * {name: 'Tim', category: 'dev', isTop: true},
  142. * {name: 'Vincent', category: 'dev', isTop: false},
  143. * {name: 'AlexS', category: 'dev', isTop: false},
  144. * {name: 'Ben', category: 'sales', isTop: true},
  145. * {name: 'Jeremy', category: 'sales', isTop: false},
  146. * {name: 'AlexK', category: 'sales', isTop: false}
  147. * ]
  148. * @param {object} object Object to flatten
  149. * @param {string} flag Flag to set to true on first element of each group
  150. * @return {array}
  151. */
  152. flattenAndFlagFirst(object, flag) {
  153. const values = this.values(object).map(collection =>
  154. collection.map((item, index) => {
  155. // eslint-disable-next-line no-param-reassign
  156. item[flag] = index === 0;
  157. return item;
  158. })
  159. );
  160. return this.flatten(values);
  161. },
  162. /*
  163. * Removes all empty strings, null, false and undefined elements array
  164. * eg.
  165. * compact([42, false, null, undefined, '', [], 'foo']);
  166. * =>
  167. * [42, [], 'foo']
  168. * @param {array} array Array to compact
  169. * @return {array}
  170. */
  171. compact(array) {
  172. const results = [];
  173. array.forEach(value => {
  174. if (!value) {
  175. return;
  176. }
  177. results.push(value);
  178. });
  179. return results;
  180. },
  181. /*
  182. * Returns the highlighted value of the specified key in the specified object.
  183. * If no highlighted value is available, will return the key value directly
  184. * eg.
  185. * getHighlightedValue({
  186. * _highlightResult: {
  187. * text: {
  188. * value: '<mark>foo</mark>'
  189. * }
  190. * },
  191. * text: 'foo'
  192. * }, 'text');
  193. * =>
  194. * '<mark>foo</mark>'
  195. * @param {object} object Hit object returned by the Algolia API
  196. * @param {string} property Object key to look for
  197. * @return {string}
  198. **/
  199. getHighlightedValue(object, property) {
  200. if (
  201. object._highlightResult &&
  202. object._highlightResult.hierarchy_camel &&
  203. object._highlightResult.hierarchy_camel[property] &&
  204. object._highlightResult.hierarchy_camel[property].matchLevel &&
  205. object._highlightResult.hierarchy_camel[property].matchLevel !== 'none' &&
  206. object._highlightResult.hierarchy_camel[property].value
  207. ) {
  208. return object._highlightResult.hierarchy_camel[property].value;
  209. }
  210. if (
  211. object._highlightResult &&
  212. object._highlightResult &&
  213. object._highlightResult[property] &&
  214. object._highlightResult[property].value
  215. ) {
  216. return object._highlightResult[property].value;
  217. }
  218. return object[property];
  219. },
  220. /*
  221. * Returns the snippeted value of the specified key in the specified object.
  222. * If no highlighted value is available, will return the key value directly.
  223. * Will add starting and ending ellipsis (…) if we detect that a sentence is
  224. * incomplete
  225. * eg.
  226. * getSnippetedValue({
  227. * _snippetResult: {
  228. * text: {
  229. * value: '<mark>This is an unfinished sentence</mark>'
  230. * }
  231. * },
  232. * text: 'This is an unfinished sentence'
  233. * }, 'text');
  234. * =>
  235. * '<mark>This is an unfinished sentence</mark>…'
  236. * @param {object} object Hit object returned by the Algolia API
  237. * @param {string} property Object key to look for
  238. * @return {string}
  239. **/
  240. getSnippetedValue(object, property) {
  241. if (
  242. !object._snippetResult ||
  243. !object._snippetResult[property] ||
  244. !object._snippetResult[property].value
  245. ) {
  246. return object[property];
  247. }
  248. let snippet = object._snippetResult[property].value;
  249. if (snippet[0] !== snippet[0].toUpperCase()) {
  250. snippet = `…${snippet}`;
  251. }
  252. if (['.', '!', '?'].indexOf(snippet[snippet.length - 1]) === -1) {
  253. snippet = `${snippet}…`;
  254. }
  255. return snippet;
  256. },
  257. /*
  258. * Deep clone an object.
  259. * Note: This will not clone functions and dates
  260. * @param {object} object Object to clone
  261. * @return {object}
  262. */
  263. deepClone(object) {
  264. return JSON.parse(JSON.stringify(object));
  265. },
  266. };
  267. export default utils;