You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

repo-home.js 5.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. import $ from 'jquery';
  2. import {stripTags} from '../utils.js';
  3. import {hideElem, showElem} from '../utils/dom.js';
  4. import {POST} from '../modules/fetch.js';
  5. const {appSubUrl} = window.config;
  6. export function initRepoTopicBar() {
  7. const $mgrBtn = $('#manage_topic');
  8. if (!$mgrBtn.length) return;
  9. const $editDiv = $('#topic_edit');
  10. const $viewDiv = $('#repo-topics');
  11. const $saveBtn = $('#save_topic');
  12. const $topicDropdown = $('#topic_edit .dropdown');
  13. const $topicForm = $editDiv; // the old logic, $editDiv is topicForm
  14. const $topicDropdownSearch = $topicDropdown.find('input.search');
  15. const topicPrompts = {
  16. countPrompt: $topicDropdown.attr('data-text-count-prompt'),
  17. formatPrompt: $topicDropdown.attr('data-text-format-prompt'),
  18. };
  19. $mgrBtn.on('click', () => {
  20. hideElem($viewDiv);
  21. showElem($editDiv);
  22. $topicDropdownSearch.trigger('focus');
  23. });
  24. $('#cancel_topic_edit').on('click', () => {
  25. hideElem($editDiv);
  26. showElem($viewDiv);
  27. $mgrBtn.trigger('focus');
  28. });
  29. $saveBtn.on('click', async () => {
  30. const topics = $('input[name=topics]').val();
  31. const data = new FormData();
  32. data.append('topics', topics);
  33. const response = await POST($saveBtn.attr('data-link'), {data});
  34. if (response.ok) {
  35. const responseData = await response.json();
  36. if (responseData.status === 'ok') {
  37. $viewDiv.children('.topic').remove();
  38. if (topics.length) {
  39. const topicArray = topics.split(',');
  40. topicArray.sort();
  41. for (const topic of topicArray) {
  42. const $link = $('<a class="ui repo-topic large label topic gt-m-0"></a>');
  43. $link.attr('href', `${appSubUrl}/explore/repos?q=${encodeURIComponent(topic)}&topic=1`);
  44. $link.text(topic);
  45. $link.insertBefore($mgrBtn); // insert all new topics before manage button
  46. }
  47. }
  48. hideElem($editDiv);
  49. showElem($viewDiv);
  50. }
  51. } else if (response.status === 422) {
  52. const responseData = await response.json();
  53. if (responseData.invalidTopics.length > 0) {
  54. topicPrompts.formatPrompt = responseData.message;
  55. const {invalidTopics} = responseData;
  56. const $topicLabels = $topicDropdown.children('a.ui.label');
  57. for (const [index, value] of topics.split(',').entries()) {
  58. if (invalidTopics.includes(value)) {
  59. $topicLabels.eq(index).removeClass('green').addClass('red');
  60. }
  61. }
  62. } else {
  63. topicPrompts.countPrompt = responseData.message;
  64. }
  65. }
  66. // Always validate the form
  67. $topicForm.form('validate form');
  68. });
  69. $topicDropdown.dropdown({
  70. allowAdditions: true,
  71. forceSelection: false,
  72. fullTextSearch: 'exact',
  73. fields: {name: 'description', value: 'data-value'},
  74. saveRemoteData: false,
  75. label: {
  76. transition: 'horizontal flip',
  77. duration: 200,
  78. variation: false,
  79. },
  80. apiSettings: {
  81. url: `${appSubUrl}/explore/topics/search?q={query}`,
  82. throttle: 500,
  83. cache: false,
  84. onResponse(res) {
  85. const formattedResponse = {
  86. success: false,
  87. results: [],
  88. };
  89. const query = stripTags(this.urlData.query.trim());
  90. let found_query = false;
  91. const current_topics = [];
  92. $topicDropdown.find('a.label.visible').each((_, el) => {
  93. current_topics.push(el.getAttribute('data-value'));
  94. });
  95. if (res.topics) {
  96. let found = false;
  97. for (let i = 0; i < res.topics.length; i++) {
  98. // skip currently added tags
  99. if (current_topics.includes(res.topics[i].topic_name)) {
  100. continue;
  101. }
  102. if (res.topics[i].topic_name.toLowerCase() === query.toLowerCase()) {
  103. found_query = true;
  104. }
  105. formattedResponse.results.push({description: res.topics[i].topic_name, 'data-value': res.topics[i].topic_name});
  106. found = true;
  107. }
  108. formattedResponse.success = found;
  109. }
  110. if (query.length > 0 && !found_query) {
  111. formattedResponse.success = true;
  112. formattedResponse.results.unshift({description: query, 'data-value': query});
  113. } else if (query.length > 0 && found_query) {
  114. formattedResponse.results.sort((a, b) => {
  115. if (a.description.toLowerCase() === query.toLowerCase()) return -1;
  116. if (b.description.toLowerCase() === query.toLowerCase()) return 1;
  117. if (a.description > b.description) return -1;
  118. if (a.description < b.description) return 1;
  119. return 0;
  120. });
  121. }
  122. return formattedResponse;
  123. },
  124. },
  125. onLabelCreate(value) {
  126. value = value.toLowerCase().trim();
  127. this.attr('data-value', value).contents().first().replaceWith(value);
  128. return $(this);
  129. },
  130. onAdd(addedValue, _addedText, $addedChoice) {
  131. addedValue = addedValue.toLowerCase().trim();
  132. $($addedChoice).attr('data-value', addedValue);
  133. $($addedChoice).attr('data-text', addedValue);
  134. },
  135. });
  136. $.fn.form.settings.rules.validateTopic = function (_values, regExp) {
  137. const $topics = $topicDropdown.children('a.ui.label');
  138. const status = $topics.length === 0 || $topics.last().attr('data-value').match(regExp);
  139. if (!status) {
  140. $topics.last().removeClass('green').addClass('red');
  141. }
  142. return status && $topicDropdown.children('a.ui.label.red').length === 0;
  143. };
  144. $topicForm.form({
  145. on: 'change',
  146. inline: true,
  147. fields: {
  148. topics: {
  149. identifier: 'topics',
  150. rules: [
  151. {
  152. type: 'validateTopic',
  153. value: /^\s*[a-z0-9][-.a-z0-9]{0,35}\s*$/,
  154. prompt: topicPrompts.formatPrompt,
  155. },
  156. {
  157. type: 'maxCount[25]',
  158. prompt: topicPrompts.countPrompt,
  159. },
  160. ],
  161. },
  162. },
  163. });
  164. }