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.6KB

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