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-legacy.js 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613
  1. import $ from 'jquery';
  2. import {createCommentEasyMDE, getAttachedEasyMDE} from './comp/EasyMDE.js';
  3. import {initCompMarkupContentPreviewTab} from './comp/MarkupContentPreview.js';
  4. import {initEasyMDEImagePaste} from './comp/ImagePaste.js';
  5. import {
  6. initRepoIssueBranchSelect, initRepoIssueCodeCommentCancel, initRepoIssueCommentDelete,
  7. initRepoIssueComments, initRepoIssueDependencyDelete, initRepoIssueReferenceIssue,
  8. initRepoIssueStatusButton, initRepoIssueTitleEdit, initRepoIssueWipToggle,
  9. initRepoPullRequestUpdate, updateIssuesMeta,
  10. } from './repo-issue.js';
  11. import {initUnicodeEscapeButton} from './repo-unicode-escape.js';
  12. import {svg} from '../svg.js';
  13. import {htmlEscape} from 'escape-goat';
  14. import {initRepoBranchTagDropdown} from '../components/RepoBranchTagDropdown.js';
  15. import {
  16. initRepoCloneLink, initRepoCommonBranchOrTagDropdown, initRepoCommonFilterSearchDropdown,
  17. initRepoCommonLanguageStats,
  18. } from './repo-common.js';
  19. import {initCitationFileCopyContent} from './citation.js';
  20. import {initCompLabelEdit} from './comp/LabelEdit.js';
  21. import {initRepoDiffConversationNav} from './repo-diff.js';
  22. import {attachTribute} from './tribute.js';
  23. import {createDropzone} from './dropzone.js';
  24. import {initCommentContent, initMarkupContent} from '../markup/content.js';
  25. import {initCompReactionSelector} from './comp/ReactionSelector.js';
  26. import {initRepoSettingBranches} from './repo-settings.js';
  27. import {initRepoPullRequestMergeForm} from './repo-issue-pr-form.js';
  28. import {hideElem, showElem} from '../utils/dom.js';
  29. const {csrfToken} = window.config;
  30. export function initRepoCommentForm() {
  31. const $commentForm = $('.comment.form');
  32. if ($commentForm.length === 0) {
  33. return;
  34. }
  35. function initBranchSelector() {
  36. const $selectBranch = $('.ui.select-branch');
  37. const $branchMenu = $selectBranch.find('.reference-list-menu');
  38. const $isNewIssue = $branchMenu.hasClass('new-issue');
  39. $branchMenu.find('.item:not(.no-select)').click(function () {
  40. const selectedValue = $(this).data('id');
  41. const editMode = $('#editing_mode').val();
  42. $($(this).data('id-selector')).val(selectedValue);
  43. if ($isNewIssue) {
  44. $selectBranch.find('.ui .branch-name').text($(this).data('name'));
  45. return;
  46. }
  47. if (editMode === 'true') {
  48. const form = $('#update_issueref_form');
  49. $.post(form.attr('action'), {_csrf: csrfToken, ref: selectedValue}, () => window.location.reload());
  50. } else if (editMode === '') {
  51. $selectBranch.find('.ui .branch-name').text(selectedValue);
  52. }
  53. });
  54. $selectBranch.find('.reference.column').on('click', function () {
  55. hideElem($selectBranch.find('.scrolling.reference-list-menu'));
  56. $selectBranch.find('.reference .text').removeClass('black');
  57. showElem($($(this).data('target')));
  58. $(this).find('.text').addClass('black');
  59. return false;
  60. });
  61. }
  62. (async () => {
  63. for (const textarea of $commentForm.find('textarea:not(.review-textarea, .no-easymde)')) {
  64. // Don't initialize EasyMDE for the dormant #edit-content-form
  65. if (textarea.closest('#edit-content-form')) {
  66. continue;
  67. }
  68. const easyMDE = await createCommentEasyMDE(textarea);
  69. initEasyMDEImagePaste(easyMDE, $commentForm.find('.dropzone'));
  70. }
  71. })();
  72. initBranchSelector();
  73. initCompMarkupContentPreviewTab($commentForm);
  74. // List submits
  75. function initListSubmits(selector, outerSelector) {
  76. const $list = $(`.ui.${outerSelector}.list`);
  77. const $noSelect = $list.find('.no-select');
  78. const $listMenu = $(`.${selector} .menu`);
  79. let hasUpdateAction = $listMenu.data('action') === 'update';
  80. const items = {};
  81. $(`.${selector}`).dropdown('setting', 'onHide', () => {
  82. hasUpdateAction = $listMenu.data('action') === 'update'; // Update the var
  83. if (hasUpdateAction) {
  84. // TODO: Add batch functionality and make this 1 network request.
  85. (async function() {
  86. for (const [elementId, item] of Object.entries(items)) {
  87. await updateIssuesMeta(
  88. item['update-url'],
  89. item.action,
  90. item['issue-id'],
  91. elementId,
  92. );
  93. }
  94. window.location.reload();
  95. })();
  96. }
  97. });
  98. $listMenu.find('.item:not(.no-select)').on('click', function (e) {
  99. e.preventDefault();
  100. if ($(this).hasClass('ban-change')) {
  101. return false;
  102. }
  103. hasUpdateAction = $listMenu.data('action') === 'update'; // Update the var
  104. const clickedItem = $(this);
  105. const scope = $(this).attr('data-scope');
  106. const canRemoveScope = e.altKey;
  107. $(this).parent().find('.item').each(function () {
  108. if (scope) {
  109. // Enable only clicked item for scoped labels
  110. if ($(this).attr('data-scope') !== scope) {
  111. return true;
  112. }
  113. if ($(this).is(clickedItem)) {
  114. if (!canRemoveScope && $(this).hasClass('checked')) {
  115. return true;
  116. }
  117. } else if (!$(this).hasClass('checked')) {
  118. return true;
  119. }
  120. } else if (!$(this).is(clickedItem)) {
  121. // Toggle for other labels
  122. return true;
  123. }
  124. if ($(this).hasClass('checked')) {
  125. $(this).removeClass('checked');
  126. $(this).find('.octicon-check').addClass('invisible');
  127. if (hasUpdateAction) {
  128. if (!($(this).data('id') in items)) {
  129. items[$(this).data('id')] = {
  130. 'update-url': $listMenu.data('update-url'),
  131. action: 'detach',
  132. 'issue-id': $listMenu.data('issue-id'),
  133. };
  134. } else {
  135. delete items[$(this).data('id')];
  136. }
  137. }
  138. } else {
  139. $(this).addClass('checked');
  140. $(this).find('.octicon-check').removeClass('invisible');
  141. if (hasUpdateAction) {
  142. if (!($(this).data('id') in items)) {
  143. items[$(this).data('id')] = {
  144. 'update-url': $listMenu.data('update-url'),
  145. action: 'attach',
  146. 'issue-id': $listMenu.data('issue-id'),
  147. };
  148. } else {
  149. delete items[$(this).data('id')];
  150. }
  151. }
  152. }
  153. });
  154. // TODO: Which thing should be done for choosing review requests
  155. // to make chosen items be shown on time here?
  156. if (selector === 'select-reviewers-modify' || selector === 'select-assignees-modify') {
  157. return false;
  158. }
  159. const listIds = [];
  160. $(this).parent().find('.item').each(function () {
  161. if ($(this).hasClass('checked')) {
  162. listIds.push($(this).data('id'));
  163. $($(this).data('id-selector')).removeClass('gt-hidden');
  164. } else {
  165. $($(this).data('id-selector')).addClass('gt-hidden');
  166. }
  167. });
  168. if (listIds.length === 0) {
  169. $noSelect.removeClass('gt-hidden');
  170. } else {
  171. $noSelect.addClass('gt-hidden');
  172. }
  173. $($(this).parent().data('id')).val(listIds.join(','));
  174. return false;
  175. });
  176. $listMenu.find('.no-select.item').on('click', function (e) {
  177. e.preventDefault();
  178. if (hasUpdateAction) {
  179. updateIssuesMeta(
  180. $listMenu.data('update-url'),
  181. 'clear',
  182. $listMenu.data('issue-id'),
  183. '',
  184. ).then(() => window.location.reload());
  185. }
  186. $(this).parent().find('.item').each(function () {
  187. $(this).removeClass('checked');
  188. $(this).find('.octicon').addClass('invisible');
  189. });
  190. if (selector === 'select-reviewers-modify' || selector === 'select-assignees-modify') {
  191. return false;
  192. }
  193. $list.find('.item').each(function () {
  194. $(this).addClass('gt-hidden');
  195. });
  196. $noSelect.removeClass('gt-hidden');
  197. $($(this).parent().data('id')).val('');
  198. });
  199. }
  200. // Init labels and assignees
  201. initListSubmits('select-label', 'labels');
  202. initListSubmits('select-assignees', 'assignees');
  203. initListSubmits('select-assignees-modify', 'assignees');
  204. initListSubmits('select-reviewers-modify', 'assignees');
  205. function selectItem(select_id, input_id) {
  206. const $menu = $(`${select_id} .menu`);
  207. const $list = $(`.ui${select_id}.list`);
  208. const hasUpdateAction = $menu.data('action') === 'update';
  209. $menu.find('.item:not(.no-select)').on('click', function () {
  210. $(this).parent().find('.item').each(function () {
  211. $(this).removeClass('selected active');
  212. });
  213. $(this).addClass('selected active');
  214. if (hasUpdateAction) {
  215. updateIssuesMeta(
  216. $menu.data('update-url'),
  217. '',
  218. $menu.data('issue-id'),
  219. $(this).data('id'),
  220. ).then(() => window.location.reload());
  221. }
  222. let icon = '';
  223. if (input_id === '#milestone_id') {
  224. icon = svg('octicon-milestone', 18, 'gt-mr-3');
  225. } else if (input_id === '#project_id') {
  226. icon = svg('octicon-project', 18, 'gt-mr-3');
  227. } else if (input_id === '#assignee_id') {
  228. icon = `<img class="ui avatar image gt-mr-3" src=${$(this).data('avatar')}>`;
  229. }
  230. $list.find('.selected').html(`
  231. <a class="item muted sidebar-item-link" href=${$(this).data('href')}>
  232. ${icon}
  233. ${htmlEscape($(this).text())}
  234. </a>
  235. `);
  236. $(`.ui${select_id}.list .no-select`).addClass('gt-hidden');
  237. $(input_id).val($(this).data('id'));
  238. });
  239. $menu.find('.no-select.item').on('click', function () {
  240. $(this).parent().find('.item:not(.no-select)').each(function () {
  241. $(this).removeClass('selected active');
  242. });
  243. if (hasUpdateAction) {
  244. updateIssuesMeta(
  245. $menu.data('update-url'),
  246. '',
  247. $menu.data('issue-id'),
  248. $(this).data('id'),
  249. ).then(() => window.location.reload());
  250. }
  251. $list.find('.selected').html('');
  252. $list.find('.no-select').removeClass('gt-hidden');
  253. $(input_id).val('');
  254. });
  255. }
  256. // Milestone, Assignee, Project
  257. selectItem('.select-project', '#project_id');
  258. selectItem('.select-milestone', '#milestone_id');
  259. selectItem('.select-assignee', '#assignee_id');
  260. }
  261. async function onEditContent(event) {
  262. event.preventDefault();
  263. $(this).closest('.dropdown').find('.menu').toggle('visible'); // eslint-disable-line
  264. const $segment = $(this).closest('.header').next();
  265. const $editContentZone = $segment.find('.edit-content-zone');
  266. const $renderContent = $segment.find('.render-content');
  267. const $rawContent = $segment.find('.raw-content');
  268. let $textarea;
  269. let easyMDE;
  270. // Setup new form
  271. if ($editContentZone.html().length === 0) {
  272. $editContentZone.html($('#edit-content-form').html());
  273. $textarea = $editContentZone.find('textarea');
  274. await attachTribute($textarea.get(), {mentions: true, emoji: true});
  275. let dz;
  276. const $dropzone = $editContentZone.find('.dropzone');
  277. if ($dropzone.length === 1) {
  278. $dropzone.data('saved', false);
  279. const fileUuidDict = {};
  280. dz = await createDropzone($dropzone[0], {
  281. url: $dropzone.data('upload-url'),
  282. headers: {'X-Csrf-Token': csrfToken},
  283. maxFiles: $dropzone.data('max-file'),
  284. maxFilesize: $dropzone.data('max-size'),
  285. acceptedFiles: (['*/*', ''].includes($dropzone.data('accepts'))) ? null : $dropzone.data('accepts'),
  286. addRemoveLinks: true,
  287. dictDefaultMessage: $dropzone.data('default-message'),
  288. dictInvalidFileType: $dropzone.data('invalid-input-type'),
  289. dictFileTooBig: $dropzone.data('file-too-big'),
  290. dictRemoveFile: $dropzone.data('remove-file'),
  291. timeout: 0,
  292. thumbnailMethod: 'contain',
  293. thumbnailWidth: 480,
  294. thumbnailHeight: 480,
  295. init() {
  296. this.on('success', (file, data) => {
  297. file.uuid = data.uuid;
  298. fileUuidDict[file.uuid] = {submitted: false};
  299. const input = $(`<input id="${data.uuid}" name="files" type="hidden">`).val(data.uuid);
  300. $dropzone.find('.files').append(input);
  301. });
  302. this.on('removedfile', (file) => {
  303. $(`#${file.uuid}`).remove();
  304. if ($dropzone.data('remove-url') && !fileUuidDict[file.uuid].submitted) {
  305. $.post($dropzone.data('remove-url'), {
  306. file: file.uuid,
  307. _csrf: csrfToken,
  308. });
  309. }
  310. });
  311. this.on('submit', () => {
  312. $.each(fileUuidDict, (fileUuid) => {
  313. fileUuidDict[fileUuid].submitted = true;
  314. });
  315. });
  316. this.on('reload', () => {
  317. $.getJSON($editContentZone.data('attachment-url'), (data) => {
  318. dz.removeAllFiles(true);
  319. $dropzone.find('.files').empty();
  320. $.each(data, function () {
  321. const imgSrc = `${$dropzone.data('link-url')}/${this.uuid}`;
  322. dz.emit('addedfile', this);
  323. dz.emit('thumbnail', this, imgSrc);
  324. dz.emit('complete', this);
  325. dz.files.push(this);
  326. fileUuidDict[this.uuid] = {submitted: true};
  327. $dropzone.find(`img[src='${imgSrc}']`).css('max-width', '100%');
  328. const input = $(`<input id="${this.uuid}" name="files" type="hidden">`).val(this.uuid);
  329. $dropzone.find('.files').append(input);
  330. });
  331. });
  332. });
  333. },
  334. });
  335. dz.emit('reload');
  336. }
  337. // Give new write/preview data-tab name to distinguish from others
  338. const $editContentForm = $editContentZone.find('.ui.comment.form');
  339. const $tabMenu = $editContentForm.find('.tabular.menu');
  340. $tabMenu.attr('data-write', $editContentZone.data('write'));
  341. $tabMenu.attr('data-preview', $editContentZone.data('preview'));
  342. $tabMenu.find('.write.item').attr('data-tab', $editContentZone.data('write'));
  343. $tabMenu.find('.preview.item').attr('data-tab', $editContentZone.data('preview'));
  344. $editContentForm.find('.write').attr('data-tab', $editContentZone.data('write'));
  345. $editContentForm.find('.preview').attr('data-tab', $editContentZone.data('preview'));
  346. easyMDE = await createCommentEasyMDE($textarea);
  347. initCompMarkupContentPreviewTab($editContentForm);
  348. initEasyMDEImagePaste(easyMDE, $dropzone);
  349. const $saveButton = $editContentZone.find('.save.button');
  350. $textarea.on('ce-quick-submit', () => {
  351. $saveButton.trigger('click');
  352. });
  353. $editContentZone.find('.cancel.button').on('click', () => {
  354. showElem($renderContent);
  355. hideElem($editContentZone);
  356. if (dz) {
  357. dz.emit('reload');
  358. }
  359. });
  360. $saveButton.on('click', () => {
  361. showElem($renderContent);
  362. hideElem($editContentZone);
  363. const $attachments = $dropzone.find('.files').find('[name=files]').map(function () {
  364. return $(this).val();
  365. }).get();
  366. $.post($editContentZone.data('update-url'), {
  367. _csrf: csrfToken,
  368. content: $textarea.val(),
  369. context: $editContentZone.data('context'),
  370. files: $attachments,
  371. }, (data) => {
  372. if (data.length === 0 || data.content.length === 0) {
  373. $renderContent.html($('#no-content').html());
  374. $rawContent.text('');
  375. } else {
  376. $renderContent.html(data.content);
  377. $rawContent.text($textarea.val());
  378. }
  379. const $content = $segment;
  380. if (!$content.find('.dropzone-attachments').length) {
  381. if (data.attachments !== '') {
  382. $content.append(`<div class="dropzone-attachments"></div>`);
  383. $content.find('.dropzone-attachments').replaceWith(data.attachments);
  384. }
  385. } else if (data.attachments === '') {
  386. $content.find('.dropzone-attachments').remove();
  387. } else {
  388. $content.find('.dropzone-attachments').replaceWith(data.attachments);
  389. }
  390. if (dz) {
  391. dz.emit('submit');
  392. dz.emit('reload');
  393. }
  394. initMarkupContent();
  395. initCommentContent();
  396. });
  397. });
  398. } else { // use existing form
  399. $textarea = $segment.find('textarea');
  400. easyMDE = getAttachedEasyMDE($textarea);
  401. }
  402. // Show write/preview tab and copy raw content as needed
  403. showElem($editContentZone);
  404. hideElem($renderContent);
  405. if ($textarea.val().length === 0) {
  406. $textarea.val($rawContent.text());
  407. easyMDE.value($rawContent.text());
  408. }
  409. requestAnimationFrame(() => {
  410. $textarea.focus();
  411. easyMDE.codemirror.focus();
  412. });
  413. }
  414. export function initRepository() {
  415. if ($('.repository').length === 0) {
  416. return;
  417. }
  418. // File list and commits
  419. if ($('.repository.file.list').length > 0 || $('.branch-dropdown').length > 0 ||
  420. $('.repository.commits').length > 0 || $('.repository.release').length > 0) {
  421. initRepoBranchTagDropdown('.choose.reference .dropdown');
  422. }
  423. // Wiki
  424. if ($('.repository.wiki.view').length > 0) {
  425. initRepoCommonFilterSearchDropdown('.choose.page .dropdown');
  426. }
  427. // Options
  428. if ($('.repository.settings.options').length > 0) {
  429. // Enable or select internal/external wiki system and issue tracker.
  430. $('.enable-system').on('change', function () {
  431. if (this.checked) {
  432. $($(this).data('target')).removeClass('disabled');
  433. if (!$(this).data('context')) $($(this).data('context')).addClass('disabled');
  434. } else {
  435. $($(this).data('target')).addClass('disabled');
  436. if (!$(this).data('context')) $($(this).data('context')).removeClass('disabled');
  437. }
  438. });
  439. $('.enable-system-radio').on('change', function () {
  440. if (this.value === 'false') {
  441. $($(this).data('target')).addClass('disabled');
  442. if ($(this).data('context') !== undefined) $($(this).data('context')).removeClass('disabled');
  443. } else if (this.value === 'true') {
  444. $($(this).data('target')).removeClass('disabled');
  445. if ($(this).data('context') !== undefined) $($(this).data('context')).addClass('disabled');
  446. }
  447. });
  448. const $trackerIssueStyleRadios = $('.js-tracker-issue-style');
  449. $trackerIssueStyleRadios.on('change input', () => {
  450. const checkedVal = $trackerIssueStyleRadios.filter(':checked').val();
  451. $('#tracker-issue-style-regex-box').toggleClass('disabled', checkedVal !== 'regexp');
  452. });
  453. }
  454. // Labels
  455. initCompLabelEdit('.repository.labels');
  456. // Milestones
  457. if ($('.repository.new.milestone').length > 0) {
  458. $('#clear-date').on('click', () => {
  459. $('#deadline').val('');
  460. return false;
  461. });
  462. }
  463. // Repo Creation
  464. if ($('.repository.new.repo').length > 0) {
  465. $('input[name="gitignores"], input[name="license"]').on('change', () => {
  466. const gitignores = $('input[name="gitignores"]').val();
  467. const license = $('input[name="license"]').val();
  468. if (gitignores || license) {
  469. $('input[name="auto_init"]').prop('checked', true);
  470. }
  471. });
  472. }
  473. // Compare or pull request
  474. const $repoDiff = $('.repository.diff');
  475. if ($repoDiff.length) {
  476. initRepoCommonBranchOrTagDropdown('.choose.branch .dropdown');
  477. initRepoCommonFilterSearchDropdown('.choose.branch .dropdown');
  478. }
  479. initRepoCloneLink();
  480. initCitationFileCopyContent();
  481. initRepoCommonLanguageStats();
  482. initRepoSettingBranches();
  483. // Issues
  484. if ($('.repository.view.issue').length > 0) {
  485. initRepoIssueCommentEdit();
  486. initRepoIssueBranchSelect();
  487. initRepoIssueTitleEdit();
  488. initRepoIssueWipToggle();
  489. initRepoIssueComments();
  490. initRepoDiffConversationNav();
  491. initRepoIssueReferenceIssue();
  492. initRepoIssueCommentDelete();
  493. initRepoIssueDependencyDelete();
  494. initRepoIssueCodeCommentCancel();
  495. initRepoIssueStatusButton();
  496. initRepoPullRequestUpdate();
  497. initCompReactionSelector();
  498. initRepoPullRequestMergeForm();
  499. }
  500. // Pull request
  501. const $repoComparePull = $('.repository.compare.pull');
  502. if ($repoComparePull.length > 0) {
  503. // show pull request form
  504. $repoComparePull.find('button.show-form').on('click', function (e) {
  505. e.preventDefault();
  506. hideElem($(this).parent());
  507. const $form = $repoComparePull.find('.pullrequest-form');
  508. showElem($form);
  509. $form.find('textarea.edit_area').each(function() {
  510. const easyMDE = getAttachedEasyMDE($(this));
  511. if (easyMDE) {
  512. easyMDE.codemirror.refresh();
  513. }
  514. });
  515. });
  516. }
  517. initUnicodeEscapeButton();
  518. }
  519. function initRepoIssueCommentEdit() {
  520. // Issue/PR Context Menus
  521. $('.comment-header-right .context-dropdown').dropdown({action: 'hide'});
  522. // Edit issue or comment content
  523. $(document).on('click', '.edit-content', onEditContent);
  524. // Quote reply
  525. $(document).on('click', '.quote-reply', function (event) {
  526. $(this).closest('.dropdown').find('.menu').toggle('visible'); // eslint-disable-line
  527. const target = $(this).data('target');
  528. const quote = $(`#${target}`).text().replace(/\n/g, '\n> ');
  529. const content = `> ${quote}\n\n`;
  530. let easyMDE;
  531. if ($(this).hasClass('quote-reply-diff')) {
  532. const $parent = $(this).closest('.comment-code-cloud');
  533. $parent.find('button.comment-form-reply').trigger('click');
  534. easyMDE = getAttachedEasyMDE($parent.find('[name="content"]'));
  535. } else {
  536. // for normal issue/comment page
  537. easyMDE = getAttachedEasyMDE($('#comment-form .edit_area'));
  538. }
  539. if (easyMDE) {
  540. if (easyMDE.value() !== '') {
  541. easyMDE.value(`${easyMDE.value()}\n\n${content}`);
  542. } else {
  543. easyMDE.value(`${content}`);
  544. }
  545. requestAnimationFrame(() => {
  546. easyMDE.codemirror.focus();
  547. easyMDE.codemirror.setCursor(easyMDE.codemirror.lineCount(), 0);
  548. });
  549. }
  550. event.preventDefault();
  551. });
  552. }