elabftw/elabftw

View on GitHub
src/ts/tags.ts

Summary

Maintainability
D
2 days
Test Coverage
/**
 * @author Nicolas CARPi <nico-git@deltablot.email>
 * @copyright 2012 Nicolas CARPi
 * @see https://www.elabftw.net Official website
 * @license AGPL-3.0
 * @package elabftw
 */
import 'jquery-ui/ui/widgets/autocomplete';
import { Malle } from '@deltablot/malle';
import FavTag from './FavTag.class';
import i18next from 'i18next';
import { addAutocompleteToTagInputs, getCheckedBoxes, notif, reloadEntitiesShow, getEntity, reloadElements } from './misc';
import { Action, Model } from './interfaces';
import { Api } from './Apiv2.class';

document.addEventListener('DOMContentLoaded', () => {
  const entity = getEntity();
  const ApiC = new Api();

  // CREATE TAG
  const createTag = (el: HTMLInputElement): void => {
    if (!el.value) {
      return;
    }
    ApiC.post(`${entity.type}/${entity.id}/${Model.Tag}`, {tag: el.value}).then(() => {
      // instead of reloading the full "tags div", reload only parts which contains tags
      // so we don't need to reload the input (and need to re-apply listeners)
      reloadElements([`tags_div_currenttags_${entity.id}`, `tags_div_suggestedtags_${entity.id}`]);
      el.value = '';
    });
  };

  if (document.querySelector('.createTagInput')) {
    document.querySelector('.createTagInput').addEventListener('blur', event => {
      createTag(event.target as HTMLInputElement);
    });

    document.querySelector('.createTagInput').addEventListener('keyup', event => {
      if ((event as KeyboardEvent).code === 'Enter') {
        createTag(event.target as HTMLInputElement);
      }
    });
  }
  // END CREATE TAG

  // CREATE TAG MULTIPLE
  const createTagMultiple = (el: HTMLInputElement): void => {
    if (!el.value) {
      return;
    }
    // get the ids of selected entities
    const checked = getCheckedBoxes();
    if (checked.length === 0) {
      const json = {
        'msg': 'Nothing selected!',
        'res': false,
      };
      notif(json);
      return;
    }

    // loop over it and add tags
    const results = [];
    checked.forEach(checkBox => {
      results.push(ApiC.post(`${entity.type}/${checkBox['id']}/${Model.Tag}`, {tag: el.value}));
    });

    Promise.all(results).then(() => {
      reloadEntitiesShow();
      el.value = '';
    });
  };

  if (document.querySelector('.createTagInputMultiple')) {
    document.querySelector('.createTagInputMultiple').addEventListener('blur', event => {
      createTagMultiple(event.target as HTMLInputElement);
    });

    document.querySelector('.createTagInputMultiple').addEventListener('keyup', event => {
      if ((event as KeyboardEvent).code === 'Enter') {
        createTagMultiple(event.target as HTMLInputElement);
      }
    });
  }
  // END CREATE TAG MULTIPLE

  // CREATE FAVORITE TAG
  const createTagFavorite = (el: HTMLInputElement): void => {
    if (!el.value) {
      return;
    }
    (new FavTag()).create(el.value).then(() => {
      reloadElements(['favtagsTagsDiv']);
      el.value = '';
    });
  };

  if (document.getElementById('createFavTagInput')) {
    document.getElementById('createFavTagInput').addEventListener('blur', event => {
      createTagFavorite(event.target as HTMLInputElement);
    });

    document.getElementById('createFavTagInput').addEventListener('keyup', event => {
      if ((event as KeyboardEvent).code === 'Enter') {
        createTagFavorite(event.target as HTMLInputElement);
      }
    });
  }
  // END CREATE FAVORITE TAG

  // AUTOCOMPLETE
  addAutocompleteToTagInputs();
  if (document.getElementById('favtagsPanel')) {
    new MutationObserver(() => addAutocompleteToTagInputs())
      .observe(document.getElementById('favtagsPanel'), {childList: true, subtree: true});
  }

  // make the tag editable (on admin page)
  const malleableTags = new Malle({
    cancel : i18next.t('cancel'),
    cancelClasses: ['button', 'btn', 'btn-danger', 'ml-1'],
    inputClasses: ['form-control'],
    formClasses: ['d-inline-flex'],
    fun: async (value, original) => {
      const resp = await ApiC.patch(`${Model.TeamTags}/${original.dataset.id}`, {'action': Action.UpdateTag, 'tag': value});
      const json = await resp.json();
      // the response contains all the tags, so we need to find the correct one to display the updated value
      return json.find((tag: Record<string, string|number>) => tag.id === parseInt(original.dataset.id, 10)).tag;
    },
    listenOn: '.tag.editable',
    returnedValueIsTrustedHtml: false,
    tooltip: i18next.t('click-to-edit'),
    submit : i18next.t('save'),
    submitClasses: ['button', 'btn', 'btn-primary', 'ml-1'],
  }).listen();

  if (document.getElementById('tagMgrDiv')) {
    new MutationObserver(() => {
      malleableTags.listen();
    }).observe(document.getElementById('tagMgrDiv'), {childList: true});
  }

  // MAIN ACTION LISTENER
  document.querySelector('.real-container').addEventListener('click', event => {
    const el = (event.target as HTMLElement);
    // DEDUPLICATE (from admin panel/tag manager)
    if (el.matches('[data-action="deduplicate-tag"]')) {
      ApiC.patch(`${Model.TeamTags}`, {'action': Action.Deduplicate}).then(() => reloadElements(['tagMgrDiv']));
    // UNREFERENCE (remove link between tag and entity)
    } else if (el.matches('[data-action="unreference-tag"]')) {
      if (confirm(i18next.t('tag-delete-warning'))) {
        ApiC.patch(`${entity.type}/${entity.id}/${Model.Tag}/${el.dataset.tagid}`, {'action': Action.Unreference}).then(() => reloadElements([`tags_div_currenttags_${entity.id}`, `tags_div_suggestedtags_${entity.id}`]));
      }
    // ADD SUGGESTED TAGS
    } else if (el.matches('[data-action="add-suggested-tag"]')) {
      ApiC.post(`${entity.type}/${entity.id}/${Model.Tag}/${el.dataset.tagid}`, {'action': Action.Add, 'tag': el.innerText}).then(() => reloadElements([`tags_div_currenttags_${entity.id}`, `tags_div_suggestedtags_${entity.id}`]));
    // DESTROY (from admin panel/tag manager)
    } else if (el.matches('[data-action="destroy-tag"]')) {
      if (confirm(i18next.t('tag-delete-warning'))) {
        ApiC.delete(`${Model.TeamTags}/${el.dataset.tagid}`).then(() => el.parentElement.parentElement.remove());
      }
    }
  });
});