Greasy Fork is available in English.

Gitlab plus

Gitlab utils

// ==UserScript==
// @name         Gitlab plus
// @namespace    https://lukaszmical.pl/
// @version      2025-02-21
// @description  Gitlab utils
// @author       Łukasz Micał
// @match        https://gitlab.com/*
// @require      https://cdn.jsdelivr.net/combine/npm/[email protected]/dist/preact.min.umd.min.js,npm/[email protected]/hooks/dist/hooks.umd.min.js,npm/[email protected]/jsx-runtime/dist/jsxRuntime.umd.min.js
// @icon         https://www.google.com/s2/favicons?sz=64&domain=gitlab.com
// ==/UserScript==

// Vite helpers
const __defProp = Object.defineProperty;
const __defNormalProp = (obj, key, value) =>
  key in obj
    ? __defProp(obj, key, {
        enumerable: true,
        configurable: true,
        writable: true,
        value,
      })
    : (obj[key] = value);
const __publicField = (obj, key, value) =>
  __defNormalProp(obj, typeof key !== 'symbol' ? key + '' : key, value);

// App code
const { jsx, jsxs, Fragment } = this.jsxRuntime;
const { render } = this.preact;
const { useMemo, useState, useEffect, useRef, useCallback, useLayoutEffect } =
  this.preactHooks;

// libs/share/src/ui/GlobalStyle.ts
class GlobalStyle {
  static addStyle(key, styles) {
    const style =
      document.getElementById(key) ||
      (function () {
        const style22 = document.createElement('style');
        style22.id = key;
        document.head.appendChild(style22);
        return style22;
      })();
    style.textContent = styles;
  }
}

const style1 =
  '.glp-modal {\n  position: fixed;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  z-index: 99999;\n  display: none;\n  background: rgba(0, 0, 0, 0.6);\n  justify-content: center;\n  align-items: center;\n}\n\n.glp-modal.glp-modal-visible {\n  display: flex;\n}\n\n.glp-modal .glp-modal-content {\n  width: 700px;\n  max-width: 95vw;\n}\n\n.gl-new-dropdown-item.glp-active .gl-new-dropdown-item-content {\n  box-shadow: inset 0 0 0 2px var(--gl-focus-ring-outer-color), inset 0 0 0 3px var(--gl-focus-ring-inner-color), inset 0 0 0 1px var(--gl-focus-ring-inner-color);\n  background-color: var(--gl-dropdown-option-background-color-unselected-hover);\n  outline: none;\n}\n\n';
const style2 =
  '.glp-image-preview-modal {\n  position: fixed;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  top: 0;\n  left: 0;\n  right: 0;\n  bottom: 0;\n  background: rgba(0, 0, 0, 0.6);\n  visibility: hidden;\n  opacity: 0;\n  pointer-events: none;\n  z-index: 99999;\n}\n\n.glp-image-preview-modal.glp-modal-visible {\n  visibility: visible;\n  opacity: 1;\n  pointer-events: auto;\n}\n\n.glp-image-preview-modal .glp-modal-img {\n  max-width: 95%;\n  max-height: 95%;\n}\n\n.glp-image-preview-modal .glp-modal-close {\n  position: absolute;\n  z-index: 2;\n  top: 20px;\n  right: 20px;\n  color: black;\n  width: 40px;\n  height: 40px;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  background: white;\n  border-radius: 20px;\n  cursor: pointer;\n}\n\n';
const style3 =
  '.glp-preview-modal {\n  position: fixed;\n  display: flex;\n  background-color: var(--gl-background-color-default, var(--gl-color-neutral-0, #fff));\n  border: 1px solid var(--gl-border-color-default);\n  border-radius: .25rem;\n  width: 350px;\n  min-height: 200px;\n  z-index: 99999;\n  visibility: hidden;\n  top: 0;\n  left: 0;\n  opacity: 0;\n  transition: all .2s ease-out;\n  transition-property: visibility, opacity, transform;\n}\n\n.glp-preview-modal.glp-modal-visible {\n  visibility: visible;\n  opacity: 1;\n}\n \n.glp-preview-modal .glp-block {\n  padding: .4rem .5em;\n  border-bottom-style: solid;\n  border-bottom-color: var(--gl-border-color-subtle, var(--gl-color-neutral-50, #ececef));\n  border-bottom-width: 1px;\n  width: 100%;\n}\n\n\n.glp-preview-modal * {\n  max-width: 100%;\n}\n';

// apps/gitlab-plus/src/styles/index.ts
GlobalStyle.addStyle('glp-style', [style1, style2, style3].join('\n'));

// libs/share/src/store/Store.ts
class Store {
  constructor(key) {
    this.key = key;
  }

  decode(val) {
    return JSON.parse(val);
  }

  encode(val) {
    return JSON.stringify(val);
  }

  get(defaultValue = void 0) {
    try {
      const data = localStorage.getItem(this.key);
      if (data) {
        return this.decode(data);
      }
      return defaultValue;
    } catch (e) {
      return defaultValue;
    }
  }

  remove() {
    localStorage.removeItem(this.key);
  }

  set(value) {
    try {
      localStorage.setItem(this.key, this.encode(value));
    } catch (e) {}
  }
}

// apps/gitlab-plus/src/services/ServiceName.ts
var ServiceName = ((ServiceName2) => {
  ServiceName2['ClearCacheService'] = 'ClearCacheService';
  ServiceName2['CreateChildIssue'] = 'CreateChildIssue';
  ServiceName2['CreateRelatedIssue'] = 'CreateRelatedIssue';
  ServiceName2['EpicPreview'] = 'EpicPreview';
  ServiceName2['ImagePreview'] = 'ImagePreview';
  ServiceName2['IssuePreview'] = 'IssuePreview';
  ServiceName2['MrPreview'] = 'MrPreview';
  ServiceName2['RelatedIssueAutocomplete'] = 'RelatedIssueAutocomplete';
  ServiceName2['RelatedIssuesLabelStatus'] = 'RelatedIssuesLabelStatus';
  ServiceName2['SortIssue'] = 'SortIssue';
  ServiceName2['UserSettings'] = 'UserSettings';
  return ServiceName2;
})(ServiceName || {});
const servicesConfig = {
  ['ClearCacheService']: { label: 'Clear cache', required: true },
  ['CreateChildIssue']: {
    label: 'Create child issue form on epic page',
  },
  ['CreateRelatedIssue']: {
    label: 'Create related issue form on issue page',
  },
  ['EpicPreview']: { label: 'Epic preview modal' },
  ['ImagePreview']: { label: 'Image preview modal' },
  ['IssuePreview']: { label: 'Issue preview modal' },
  ['MrPreview']: { label: 'Merge request preview modal' },
  ['RelatedIssueAutocomplete']: {
    label: 'Related issue autocomplete in related issues input',
  },
  ['RelatedIssuesLabelStatus']: {
    label: 'Label status in related issues list items',
  },
  ['SortIssue']: {
    experimental: true,
    label: 'Sort issues in board',
  },
  ['UserSettings']: { label: 'User settings', required: true },
};

// apps/gitlab-plus/src/components/user-settings/UserSettingsStore.ts
class UserSettingsStore {
  constructor() {
    __publicField(this, 'settings', {});
    __publicField(this, 'store', new Store('gitlab-plus-settings'));
    this.load();
  }

  isActive(name2) {
    if (!(name2 in servicesConfig)) {
      return false;
    }
    if (servicesConfig[name2].required) {
      return true;
    }
    if (servicesConfig[name2].experimental) {
      return this.getItem(name2, false);
    }
    return this.getItem(name2, true);
  }

  setIsActive(name2, value) {
    this.setItem(name2, value);
  }

  getItem(key, defaultValue) {
    if (this.settings[key] === void 0) {
      return defaultValue;
    }
    return this.settings[key];
  }

  load() {
    this.settings = this.store.get() || {};
  }

  persist() {
    this.store.set(this.settings);
  }

  setItem(key, value) {
    this.settings[key] = value;
    this.persist();
  }
}

const userSettingsStore = new UserSettingsStore();

// libs/share/src/store/Cache.ts
class Cache {
  constructor(prefix) {
    this.prefix = prefix;
  }

  clearInvalid() {
    for (const key in localStorage) {
      if (key.startsWith(this.prefix) && !this.isValid(this.getItem(key))) {
        localStorage.removeItem(key);
      }
    }
  }

  expirationDate(minutes) {
    if (typeof minutes === 'string') {
      return minutes;
    }
    const time = new Date();
    time.setMinutes(time.getMinutes() + minutes);
    return time;
  }

  get(key) {
    try {
      const data = this.getItem(this.key(key));
      if (this.isValid(data)) {
        return data.value;
      }
    } catch (e) {
      return void 0;
    }
    return void 0;
  }

  key(key) {
    return `${this.prefix}${key}`;
  }

  set(key, value, minutes) {
    localStorage.setItem(
      this.key(key),
      JSON.stringify({
        expirationDate: this.expirationDate(minutes),
        value,
      })
    );
  }

  getItem(key) {
    try {
      return JSON.parse(localStorage.getItem(key) || '');
    } catch (e) {
      return void 0;
    }
  }

  isValid(item) {
    if (item) {
      return (
        item.expirationDate === 'lifetime' ||
        new Date(item.expirationDate) > new Date()
      );
    }
    return false;
  }
}

// apps/gitlab-plus/src/services/BaseService.ts
class BaseService {
  root(className, parent, usePrepend = false) {
    const root = document.createElement('div');
    root.classList.add(className);
    if (parent) {
      parent[usePrepend ? 'prepend' : 'append'](root);
    }
    return root;
  }

  rootBody(className) {
    return this.root(className, document.body);
  }
}

// apps/gitlab-plus/src/services/ClearCacheService.ts
class ClearCacheService extends BaseService {
  constructor() {
    super(...arguments);
    __publicField(this, 'name', ServiceName.ClearCacheService);
    __publicField(this, 'cache', new Cache('glp-'));
  }

  init() {
    this.cache.clearInvalid();
    window.setInterval(this.cache.clearInvalid.bind(this.cache), 60 * 1e3);
  }
}

// libs/share/src/utils/clsx.ts
function clsx(...args) {
  return args
    .map((item) => {
      if (!item) {
        return '';
      }
      if (typeof item === 'string') {
        return item;
      }
      if (Array.isArray(item)) {
        return clsx(...item);
      }
      if (typeof item === 'object') {
        return clsx(
          Object.entries(item)
            .filter(([_, value]) => value)
            .map(([key]) => key)
        );
      }
      return '';
    })
    .filter(Boolean)
    .join(' ');
}

// apps/gitlab-plus/src/components/common/GitlabIcon.tsx
const buildId =
  '236e3b687d786d9dfe4709143a94d4c53b8d5a1f235775401e5825148297fa84';
const iconUrl = (icon) => {
  let _a;
  const svgSprite =
    ((_a = unsafeWindow.gon) == null ? void 0 : _a.sprite_icons) ||
    `/assets/icons-${buildId}.svg`;
  return `${svgSprite}#${icon}`;
};

function GitlabIcon({ className, icon, size = 12, title }) {
  return jsx('svg', {
    className: clsx('gl-icon gl-fill-current', `s${size}`, className),
    title,
    children: jsx('use', { href: iconUrl(icon) }),
  });
}

// apps/gitlab-plus/src/components/common/GitlabLoader.tsx
function GitlabLoader({ size = 24 }) {
  return jsx('span', {
    class: 'gl-spinner-container',
    role: 'status',
    children: jsx('span', {
      class: 'gl-spinner gl-spinner-sm gl-spinner-dark !gl-align-text-bottom',
      style: {
        width: size,
        height: size,
      },
    }),
  });
}

// apps/gitlab-plus/src/components/common/GitlabButton.tsx
const buttonVariantClass = {
  default: 'btn-default',
  info: 'btn-confirm',
  tertiary: 'btn-default-tertiary',
};

function GitlabButton({
  children,
  className,
  icon,
  iconSize = 12,
  isLoading,
  onClick,
  size = 'sm',
  title,
  variant = 'default',
}) {
  const IconComponent = useMemo(() => {
    if (isLoading) {
      return jsx(GitlabLoader, { size: iconSize });
    }
    if (icon) {
      return jsx(GitlabIcon, { icon, size: iconSize });
    }
    return null;
  }, [icon, isLoading]);
  return jsxs('button', {
    onClick,
    title,
    type: 'button',
    class: clsx(
      `btn btn-${size} gl-button`,
      buttonVariantClass[variant],
      className
    ),
    children: [
      children && jsx('span', { class: 'gl-button-text', children }),
      IconComponent,
    ],
  });
}

// apps/gitlab-plus/src/components/common/CloseButton.tsx
function CloseButton({ onClick, title = 'Close' }) {
  return jsx(GitlabButton, {
    className: 'btn-icon',
    icon: 'close-xs',
    iconSize: 16,
    onClick,
    title,
    variant: 'tertiary',
  });
}

// apps/gitlab-plus/src/components/common/modal/GlpModal.tsx
function GlpModal({ children, isVisible, onClose, title }) {
  return jsx('div', {
    class: clsx('glp-modal', isVisible && 'glp-modal-visible'),
    children: jsxs('div', {
      className: clsx(
        'glp-modal-content crud gl-border',
        'gl-rounded-form gl-border-section gl-bg-subtle gl-mt-5'
      ),
      children: [
        jsxs('div', {
          className: clsx(
            'crud-header gl-border-b gl-flex gl-flex-wrap',
            'gl-justify-between gl-gap-x-5 gl-gap-y-2 gl-rounded-t-form',
            'gl-border-section gl-bg-section gl-px-5 gl-py-4 gl-relative'
          ),
          children: [
            jsx('h2', {
              className: clsx(
                'gl-m-0 gl-inline-flex gl-items-center gl-gap-3',
                'gl-text-form gl-font-bold gl-leading-normal'
              ),
              children: title,
            }),
            jsx(CloseButton, { onClick: onClose }),
          ],
        }),
        children,
      ],
    }),
  });
}

// apps/gitlab-plus/src/components/common/modal/useGlpModal.ts
function useGlpModal(eventName) {
  const [isVisible, setIsVisible] = useState(false);
  useEffect(() => {
    document.addEventListener(eventName, () => setIsVisible(true));
  }, []);
  return {
    isVisible,
    onClose: () => setIsVisible(false),
  };
}

// apps/gitlab-plus/src/components/common/base/Text.tsx
function Text({ children, className, color, size, variant, weight }) {
  return jsx('span', {
    class: clsx(
      size && `gl-text-${size}`,
      weight && `gl-font-${weight}`,
      variant && `gl-text-${variant}`,
      color && `gl-text-${color}`,
      className
    ),
    children,
  });
}

// apps/gitlab-plus/src/components/common/form/FormField.tsx
function FormField({ children, error, hint, title }) {
  return jsxs('fieldset', {
    class: clsx(
      'form-group gl-form-group gl-w-full',
      error && 'gl-show-field-errors'
    ),
    children: [
      jsx('legend', {
        class: 'bv-no-focus-ring col-form-label pt-0 col-form-label',
        children: title,
      }),
      children,
      Boolean(!error && hint) && jsx('small', { children: hint }),
      Boolean(error) &&
        jsx('small', {
          class: 'gl-field-error',
          children: error,
        }),
    ],
  });
}

// apps/gitlab-plus/src/components/common/form/FormRow.tsx
function FormRow({ children }) {
  return jsx('div', { class: 'gl-flex gl-gap-x-3', children });
}

// libs/share/src/utils/camelizeKeys.ts
function camelizeKeys(data) {
  if (!data || ['string', 'number', 'boolean'].includes(typeof data)) {
    return data;
  }
  if (Array.isArray(data)) {
    return data.map(camelizeKeys);
  }
  const camelize = (key) => {
    const _key = key.replace(/[-_\s]+(.)?/g, (_, chr) =>
      chr ? chr.toUpperCase() : ''
    );
    return _key.substring(0, 1).toLowerCase() + _key.substring(1);
  };
  return Object.entries(data).reduce(
    (result, [key, value]) => ({
      ...result,
      [camelize(key)]: camelizeKeys(value),
    }),
    {}
  );
}

// apps/gitlab-plus/src/providers/GitlabProvider.ts
class GitlabProvider {
  constructor(force = false) {
    __publicField(this, 'cache', new Cache('glp-'));
    __publicField(this, 'graphqlApi', 'https://gitlab.com/api/graphql');
    __publicField(this, 'url', 'https://gitlab.com/api/v4/');
    this.force = force;
  }

  async cached(key, getValue, minutes) {
    const cacheValue = this.cache.get(key);
    if (cacheValue && !this.force) {
      return cacheValue;
    }
    const value = await getValue();
    this.cache.set(key, value, minutes);
    return value;
  }

  csrf() {
    const token = document.querySelector('meta[name=csrf-token]');
    if (token) {
      return token.getAttribute('content');
    }
    return '';
  }

  async get(path) {
    const response = await fetch(`${this.url}${path}`, {
      headers: this.headers(),
      method: 'GET',
    });
    const data = await response.json();
    return camelizeKeys(data);
  }

  async getCached(key, path, minutes) {
    return this.cached(key, () => this.get(path), minutes);
  }

  headers() {
    const headers = {
      'content-type': 'application/json',
    };
    const csrf = this.csrf();
    if (csrf) {
      headers['X-CSRF-Token'] = csrf;
    }
    return headers;
  }

  async post(path, body) {
    const response = await fetch(`${this.url}${path}`, {
      body: JSON.stringify(body),
      headers: this.headers(),
      method: 'POST',
    });
    const data = await response.json();
    return camelizeKeys(data);
  }

  async query(query, variables) {
    const response = await fetch(this.graphqlApi, {
      body: JSON.stringify({ query, variables }),
      headers: this.headers(),
      method: 'POST',
    });
    return response.json();
  }

  async queryCached(key, query, variables, minutes) {
    return this.cached(key, () => this.query(query, variables), minutes);
  }
}

// apps/gitlab-plus/src/providers/query/user.ts
const userFragment = `
fragment User on User {
  id
  avatarUrl
  name
  username
  webUrl
  webPath
  __typename
}
`;
const userQuery = `
query workspaceAutocompleteUsersSearch($search: String!, $fullPath: ID!, $isProject: Boolean = true) {
  groupWorkspace: group(fullPath: $fullPath) @skip(if: $isProject) {
    id
    users: autocompleteUsers(search: $search) {
      ...User
      ...UserAvailability
      __typename
    }
    __typename
  }
  workspace: project(fullPath: $fullPath) {
    id
    users: autocompleteUsers(search: $search) {
      ...User
      ...UserAvailability
      __typename
    }
    __typename
  }
}

${userFragment}
fragment UserAvailability on User {
  status {
    availability
    __typename
  }
  __typename
}
`;

// apps/gitlab-plus/src/providers/UsersProvider.ts
class UsersProvider extends GitlabProvider {
  async getUsers(projectId, search = '') {
    return this.queryCached(
      `users-${projectId}-${search}`,
      userQuery,
      {
        fullPath: projectId,
        search,
      },
      search === '' ? 20 : 0.5
    );
  }
}

// apps/gitlab-plus/src/components/common/form/autocomplete/useAsyncAutocompleteButton.ts
function useAsyncAutocompleteButton(hide) {
  const ref = useRef(null);
  useEffect(() => {
    document.body.addEventListener('click', (e) => {
      if (
        ref.current &&
        e.target !== ref.current &&
        !ref.current.contains(e.target)
      ) {
        hide();
      }
    });
  }, []);
  return ref;
}

// apps/gitlab-plus/src/components/common/form/autocomplete/AsyncAutocompleteButton.tsx
function AsyncAutocompleteButton({
  isOpen,
  renderLabel,
  reset,
  setIsOpen,
  size = 'md',
  value,
}) {
  const ref = useAsyncAutocompleteButton(() => setIsOpen(false));
  const icon = useMemo(() => {
    if (value.length) {
      return 'close-xs';
    }
    return isOpen ? 'chevron-lg-up' : 'chevron-lg-down';
  }, [isOpen, value]);
  return jsx('button', {
    class: `btn btn-default btn-${size} btn-block gl-button gl-new-dropdown-toggle`,
    ref,
    type: 'button',
    onClick: (e) => {
      e.preventDefault();
      setIsOpen(true);
    },
    children: jsxs('span', {
      class: 'gl-button-text gl-w-full',
      children: [
        jsx('span', {
          class: 'gl-new-dropdown-button-text',
          children: renderLabel(value),
        }),
        jsx('span', {
          onClick: (e) => {
            if (value.length) {
              e.preventDefault();
              reset();
            }
          },
          children: jsx(GitlabIcon, { icon, size: 16 }),
        }),
      ],
    }),
  });
}

// apps/gitlab-plus/src/components/common/form/autocomplete/AsyncAutocompleteOption.tsx
function AsyncAutocompleteOption({
  hideCheckbox = false,
  isActive,
  onClick,
  option,
  removeFromRecent,
  renderOption,
  selected,
}) {
  const selectedIds = selected.map((i) => i.id);
  const selectedClass = (id) => selectedIds.includes(id);
  return jsx('li', {
    onClick: () => onClick(option),
    class: clsx(
      'gl-new-dropdown-item', // selectedClass(option.id),
      isActive && 'glp-active'
    ),
    children: jsxs('span', {
      class: 'gl-new-dropdown-item-content',
      children: [
        !hideCheckbox &&
          jsx(GitlabIcon, {
            className: 'glp-item-check gl-pr-2',
            icon: selectedClass(option.id) ? 'mobile-issue-close' : '',
            size: 16,
          }),
        renderOption(option),
        removeFromRecent &&
          jsx(CloseButton, {
            title: 'Remove from recently used',
            onClick: (e) => {
              e.preventDefault();
              e.stopPropagation();
              removeFromRecent(option);
            },
          }),
      ],
    }),
  });
}

// apps/gitlab-plus/src/components/common/form/autocomplete/AsyncAutocompleteList.tsx
function AsyncAutocompleteList({
  hideCheckbox,
  activeIndex,
  onClick,
  options,
  recently,
  removeRecently,
  renderOption,
  value,
}) {
  return jsx('div', {
    onClick: (e) => e.stopPropagation(),
    class:
      'gl-new-dropdown-contents gl-new-dropdown-contents-with-scrim-overlay bottom-scrim-visible gl-new-dropdown-contents',
    style: {
      maxWidth: '800px',
      width: '100%',
      left: '0',
      top: '100%',
    },
    children: jsx('div', {
      class: 'gl-new-dropdown-inner',
      children: jsxs('ul', {
        class: 'gl-mb-0 gl-pl-0',
        children: [
          Boolean(recently.length) &&
            jsxs(Fragment, {
              children: [
                jsx('li', {
                  class:
                    'gl-pb-2 gl-pl-4 gl-pt-3 gl-text-sm gl-font-bold gl-text-strong',
                  children: 'Recently used',
                }),
                recently.map((item, index) =>
                  jsx(
                    AsyncAutocompleteOption,
                    {
                      hideCheckbox,
                      isActive: index === activeIndex,
                      onClick,
                      option: item,
                      removeFromRecent: removeRecently,
                      renderOption,
                      selected: value,
                    },
                    item.id
                  )
                ),
              ],
            }),
          Boolean(options.length) &&
            jsxs(Fragment, {
              children: [
                jsx('li', {
                  class:
                    'gl-pb-2 gl-pl-4 gl-pt-3 gl-text-sm gl-font-bold gl-text-strong gl-border-t',
                }),
                options.map((item, index) =>
                  jsx(
                    AsyncAutocompleteOption,
                    {
                      hideCheckbox,
                      isActive: recently.length + index === activeIndex,
                      onClick,
                      option: item,
                      renderOption,
                      selected: value,
                    },
                    item.id
                  )
                ),
              ],
            }),
          options.length + recently.length === 0 &&
            jsx('li', { class: 'gl-p-4', children: 'No options' }),
        ],
      }),
    }),
  });
}

// apps/gitlab-plus/src/components/common/form/autocomplete/AsyncAutocompleteSearch.tsx
function AsyncAutocompleteSearch({ navigate, setValue, value }) {
  return jsx('div', {
    class: 'gl-border-b-1 gl-border-b-solid gl-border-b-dropdown',
    children: jsxs('div', {
      class: 'gl-listbox-search gl-listbox-topmost',
      children: [
        jsx(GitlabIcon, {
          className: 'gl-search-box-by-type-search-icon',
          icon: 'search',
          size: 16,
        }),
        jsx('input', {
          class: 'gl-listbox-search-input',
          onInput: (e) => setValue(e.target.value),
          onKeyDown: (e) => navigate(e.key),
          value,
          autofocus: true,
        }),
        Boolean(value) &&
          jsx('div', {
            class: 'gl-search-box-by-type-right-icons',
            style: { top: '0' },
            children: jsx(CloseButton, {
              onClick: () => setValue(''),
              title: 'Clear input',
            }),
          }),
      ],
    }),
  });
}

// apps/gitlab-plus/src/components/common/form/autocomplete/useListNavigate.ts
function useListNavigate(options, recent, onClick, onClose) {
  const [activeIndex, setActiveIndex] = useState(-1);
  const navigate = (key) => {
    if (['ArrowDown', 'ArrowUp'].includes(key)) {
      const total = recent.length + options.length;
      const diff = key === 'ArrowDown' ? 1 : -1;
      setActiveIndex((activeIndex + diff + total) % total);
    } else if (key === 'Enter') {
      const allItems = [...recent, ...options];
      if (-1 < activeIndex && activeIndex < allItems.length) {
        onClick(allItems[activeIndex]);
      }
    } else if (key === 'Escape') {
      onClose();
    }
  };
  return {
    activeIndex,
    navigate,
  };
}

// apps/gitlab-plus/src/components/common/form/autocomplete/AsyncAutocompleteDropdown.tsx
function AsyncAutocompleteDropdown({
  hideCheckbox,
  onClick,
  onClose,
  options,
  recently = [],
  removeRecently,
  renderOption,
  searchTerm,
  setSearchTerm,
  value,
}) {
  const { activeIndex, navigate } = useListNavigate(
    options,
    recently,
    onClick,
    onClose
  );
  return jsx('div', {
    class: clsx('gl-new-dropdown-panel gl-absolute !gl-block'),
    onClick: (e) => e.stopPropagation(),
    style: {
      maxWidth: '800px',
      width: '100%',
      left: 'auto',
      right: '0',
      top: '100%',
    },
    children: jsxs('div', {
      class: 'gl-new-dropdown-inner',
      children: [
        jsx(AsyncAutocompleteSearch, {
          navigate,
          setValue: setSearchTerm,
          value: searchTerm,
        }),
        jsx(AsyncAutocompleteList, {
          hideCheckbox,
          activeIndex,
          onClick,
          options,
          recently,
          removeRecently,
          renderOption,
          value,
        }),
      ],
    }),
  });
}

// libs/share/src/utils/useDebounce.ts
function useDebounce(value, delay = 300) {
  const [debouncedValue, setDebouncedValue] = useState(value);
  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);
    return () => {
      clearTimeout(handler);
    };
  }, [value, delay]);
  return debouncedValue;
}

// apps/gitlab-plus/src/components/common/form/autocomplete/useAsyncAutocompleteOptions.ts
function useAsyncAutocompleteOptions(searchTerm, getValues) {
  const [options, setOptions] = useState([]);
  const term = useDebounce(searchTerm);
  const loadOptions = useCallback(
    async (term2) => {
      const items = await getValues(term2);
      setOptions(items);
    },
    [getValues]
  );
  useEffect(() => {
    loadOptions(term);
  }, [term, loadOptions]);
  return options;
}

// apps/gitlab-plus/src/providers/RecentlyProvider.ts
class RecentlyProvider {
  constructor(key) {
    __publicField(this, 'cache', new Cache('glp-'));
    __publicField(this, 'key');
    __publicField(this, 'eventName');
    this.key = `recently-${key}`;
    this.eventName = `recently-${key}-change`;
  }

  add(...items) {
    const itemsId = items.map((i) => i.id);
    this.cache.set(
      this.key,
      [...items, ...this.get().filter((el) => !itemsId.includes(el.id))],
      'lifetime'
    );
    this.triggerChange();
  }

  get() {
    return this.cache.get(this.key) || [];
  }

  onChange(callback) {
    document.addEventListener(this.eventName, callback);
  }

  remove(...items) {
    const itemsId = items.map((i) => i.id);
    this.cache.set(
      this.key,
      this.get().filter((el) => !itemsId.includes(el.id)),
      'lifetime'
    );
    this.triggerChange();
  }

  triggerChange() {
    document.dispatchEvent(new CustomEvent(this.eventName));
  }
}

// apps/gitlab-plus/src/components/common/form/autocomplete/useAsyncAutocompleteRecently.ts
function useAsyncAutocompleteRecently(name2) {
  const store = useRef(new RecentlyProvider(name2));
  const [recently, setRecently] = useState(store.current.get());
  useEffect(() => {
    store.current.onChange(() => {
      setRecently(store.current.get());
    });
  }, []);
  return {
    add: store.current.add.bind(store.current),
    recently,
    remove: store.current.remove.bind(store.current),
  };
}

// apps/gitlab-plus/src/components/common/form/autocomplete/useAsyncAutocomplete.ts
function useAsyncAutocomplete(
  name2,
  value,
  getValues,
  onChange,
  isMultiselect
) {
  const [searchTerm, setSearchTerm] = useState('');
  const [isOpen, setIsOpen] = useState(false);
  const { recently: allRecently, remove: removeRecently } =
    useAsyncAutocompleteRecently(name2);
  const options = useAsyncAutocompleteOptions(searchTerm, getValues);
  const onClick = (item) => {
    if (isMultiselect) {
      if (value.find((i) => i.id === item.id)) {
        onChange(value.filter((i) => i.id !== item.id));
      } else {
        onChange([...value, item]);
      }
    } else {
      onChange([item]);
      setIsOpen(false);
    }
  };
  const recently = useMemo(() => {
    const optionsIds = options.map((i) => i.id);
    return searchTerm.length
      ? allRecently.filter((i) => optionsIds.includes(i.id))
      : allRecently;
  }, [options, allRecently]);
  return {
    isOpen,
    onClick,
    options: useMemo(() => {
      const recentlyIds = recently.map((i) => i.id);
      return options.filter((i) => !recentlyIds.includes(i.id));
    }, [options, recently]),
    recently,
    removeRecently,
    searchTerm,
    setIsOpen,
    setSearchTerm,
  };
}

// apps/gitlab-plus/src/components/common/form/autocomplete/AsyncAutocomplete.tsx
function AsyncAutocomplete({
  hideCheckbox = false,
  buttonSize,
  getValues,
  isDisabled,
  isMultiselect = false,
  name: name2,
  onChange,
  renderLabel,
  renderOption,
  value,
}) {
  const {
    isOpen,
    onClick,
    options,
    recently,
    removeRecently,
    searchTerm,
    setIsOpen,
    setSearchTerm,
  } = useAsyncAutocomplete(name2, value, getValues, onChange, isMultiselect);
  return jsxs('div', {
    class: clsx(
      'gl-relative gl-w-full gl-new-dropdown !gl-block',
      isDisabled && 'gl-pointer-events-none gl-opacity-5'
    ),
    children: [
      jsx(AsyncAutocompleteButton, {
        isOpen,
        renderLabel,
        reset: () => onChange([]),
        setIsOpen,
        size: buttonSize,
        value,
      }),
      isOpen &&
        jsx(AsyncAutocompleteDropdown, {
          hideCheckbox,
          onClick,
          onClose: () => setIsOpen(false),
          options,
          recently,
          removeRecently,
          renderOption,
          searchTerm,
          setSearchTerm,
          value,
        }),
    ],
  });
}

// apps/gitlab-plus/src/components/common/GitlabUser.tsx
function GitlabUser({ showUsername, size = 24, smallText, user, withLink }) {
  const label = useMemo(() => {
    return jsxs(Fragment, {
      children: [
        jsx('span', {
          class: clsx('gl-mr-2 gl-block', smallText && '!gl-text-sm'),
          children: user.name,
        }),
        showUsername &&
          jsx('span', {
            class: 'gl-block gl-text-secondary !gl-text-sm',
            children: user.username,
          }),
      ],
    });
  }, [smallText, showUsername, user]);
  const iconClsx = [
    `gl-avatar gl-avatar-s${size}`,
    smallText ? 'gl-mr-1' : 'gl-mr-3',
  ];
  return jsxs('div', {
    class: 'gl-flex gl-items-center',
    children: [
      user.avatarUrl
        ? jsx('img', {
            alt: `${user.name}'s avatar`,
            class: clsx(...iconClsx, `gl-avatar-circle`),
            src: user.avatarUrl,
          })
        : jsx('div', {
            class: clsx(
              ...iconClsx,
              `gl-avatar-identicon gl-avatar-identicon-bg1`
            ),
            children: user.name[0].toUpperCase(),
          }),
      withLink
        ? jsx('a', { href: user.webUrl, children: label })
        : jsx('div', { children: label }),
    ],
  });
}

// apps/gitlab-plus/src/components/create-issue/fields/AssigneesField.tsx
function AssigneesField({ projectPath, setValue, value }) {
  const getUsers = useCallback(
    async (search) => {
      if (!projectPath) {
        return [];
      }
      const response = await new UsersProvider().getUsers(projectPath, search);
      return response.data.workspace.users;
    },
    [projectPath]
  );
  const renderLabel = useCallback((items) => {
    const label = items.map((i) => i.name).join(', ');
    return jsx('div', {
      title: label,
      children: items.length ? label : 'Select assignee',
    });
  }, []);
  const renderOption = useCallback((item) => {
    return jsx('span', {
      class: 'gl-new-dropdown-item-text-wrapper',
      children: jsx(GitlabUser, { user: item, showUsername: true }),
    });
  }, []);
  return jsx(AsyncAutocomplete, {
    getValues: getUsers,
    isDisabled: !projectPath,
    name: 'assignees',
    onChange: setValue,
    renderLabel,
    renderOption,
    value,
    isMultiselect: true,
  });
}

// apps/gitlab-plus/src/components/create-issue/fields/ButtonField.tsx
function ButtonField({ create, isLoading, reset }) {
  return jsxs(Fragment, {
    children: [
      jsxs('button', {
        class: 'btn btn-confirm btn-sm gl-button gl-gap-2',
        disabled: isLoading,
        onClick: create,
        type: 'button',
        children: [
          jsx('span', {
            class: 'gl-button-text',
            children: 'Add',
          }),
          isLoading
            ? jsx(GitlabLoader, { size: 12 })
            : jsx(GitlabIcon, { icon: 'plus', size: 12 }),
        ],
      }),
      jsx('button', {
        class: 'btn btn-sm gl-button',
        onClick: reset,
        type: 'button',
        children: jsx('span', { class: 'gl-button-text', children: 'Reset' }),
      }),
    ],
  });
}

// apps/gitlab-plus/src/providers/query/iteration.ts
const iterationFragment = `fragment IterationFragment on Iteration {
  id
  title
  startDate
  dueDate
  webUrl
  iterationCadence {
    id
    title
    __typename
  }
  __typename
}`;
const iterationQuery = `query issueIterationsAliased($fullPath: ID!, $title: String, $state: IterationState) {
  workspace: group(fullPath: $fullPath) {
    id
    attributes: iterations(
      search: $title
      in: [TITLE, CADENCE_TITLE]
      state: $state
    ) {
      nodes {
        ...IterationFragment
        state
        __typename
      }
      __typename
    }
    __typename
  }
}
${iterationFragment}
`;

// apps/gitlab-plus/src/providers/IterationsProvider.ts
class IterationsProvider extends GitlabProvider {
  async getIterations(projectId, title = '') {
    return this.queryCached(
      `iterations-${projectId}-search-${title}`,
      iterationQuery,
      {
        fullPath: projectId,
        state: 'opened',
        title,
      },
      title !== '' ? 0.5 : 20
    );
  }
}

// apps/gitlab-plus/src/components/create-issue/fields/IterationField.tsx
function iterationName(iteration) {
  const start = new Date(iteration.startDate).toLocaleDateString();
  const end = new Date(iteration.dueDate).toLocaleDateString();
  return `${iteration.iterationCadence.title}: ${start} - ${end}`;
}

function IterationField({ link, setValue, value }) {
  const getUsers = useCallback(
    async (search) => {
      const response = await new IterationsProvider().getIterations(
        link.workspacePath,
        search
      );
      return response.data.workspace.attributes.nodes
        .map((iteration) => ({
          ...iteration,
          name: iterationName(iteration),
        }))
        .toSorted((a, b) => a.name.localeCompare(b.name));
    },
    [link]
  );
  const renderLabel = useCallback(([item]) => {
    return item ? item.name : 'Select iteration';
  }, []);
  const renderOption = useCallback((item) => {
    return jsx('span', {
      class: 'gl-new-dropdown-item-text-wrapper',
      children: jsx('span', {
        class: 'gl-flex gl-w-full gl-items-center',
        children: jsx('span', {
          class: 'gl-mr-2 gl-block',
          children: item.name,
        }),
      }),
    });
  }, []);
  return jsx(AsyncAutocomplete, {
    getValues: getUsers,
    name: 'iterations',
    onChange: setValue,
    renderLabel,
    renderOption,
    value,
  });
}

// apps/gitlab-plus/src/providers/query/label.ts
const labelFragment = `
  fragment Label on Label {
    id
    title
    description
    color
    textColor
    __typename
  }
`;
const projectLabelsQuery = `query projectLabels($fullPath: ID!, $searchTerm: String) {
  workspace: project(fullPath: $fullPath) {
    id
    labels(
      searchTerm: $searchTerm
      includeAncestorGroups: true
    ) {
      nodes {
        ...Label
        __typename
      }
      __typename
    }
    __typename
  }
}
${labelFragment}
`;
const workspaceLabelsQuery = `query groupLabels($fullPath: ID!, $searchTerm: String) {
  workspace: group(fullPath: $fullPath) {
    id
    labels(
      searchTerm: $searchTerm
      onlyGroupLabels: true
      includeAncestorGroups: true
    ) {
      nodes {
        ...Label
        __typename
      }
      __typename
    }
    __typename
  }
}

${labelFragment}
`;

// apps/gitlab-plus/src/providers/LabelsProvider.ts
class LabelsProvider extends GitlabProvider {
  async getProjectLabels(projectPath, search = '') {
    return this.queryCached(
      `project-${projectPath}-labels-${search}`,
      projectLabelsQuery,
      {
        fullPath: projectPath,
        searchTerm: search,
      },
      search === '' ? 20 : 0.5
    );
  }

  async getWorkspaceLabels(workspacePath, search = '') {
    return this.queryCached(
      `workspace-${workspacePath}-labels-${search}`,
      workspaceLabelsQuery,
      {
        fullPath: workspacePath,
        searchTerm: search,
      },
      search === '' ? 20 : 0.5
    );
  }
}

// apps/gitlab-plus/src/components/common/GitlabLabel.tsx
function GitlabLabel({ label, onRemove }) {
  const [scope, text] = label.title.split('::');
  const props = useMemo(() => {
    const className = [
      'gl-label',
      'hide-collapsed',
      label.textColor === '#FFFFFF'
        ? 'gl-label-text-light'
        : 'gl-label-text-dark',
    ];
    if (label.title.includes('::')) {
      className.push('gl-label-scoped');
    }
    return {
      class: clsx(className),
      style: {
        '--label-background-color': label.color,
        '--label-inset-border': `inset 0 0 0 2px ${label.color}`,
      },
    };
  }, [label]);
  return jsxs('span', {
    class: props.class,
    style: props.style,
    children: [
      jsxs('span', {
        class: 'gl-link gl-label-link gl-label-link-underline',
        children: [
          jsx('span', {
            class: 'gl-label-text',
            children: scope,
          }),
          text &&
            jsx('span', { class: 'gl-label-text-scoped', children: text }),
        ],
      }),
      onRemove &&
        jsx('button', {
          onClick: onRemove,
          type: 'button',
          class:
            'btn gl-label-close !gl-p-0 btn-reset btn-sm gl-button btn-reset-tertiary',
          children: jsx('span', {
            class: 'gl-button-text',
            children: jsx(GitlabIcon, { icon: 'close-xs' }),
          }),
        }),
    ],
  });
}

// apps/gitlab-plus/src/components/create-issue/fields/LabelsField.tsx
function LabelField({ copyLabels, projectPath, setValue, value }) {
  const getLabels = useCallback(
    async (search) => {
      if (!projectPath) {
        return [];
      }
      const response = await new LabelsProvider().getProjectLabels(
        projectPath,
        search
      );
      return response.data.workspace.labels.nodes;
    },
    [projectPath]
  );
  const renderLabel = useCallback((items) => {
    return items.length
      ? items.map((i) => i.title).join(', ')
      : 'Select labels';
  }, []);
  const renderOption = useCallback((item) => {
    return jsxs('div', {
      class: 'gl-flex gl-flex-1 gl-break-anywhere gl-pb-3 gl-pl-4 gl-pt-3',
      children: [
        jsx('span', {
          class: 'dropdown-label-box gl-top-0 gl-mr-3 gl-shrink-0',
          style: { backgroundColor: item.color },
        }),
        jsx('span', { children: item.title }),
      ],
    });
  }, []);
  return jsxs(Fragment, {
    children: [
      jsx('div', {
        class: 'gl-mt-1 gl-pb-2 gl-flex gl-flex-wrap gl-gap-2',
        children: value.map((label) =>
          jsx(
            GitlabLabel,
            {
              label,
              onRemove: () =>
                setValue(value.filter((item) => label.id !== item.id)),
            },
            label.id
          )
        ),
      }),
      jsxs('div', {
        className: 'gl-flex gl-gap-1 gl-relative gl-pr-7',
        children: [
          jsx(AsyncAutocomplete, {
            getValues: getLabels,
            isDisabled: !projectPath,
            name: 'labels',
            onChange: setValue,
            renderLabel,
            renderOption,
            value,
            isMultiselect: true,
          }),
          jsx('div', {
            className: 'gl-flex gl-absolute gl-h-full gl-right-0',
            children: jsx(GitlabButton, {
              icon: 'labels',
              onClick: copyLabels,
              title: 'Copy labels from parent',
            }),
          }),
        ],
      }),
    ],
  });
}

// apps/gitlab-plus/src/providers/query/milestone.ts
const milestoneQuery = `query projectMilestones($fullPath: ID!, $title: String, $state: MilestoneStateEnum) {
  workspace: project(fullPath: $fullPath) {
    id
    attributes: milestones(
      searchTitle: $title
      state: $state
      sort: EXPIRED_LAST_DUE_DATE_ASC
      first: 20
      includeAncestors: true
    ) {
      nodes {
        ...MilestoneFragment
        state
        __typename
      }
      __typename
    }
    __typename
  }
}

fragment MilestoneFragment on Milestone {
  id
  iid
  title
  webUrl: webPath
  dueDate
  expired
  __typename
}

`;

// apps/gitlab-plus/src/providers/MilestonesProvider.ts
class MilestonesProvider extends GitlabProvider {
  async getMilestones(projectId, title = '') {
    return this.queryCached(
      `milestones-${projectId}-${title}`,
      milestoneQuery,
      {
        fullPath: projectId,
        state: 'active',
        title,
      },
      title === '' ? 20 : 0.5
    );
  }
}

// apps/gitlab-plus/src/components/create-issue/fields/MilestoneField.tsx
function MilestoneField({ projectPath, setValue, value }) {
  const getMilestones = useCallback(
    async (search) => {
      if (!projectPath) {
        return [];
      }
      const response = await new MilestonesProvider().getMilestones(
        projectPath,
        search
      );
      return response.data.workspace.attributes.nodes;
    },
    [projectPath]
  );
  const renderLabel = useCallback(([item]) => {
    return item ? item.title : 'Select milestone';
  }, []);
  const renderOption = useCallback((item) => {
    return jsx('span', {
      class: 'gl-new-dropdown-item-text-wrapper',
      children: jsx('span', {
        class: 'gl-flex gl-w-full gl-items-center',
        children: jsx('span', {
          class: 'gl-mr-2 gl-block',
          children: item.title,
        }),
      }),
    });
  }, []);
  return jsx(AsyncAutocomplete, {
    getValues: getMilestones,
    isDisabled: !projectPath,
    name: 'milestones',
    onChange: setValue,
    renderLabel,
    renderOption,
    value,
  });
}

// apps/gitlab-plus/src/providers/query/project.ts
const projectsQuery = `query boardsGetGroupProjects($fullPath: ID!, $search: String, $after: String) {
  group(fullPath: $fullPath) {
    id
    projects(search: $search, after: $after, first: 100, includeSubgroups: true) {
      nodes {
        id
        name
        avatarUrl
        fullPath
        nameWithNamespace
        archived
        __typename
      }
      pageInfo {
        ...PageInfo
        __typename
      }
      __typename
    }
    __typename
  }
}

fragment PageInfo on PageInfo {
  hasNextPage
  hasPreviousPage
  startCursor
  endCursor
  __typename
}

`;

// apps/gitlab-plus/src/providers/ProjectsProvider.ts
class ProjectsProvider extends GitlabProvider {
  async getProjects(workspacePath, search = '') {
    return this.queryCached(
      `projects-${workspacePath}-${search}`,
      projectsQuery,
      {
        fullPath: workspacePath,
        search,
      },
      search === '' ? 20 : 0.5
    );
  }
}

// apps/gitlab-plus/src/components/common/GitlabProject.tsx
function GitlabProject({ project, size = 32 }) {
  return jsxs('span', {
    class: 'gl-flex gl-w-full gl-items-center',
    children: [
      project.avatarUrl
        ? jsx('img', {
            alt: project.name,
            class: `gl-mr-3 gl-avatar gl-avatar-s${size}`,
            src: project.avatarUrl,
          })
        : jsx('div', {
            class: `gl-mr-3 gl-avatar gl-avatar-identicon gl-avatar-s${size} gl-avatar-identicon-bg1`,
            children: project.name[0].toUpperCase(),
          }),
      jsxs('span', {
        children: [
          jsx('span', { class: 'gl-mr-2 gl-block', children: project.name }),
          jsx('span', {
            class: 'gl-block gl-text-secondary !gl-text-sm',
            children: project.nameWithNamespace,
          }),
        ],
      }),
    ],
  });
}

// apps/gitlab-plus/src/components/create-issue/fields/ProjectField.tsx
function ProjectField({ link, setValue, value }) {
  const getProjects = useCallback(
    async (search) => {
      const response = await new ProjectsProvider().getProjects(
        link.workspacePath,
        search
      );
      return response.data.group.projects.nodes;
    },
    [link]
  );
  const renderLabel = useCallback(([item]) => {
    return item ? item.nameWithNamespace : 'Select project';
  }, []);
  const renderOption = useCallback((item) => {
    return jsx('span', {
      class: 'gl-new-dropdown-item-text-wrapper',
      children: jsx(GitlabProject, { project: item }),
    });
  }, []);
  return jsx(AsyncAutocomplete, {
    getValues: getProjects,
    name: 'projects',
    onChange: setValue,
    renderLabel,
    renderOption,
    value,
  });
}

// apps/gitlab-plus/src/types/Issue.ts
const issueRelation = ['blocks', 'is_blocked_by', 'relates_to'];

// apps/gitlab-plus/src/components/create-issue/fields/RelationField.tsx
const labels = (relation) => {
  switch (relation) {
    case 'blocks':
      return 'blocks current issue';
    case 'is_blocked_by':
      return 'is blocked by current issue';
    case 'relates_to':
      return 'relates to current issue';
    default:
      return 'is not related to current issue';
  }
};

function RelationField({ setValue, value }) {
  return jsx('div', {
    class: 'linked-issue-type-radio',
    children: [...issueRelation, null].map((relation) =>
      jsxs(
        'div',
        {
          class: 'gl-form-radio custom-control custom-radio',
          children: [
            jsx('input', {
              id: `create-related-issue-relation-${relation}`,
              checked: value === relation,
              class: 'custom-control-input',
              name: 'linked-issue-type-radio',
              onChange: () => setValue(relation),
              type: 'radio',
              value: relation ?? '',
            }),
            jsx('label', {
              class: 'custom-control-label',
              for: `create-related-issue-relation-${relation}`,
              children: labels(relation),
            }),
          ],
        },
        relation
      )
    ),
  });
}

// apps/gitlab-plus/src/components/create-issue/fields/TitleField.tsx
function TitleField({ error, onChange, value }) {
  return jsx('input', {
    onInput: (e) => onChange(e.target.value),
    placeholder: 'Add a title',
    value,
    class: clsx(
      'gl-form-input form-control',
      error && 'gl-field-error-outline'
    ),
  });
}

// apps/gitlab-plus/src/helpers/LinkParser.ts
class LinkParser {
  static isEpicLink(link) {
    return link.epic !== void 0;
  }

  static isIssueLink(link) {
    return link.issue !== void 0;
  }

  static isMrLink(link) {
    return link.mr !== void 0;
  }

  static parseEpicLink(link) {
    if (LinkParser.validateEpicLink(link)) {
      return LinkParser.parseGitlabLink(
        link,
        /\/groups\/(?<workspacePath>.+)\/-\/epics\/(?<epic>\d+)/
      );
    }
    return void 0;
  }

  static parseGitlabLink(link, pattern) {
    const url = new URL(link);
    const result = url.pathname.match(pattern);
    if (result && result.groups) {
      return result.groups;
    }
    return void 0;
  }

  static parseIssueLink(link) {
    if (LinkParser.validateIssueLink(link)) {
      return LinkParser.parseGitlabLink(
        link,
        /\/(?<projectPath>(?<workspacePath>.+)\/[^/]+)\/-\/issues\/(?<issue>\d+)/
      );
    }
    return void 0;
  }

  static parseMrLink(link) {
    if (LinkParser.validateMrLink(link)) {
      return LinkParser.parseGitlabLink(
        link,
        /\/(?<projectPath>(?<workspacePath>.+)\/[^/]+)\/-\/merge_requests\/(?<mr>\d+)\/?$/
      );
    }
    return void 0;
  }

  static validateEpicLink(link) {
    return LinkParser.validateGitlabLink(link, 'epics');
  }

  static validateGitlabLink(link, type) {
    return Boolean(typeof link === 'string' && link.includes(`/-/${type}/`));
  }

  static validateIssueLink(link) {
    return LinkParser.validateGitlabLink(link, 'issues');
  }

  static validateMrLink(link) {
    return LinkParser.validateGitlabLink(link, 'merge_requests');
  }
}

// apps/gitlab-plus/src/helpers/Widget.ts
class WidgetHelper {
  static epicLabels(epic) {
    const labelWidgets = epic.widgets.find((w) => w.type === 'LABELS');
    if (labelWidgets) {
      return labelWidgets.labels.nodes;
    }
    return [];
  }
}

// apps/gitlab-plus/src/providers/query/epic.ts
const epicQuery = `query namespaceWorkItem($fullPath: ID!, $iid: String!) {
  workspace: namespace(fullPath: $fullPath) {
    id
    workItem(iid: $iid) {
      ...WorkItem
      __typename
    }
    __typename
  }
}

fragment WorkItem on WorkItem {
  id
  iid
  archived
  title
  state
  description
  confidential
  createdAt
  closedAt
  webUrl
  reference(full: true)
  createNoteEmail
  project {
    id
    __typename
  }
  namespace {
    id
    fullPath
    name
    fullName
    __typename
  }
  author {
    ...Author
    __typename
  }

  workItemType {
    id
    name
    iconName
    __typename
  }
  userPermissions {
    deleteWorkItem
    updateWorkItem
    adminParentLink
    setWorkItemMetadata
    createNote
    adminWorkItemLink
    markNoteAsInternal
    reportSpam
    __typename
  }
  widgets {
    ...WorkItemWidgets
    __typename
  }
  __typename
}

fragment WorkItemWidgets on WorkItemWidget {
  type
    ... on WorkItemWidgetHierarchy {
    hasChildren
    children(first: 100) {
      count
      nodes {
        id
        iid
        title
        state
        webUrl
      }
    }
  }
  ... on WorkItemWidgetAssignees {
    assignees {
      nodes {
        ...User
      }
    }
  }
  ... on WorkItemWidgetLabels {
    labels {
      nodes {
        ...Label
      }
    }
  }
  ... on WorkItemWidgetStartAndDueDate {
    dueDate
    startDate
    rollUp
    isFixed
    __typename
  }
  ... on WorkItemWidgetProgress {
    progress
    updatedAt
    __typename
  }
  ... on WorkItemWidgetIteration {
    iteration {
      id
      title
      startDate
      dueDate
      webUrl
      iterationCadence {
        id
        title
      }
      __typename
    }
    __typename
  }
  ... on WorkItemWidgetMilestone {
    milestone {
      ...MilestoneFragment
      __typename
    }
    __typename
  }
  ... on WorkItemWidgetNotes {
    discussionLocked
    __typename
  }
  ... on WorkItemWidgetHealthStatus {
    healthStatus
    rolledUpHealthStatus {
      count
      healthStatus
      __typename
    }
    __typename
  }
  ... on WorkItemWidgetNotifications {
    subscribed
    __typename
  }
  ... on WorkItemWidgetCurrentUserTodos {
    currentUserTodos(state: pending) {
      nodes {
        id
        __typename
      }
      __typename
    }
    __typename
  }
  ... on WorkItemWidgetColor {
    color
    textColor
    __typename
  }
  ... on WorkItemWidgetLinkedItems {
    linkedItems {
      nodes {
        linkId
        linkType
        __typename
      }
      __typename
    }
    __typename
  }
  ... on WorkItemWidgetCrmContacts {
    contacts {
      nodes {
        id
        email
        firstName
        lastName
        phone
        description
        organization {
          id
          name
          description
          defaultRate
          __typename
        }
        __typename
      }
      __typename
    }
    __typename
  }
  __typename
}

fragment User on User {
  id
  avatarUrl
  name
  username
  webUrl
  webPath
  __typename
}

fragment MilestoneFragment on Milestone {
  expired
  id
  title
  state
  startDate
  dueDate
  webPath
  __typename
}

fragment Author on User {
  id
  avatarUrl
  name
  username
  webUrl
  webPath
  __typename
}

${labelFragment}
`;
const epicSetLabelsMutation = `
mutation workItemUpdate($input: WorkItemUpdateInput!) {
  workItemUpdate(input: $input) {
    workItem {
      __typename
    }
    errors
  }
}
`;

// apps/gitlab-plus/src/providers/EpicProvider.ts
class EpicProvider extends GitlabProvider {
  async getEpic(workspacePath, epicId) {
    return this.queryCached(
      `epic-${workspacePath}-${epicId}`,
      epicQuery,
      {
        iid: epicId,
        cursor: '',
        fullPath: workspacePath,
        pageSize: 50,
      },
      2
    );
  }

  async updateEpicLabels(id, addLabelIds, removeLabelIds) {
    return await this.query(epicSetLabelsMutation, {
      input: {
        id,
        labelsWidget: {
          addLabelIds,
          removeLabelIds,
        },
      },
    });
  }
}

// apps/gitlab-plus/src/providers/query/issue.ts
const issueQuery = `query issueEE($projectPath: ID!, $iid: String!) {
  project(fullPath: $projectPath) {
    id
    issue(iid: $iid) {
      id
      iid
      title
      description
      createdAt
      state
      confidential
      dueDate
      projectId
      milestone {
        id
        title
        startDate
        dueDate
        __typename
      }
      epic {
        id
        iid
        title
        webUrl
      }
      iteration {
        id
        title
        startDate
        dueDate
        iterationCadence {
          id
          title
          __typename
        }
        __typename
      }
      labels {
        nodes {
          ...Label
        }
      }
      relatedMergeRequests {
        nodes {
          iid
          title
          state
          webUrl
          author {
            ...User
          }
        }
      }
      assignees {
        nodes {
          ...User
        }
      }
      author {
        ...User
      }
      weight
      type
      linkedWorkItems {
        nodes {
          linkType
          workItemState
          workItem {
            id
            iid
            webUrl
            title
          }
        }
      }
      __typename
    }
    __typename
  }
}

${labelFragment}
${userFragment}
`;
const issueWithRelatedIssuesLabelsQuery = `query issueEE($projectPath: ID!, $iid: String!) {
  project(fullPath: $projectPath) {
    issue(iid: $iid) {
      linkedWorkItems {
        nodes {
          workItem {
            id
            iid
            widgets {
              type
              ...LabelsWidget
            }
          }
        }
      }
    }
  }
}

fragment LabelsWidget on WorkItemWidgetLabels {
  labels {
    nodes {
      ...Label
    }
  }
}

${labelFragment}
`;
const issuesQuery = `query groupWorkItems($searchTerm: String, $fullPath: ID!, $types: [IssueType!], $in: [IssuableSearchableField!], $includeAncestors: Boolean = false, $includeDescendants: Boolean = false, $iid: String = null, $searchByIid: Boolean = false, $searchByText: Boolean = true, $searchEmpty: Boolean = true) {
  workspace: group(fullPath: $fullPath) {
    id
    workItems(
      search: $searchTerm
      types: $types
      in: $in
      includeAncestors: $includeAncestors
      includeDescendants: $includeDescendants
    ) @include(if: $searchByText) {
      nodes {
        id
        iid
        title
        confidential
        project {
          fullPath
        }
        __typename
      }
      __typename
    }
    workItemsByIid: workItems(
      iid: $iid
      types: $types
      includeAncestors: $includeAncestors
      includeDescendants: $includeDescendants
    ) @include(if: $searchByIid) {
      nodes {
        id
        iid
        title
        confidential
        project {
          fullPath
        }
        __typename
      }
      __typename
    }
    workItemsEmpty: workItems(
      types: $types
      includeAncestors: $includeAncestors
      includeDescendants: $includeDescendants
    ) @include(if: $searchEmpty) {
      nodes {
        id
        iid
        title
        confidential
        project {
          fullPath
        }
        __typename
      }
      __typename
    }
    __typename
  }
}
`;
const issueMutation = `
mutation CreateIssue($input: CreateIssueInput!) {
  createIssuable: createIssue(input: $input) {
    issuable: issue {
      ...Issue
      __typename
    }
    errors
    __typename
  }
}

fragment Issue on Issue {
  ...IssueNode
  id
  weight
  blocked
  blockedByCount
  epic {
    id
    __typename
  }
  iteration {
    id
    title
    startDate
    dueDate
    iterationCadence {
      id
      title
      __typename
    }
    __typename
  }
  healthStatus
  __typename
}

fragment IssueNode on Issue {
  id
  iid
  title
  referencePath: reference(full: true)
  closedAt
  dueDate
  timeEstimate
  totalTimeSpent
  humanTimeEstimate
  humanTotalTimeSpent
  emailsDisabled
  confidential
  hidden
  webUrl
  relativePosition
  projectId
  type
  severity
  milestone {
    ...MilestoneFragment
    __typename
  }
  assignees {
    nodes {
      ...User
      __typename
    }
    __typename
  }
  labels {
    nodes {
      id
      title
      color
      description
      __typename
    }
    __typename
  }
  __typename
}

fragment MilestoneFragment on Milestone {
  expired
  id
  state
  title
  __typename
}

fragment User on User {
  id
  avatarUrl
  name
  username
  webUrl
  webPath
  __typename
}
`;
const issueSetEpicMutation = `
mutation projectIssueUpdateParent($input: WorkItemUpdateInput!) {
  issuableSetAttribute: workItemUpdate(input: $input) {
    workItem {
      id
      widgets {
        ... on WorkItemWidgetHierarchy {
          type
          parent {
            id
            title
            webUrl
          }
        }
      }
    }
    errors
  }
}
`;
const issueSetLabelsMutation = `
mutation issueSetLabels($input: UpdateIssueInput!) {
  updateIssuableLabels: updateIssue(input: $input) {
    issuable: issue {
      __typename
    }
    errors
    __typename
  }
}
`;

// apps/gitlab-plus/src/providers/IssueProvider.ts
class IssueProvider extends GitlabProvider {
  async createIssue(input) {
    return await this.query(issueMutation, { input });
  }

  async createIssueRelation(input) {
    const path = [
      'projects/:PROJECT_ID',
      '/issues/:ISSUE_ID/links',
      '?target_project_id=:TARGET_PROJECT_ID',
      '&target_issue_iid=:TARGET_ISSUE_IID',
      '&link_type=:LINK_TYPE',
    ]
      .join('')
      .replace(':PROJECT_ID', `${input.projectId}`)
      .replace(':ISSUE_ID', `${input.issueId}`)
      .replace(':TARGET_PROJECT_ID', input.targetProjectId)
      .replace(':TARGET_ISSUE_IID', input.targetIssueIid)
      .replace(':LINK_TYPE', input.linkType);
    return await this.post(path, {});
  }

  async getIssue(projectPath, iid) {
    return this.queryCached(
      `issue-${projectPath}-${iid}`,
      issueQuery,
      {
        iid,
        projectPath,
      },
      2
    );
  }

  async getIssues(projectPath, search) {
    const searchById = !!search.match(/^\d+$/);
    return await this.query(issuesQuery, {
      iid: searchById ? search : null,
      searchByIid: searchById,
      fullPath: projectPath,
      in: 'TITLE',
      includeAncestors: true,
      includeDescendants: true,
      searchByText: Boolean(search),
      searchEmpty: !search,
      searchTerm: search,
      types: ['ISSUE'],
    });
  }

  async getIssueWithRelatedIssuesLabels(projectPath, iid) {
    return this.queryCached(
      `issue-related-issues-${projectPath}-${iid}`,
      issueWithRelatedIssuesLabelsQuery,
      {
        iid,
        projectPath,
      },
      0.02
    );
  }

  async issueSetEpic(issueId, epicId) {
    return await this.query(issueSetEpicMutation, {
      input: {
        hierarchyWidget: {
          parentId: epicId,
        },
        id: issueId,
      },
    });
  }

  async issueSetLabels(input) {
    return await this.query(issueSetLabelsMutation, {
      input,
    });
  }
}

// apps/gitlab-plus/src/components/create-issue/useCreateIssueForm.ts
const initialState = () => ({
  assignees: [],
  iteration: null,
  labels: [],
  milestone: null,
  project: null,
  relation: null,
  title: '',
});
const initialError = () => ({
  assignees: void 0,
  iteration: void 0,
  labels: void 0,
  milestone: void 0,
  project: void 0,
  relation: void 0,
  title: void 0,
});

function useCreateIssueForm({ isVisible, link, onClose }) {
  let _a;
  const [values, setValues] = useState(initialState());
  const [errors, setErrors] = useState(initialError());
  const [parentIssue, setParentIssue] = useState(null);
  const [parentEpic, setParentEpic] = useState(null);
  const [isLoading, setIsLoading] = useState(false);
  const [message, setMessage] = useState('');
  const [error, setError] = useState('');
  const reset = () => {
    setIsLoading(false);
    setValues(initialState());
    setErrors(initialError());
    setMessage('');
    setError('');
    setParentIssue(null);
    setParentEpic(null);
  };
  const createPayload = () => {
    const data = {
      projectPath: values.project.fullPath,
      title: values.title,
    };
    if (values.milestone) {
      data['milestoneId'] = values.milestone.id;
    }
    if (values.iteration) {
      data['iterationId'] = values.iteration.id;
      data['iterationCadenceId'] = values.iteration.iterationCadence.id;
    }
    if (values.assignees) {
      data['assigneeIds'] = values.assignees.map((a) => a.id);
    }
    data['labelIds'] = values.labels.map((label) => label.id);
    return data;
  };
  const persistRecently = () => {
    Object.entries({
      assignees: values.assignees,
      iterations: values.iteration ? [values.iteration] : [],
      labels: values.labels,
      milestones: values.milestone ? [values.milestone] : [],
      projects: values.project ? [values.project] : [],
    }).map(([key, values2]) => {
      new RecentlyProvider(key).add(...values2);
    });
  };
  const validate = () => {
    let isValid = true;
    const errors2 = {};
    if (values.title.length < 1) {
      errors2.title = 'Title is required';
      isValid = false;
    } else if (values.title.length > 255) {
      errors2.title = 'Title is too long';
      isValid = false;
    }
    if (!values.project) {
      errors2.project = 'Project must be selected';
      isValid = false;
    }
    setErrors((prev) => ({ ...prev, ...errors2 }));
    return isValid;
  };
  const createIssue = async (payload) => {
    return await new IssueProvider().createIssue(payload);
  };
  const createRelation = async (issue, targetIssue, relation) => {
    await new IssueProvider().createIssueRelation({
      targetIssueIid: targetIssue.iid,
      issueId: issue.iid,
      linkType: relation,
      projectId: issue.projectId,
      targetProjectId: targetIssue.projectId,
    });
  };
  const setIssueEpic = async (issue, epic) => {
    await new IssueProvider().issueSetEpic(issue.id, epic.id);
  };
  const submit = async () => {
    if (!validate()) {
      return;
    }
    setIsLoading(true);
    try {
      setMessage('Creating issue...');
      const payload = createPayload();
      const response = await createIssue(payload);
      persistRecently();
      if (values.relation && parentIssue) {
        setMessage('Creating relation to parent issue...');
        await createRelation(
          response.data.createIssuable.issuable,
          parentIssue,
          values.relation
        );
      }
      if (parentEpic) {
        setMessage('Linking to epic...');
        await setIssueEpic(response.data.createIssuable.issuable, parentEpic);
      }
      setMessage('Issue was created');
      window.setTimeout(() => onClose(), 2e3);
    } catch (e) {
      setMessage('');
      setError(e.message);
    }
    setIsLoading(false);
  };
  const fetchParent = async () => {
    if (LinkParser.isIssueLink(link)) {
      const issue = await new IssueProvider().getIssue(
        link.projectPath,
        link.issue
      );
      setParentIssue(issue.data.project.issue);
    }
    if (LinkParser.isEpicLink(link)) {
      const epic = await new EpicProvider().getEpic(
        link.workspacePath,
        link.epic
      );
      setParentEpic(epic.data.workspace.workItem);
    }
  };
  useEffect(() => {
    if (isVisible) {
      fetchParent();
    } else {
      reset();
    }
  }, [isVisible]);
  return {
    actions: {
      reset,
      submit,
    },
    error,
    form: {
      assignees: {
        errors: errors.assignees,
        onChange: (assignees) => setValues({ ...values, assignees }),
        value: values.assignees,
      },
      iteration: {
        errors: errors.iteration,
        onChange: ([iteration]) =>
          setValues({ ...values, iteration: iteration ?? null }),
        value: values.iteration ? [values.iteration] : [],
      },
      labels: {
        copy: () => {
          if (parentEpic) {
            setValues({
              ...values,
              labels: WidgetHelper.epicLabels(parentEpic),
            });
          }
          if (parentIssue) {
            setValues({ ...values, labels: parentIssue.labels.nodes });
          }
        },
        errors: errors.labels,
        onChange: (labels2) => setValues({ ...values, labels: labels2 }),
        value: values.labels,
      },
      milestone: {
        errors: errors.milestone,
        onChange: ([milestone]) =>
          setValues({ ...values, milestone: milestone ?? null }),
        value: values.milestone ? [values.milestone] : [],
      },
      project: {
        errors: errors.project,
        onChange: ([project]) =>
          setValues({ ...values, project: project ?? null }),
        value: values.project ? [values.project] : [],
      },
      relation: {
        errors: errors.relation,
        onChange: (relation) => setValues({ ...values, relation }),
        value: values.relation,
      },
      title: {
        copy: () => {
          const parentTitle =
            (parentIssue == null ? void 0 : parentIssue.title) ||
            (parentEpic == null ? void 0 : parentEpic.title);
          if (parentTitle) {
            setValues({
              ...values,
              title: parentTitle,
            });
          }
        },
        errors: errors.title,
        onChange: (title) => setValues({ ...values, title }),
        value: values.title,
      },
    },
    isLoading,
    message,
    parentEpic,
    parentIssue,
    projectPath: (_a = values.project) == null ? void 0 : _a.fullPath,
  };
}

// apps/gitlab-plus/src/components/create-issue/CreateIssueForm.tsx
function CreateIssueForm({ isVisible, link, onClose }) {
  const {
    actions,
    error,
    form,
    isLoading,
    message,
    parentEpic,
    parentIssue,
    projectPath,
  } = useCreateIssueForm({ isVisible, link, onClose });
  return jsxs('form', {
    class: 'crud-body add-tree-form gl-mx-5 gl-my-4 gl-rounded-b-form',
    children: [
      jsx(FormField, {
        error: form.title.errors,
        hint: 'Maximum of 255 characters',
        title: 'Title',
        children: jsxs('div', {
          className: 'gl-flex gl-gap-1',
          children: [
            jsx(TitleField, {
              error: form.title.errors,
              onChange: form.title.onChange,
              value: form.title.value,
            }),
            jsx(GitlabButton, {
              icon: 'title',
              onClick: form.title.copy,
              title: 'Copy from parent title',
            }),
          ],
        }),
      }),
      jsxs(FormRow, {
        children: [
          jsx(FormField, {
            error: form.project.errors,
            title: 'Project',
            children: jsx(ProjectField, {
              link,
              setValue: form.project.onChange,
              value: form.project.value,
            }),
          }),
          jsx(FormField, {
            error: form.assignees.errors,
            title: 'Assignees',
            children: jsx(AssigneesField, {
              projectPath,
              setValue: form.assignees.onChange,
              value: form.assignees.value,
            }),
          }),
        ],
      }),
      jsxs(FormRow, {
        children: [
          jsx(FormField, {
            error: form.iteration.errors,
            title: 'Iteration',
            children: jsx(IterationField, {
              link,
              setValue: form.iteration.onChange,
              value: form.iteration.value,
            }),
          }),
          jsx(FormField, {
            error: form.milestone.errors,
            title: 'Milestone',
            children: jsx(MilestoneField, {
              projectPath,
              setValue: form.milestone.onChange,
              value: form.milestone.value,
            }),
          }),
        ],
      }),
      jsx(FormField, {
        error: form.labels.errors,
        title: 'Labels',
        children: jsx(LabelField, {
          copyLabels: form.labels.copy,
          projectPath,
          setValue: form.labels.onChange,
          value: form.labels.value,
        }),
      }),
      parentIssue &&
        jsxs(FormField, {
          error: form.relation.errors,
          title: 'New issue',
          children: [
            jsx(RelationField, {
              setValue: form.relation.onChange,
              value: form.relation.value,
            }),
            jsxs(Text, {
              size: 'sm',
              variant: 'secondary',
              children: [
                'Parent issue: #',
                parentIssue.iid,
                ' ',
                parentIssue.title,
              ],
            }),
          ],
        }),
      parentEpic &&
        jsx(FormField, {
          title: '',
          children: jsxs(Text, {
            size: 'sm',
            variant: 'secondary',
            children: ['Parent epic: &', parentEpic.iid, ' ', parentEpic.title],
          }),
        }),
      jsx(FormField, {
        error,
        hint: message,
        title: '',
        children: jsx(FormRow, {
          children: jsx(ButtonField, {
            create: actions.submit,
            isLoading,
            reset: actions.reset,
          }),
        }),
      }),
    ],
  });
}

// apps/gitlab-plus/src/components/create-issue/events.ts
const showRelatedIssueModal = 'glp-show-create-issue-modal';
const showChildIssueModal = 'glp-show-create-child-issue-modal';
const ShowRelatedIssueModalEvent = new CustomEvent(showRelatedIssueModal);
const ShowChildIssueModalEvent = new CustomEvent(showChildIssueModal);

// apps/gitlab-plus/src/components/create-issue/CreateChildIssueModal.tsx
function CreateChildIssueModal({ link }) {
  const { isVisible, onClose } = useGlpModal(showChildIssueModal);
  return jsx(GlpModal, {
    isVisible,
    onClose,
    title: 'Create child issue',
    children: jsx(CreateIssueForm, { isVisible, link, onClose }),
  });
}

// apps/gitlab-plus/src/services/CreateChildIssue.tsx
class CreateChildIssue extends BaseService {
  constructor() {
    super(...arguments);
    __publicField(this, 'name', ServiceName.CreateChildIssue);
    __publicField(this, 'isMounted', false);
  }

  init() {
    this.mount();
    setTimeout(this.mount.bind(this), 1e3);
    setTimeout(this.mount.bind(this), 3e3);
  }

  mount() {
    if (this.isMounted) {
      return;
    }
    const link = LinkParser.parseEpicLink(window.location.href);
    const parent = document.querySelector(
      '#childitems [data-testid="crud-actions"]'
    );
    if (!link || !parent) {
      return;
    }
    this.isMounted = true;
    render(
      jsx(GitlabButton, {
        onClick: () => document.dispatchEvent(ShowChildIssueModalEvent),
        children: 'Create child item',
      }),
      this.root('glp-child-issue-button', parent, true)
    );
    render(
      jsx(CreateChildIssueModal, { link }),
      this.rootBody('glp-child-issue-modal')
    );
  }
}

// apps/gitlab-plus/src/components/create-issue/CreateRelatedIssueModal.tsx
function CreateRelatedIssueModal({ link }) {
  const { isVisible, onClose } = useGlpModal(showRelatedIssueModal);
  return jsx(GlpModal, {
    isVisible,
    onClose,
    title: 'Create related issue',
    children: jsx(CreateIssueForm, { isVisible, link, onClose }),
  });
}

// apps/gitlab-plus/src/services/CreateRelatedIssue.tsx
class CreateRelatedIssue extends BaseService {
  constructor() {
    super(...arguments);
    __publicField(this, 'name', ServiceName.CreateRelatedIssue);
    __publicField(this, 'isMounted', false);
  }

  init() {
    this.mount();
    setTimeout(this.mount.bind(this), 1e3);
    setTimeout(this.mount.bind(this), 3e3);
  }

  mount() {
    if (this.isMounted) {
      return;
    }
    const link = LinkParser.parseIssueLink(window.location.href);
    const parent = document.querySelector(
      '#related-issues [data-testid="crud-actions"]'
    );
    if (!link || !parent) {
      return;
    }
    this.isMounted = true;
    render(
      jsx(GitlabButton, {
        onClick: () => document.dispatchEvent(ShowRelatedIssueModalEvent),
        children: 'Create related issue',
      }),
      this.root('glp-related-issue-button', parent)
    );
    render(
      jsx(CreateRelatedIssueModal, { link }),
      this.rootBody('glp-related-issue-modal')
    );
  }
}

// apps/gitlab-plus/src/components/common/base/Row.tsx
function Row({ children, className, gap, items, justify }) {
  return jsx('div', {
    class: clsx(
      'gl-flex gl-flex-row',
      justify && `gl-justify-${justify}`,
      items && `gl-items-${items}`,
      gap && `gl-gap-${gap}`,
      className
    ),
    children,
  });
}

// libs/share/src/ui/Events.ts
class Events {
  static intendHover(validate, mouseover, mouseleave, timeout = 500) {
    let hover = false;
    let id = 0;
    const onHover = (event) => {
      if (!event.target || !validate(event.target)) {
        return;
      }
      const element = event.target;
      hover = true;
      element.addEventListener(
        'mouseleave',
        (ev) => {
          mouseleave == null ? void 0 : mouseleave.call(element, ev);
          clearTimeout(id);
          hover = false;
        },
        { once: true }
      );
      clearTimeout(id);
      id = window.setTimeout(() => {
        if (hover) {
          mouseover.call(element, event);
        }
      }, timeout);
    };
    document.body.addEventListener('mouseover', onHover);
  }
}

// apps/gitlab-plus/src/components/common/useOnLinkHover.ts
const modalZIndex = 1e3;

function useOnLinkHover(parser, validator) {
  const [hoverPosition, setHoverPosition] = useState({ x: 0, y: 0 });
  const [hoverLink, setHoverLink] = useState();
  const [zIndex, setZIndex] = useState(modalZIndex);
  const hoverLinkRef = useRef(false);
  const onHover = (event) => {
    const anchor = event.target;
    const link = parser(anchor.href);
    if (!link) {
      return;
    }
    anchor.title = '';
    setHoverLink(link);
    setZIndex(
      anchor.dataset.zIndex ? Number(anchor.dataset.zIndex) : modalZIndex
    );
    setHoverPosition({
      x: event.clientX + 15,
      y: event.clientY,
    });
  };
  useEffect(() => {
    Events.intendHover(
      (element) => validator(element.href),
      onHover,
      () => {
        setTimeout(() => {
          if (!hoverLinkRef.current) {
            setHoverLink(void 0);
          }
        }, 50);
      }
    );
  }, []);
  return {
    hoverLink,
    hoverPosition,
    onLinkEnter: () => (hoverLinkRef.current = true),
    onLinkLeave: () => {
      hoverLinkRef.current = false;
      setHoverLink(void 0);
    },
    zIndex,
  };
}

// apps/gitlab-plus/src/components/common/usePreviewModal.ts
function usePreviewModal(link, fetch2, reset, isLoading) {
  const [isVisible, setIsVisible] = useState(false);
  const [offset, setOffset] = useState({ x: 0, y: 0 });
  const ref = useRef(null);
  useEffect(() => {
    if (!isLoading) {
      setTimeout(() => {
        const rect = ref.current.getBoundingClientRect();
        const dY = rect.height + rect.top - window.innerHeight;
        const dX = rect.width + rect.left - window.innerWidth;
        setOffset({
          x: dX > 0 ? dX + 15 : 0,
          y: dY > 0 ? dY + 15 : 0,
        });
      }, 300);
    }
  }, [isLoading]);
  useEffect(() => {
    if (!isVisible) {
      setOffset({ x: 0, y: 0 });
    }
  }, [isVisible]);
  useEffect(() => {
    if (link) {
      fetch2(link);
      setIsVisible(true);
    } else {
      setIsVisible(false);
      reset();
    }
  }, [link]);
  return {
    isVisible,
    offset,
    ref,
  };
}

// apps/gitlab-plus/src/components/common/PreviewModal.tsx
function PreviewModal({
  validator,
  children,
  fetch: fetch2,
  isError,
  isLoading = false,
  isRefreshing = false,
  parser,
  reset,
}) {
  const { hoverLink, hoverPosition, onLinkEnter, onLinkLeave, zIndex } =
    useOnLinkHover(parser, validator);
  const { isVisible, offset, ref } = usePreviewModal(
    hoverLink,
    fetch2,
    reset,
    isLoading
  );
  const content = useMemo(() => {
    if (isLoading || !isVisible) {
      return jsx(Row, {
        className: 'gl-flex-1',
        items: 'center',
        justify: 'center',
        children: jsx(GitlabLoader, { size: '3em' }),
      });
    }
    if (isError) {
      return jsx(Row, {
        className: 'gl-flex-1',
        items: 'center',
        justify: 'center',
        children: 'Error',
      });
    }
    return jsxs('div', {
      className: 'gl-flex gl-w-full gl-flex-col',
      children: [
        children,
        isRefreshing &&
          jsx(Row, {
            className: 'gl-h-full gl-w-full gl-absolute gl-bg-overlay',
            items: 'center',
            justify: 'center',
            children: jsx(GitlabLoader, { size: '3em' }),
          }),
      ],
    });
  }, [isLoading, isRefreshing, isError, isVisible, children]);
  return jsx('div', {
    className: clsx('glp-preview-modal', isVisible && 'glp-modal-visible'),
    onMouseEnter: onLinkEnter,
    onMouseLeave: onLinkLeave,
    ref,
    style: {
      left: hoverPosition.x,
      top: hoverPosition.y,
      transform: `translate(-${offset.x}px, -${offset.y}px )`,
      zIndex,
    },
    children: content,
  });
}

// apps/gitlab-plus/src/components/common/block/HeadingBlock.tsx
function HeadingBlock({
  author,
  badge,
  createdAt,
  entityId,
  icon,
  onRefresh,
  title,
}) {
  return jsxs('div', {
    className: 'glp-block gl-relative',
    children: [
      jsxs(Row, {
        className: '',
        items: 'center',
        justify: 'between',
        children: [
          jsx('span', {
            className: clsx(
              'gl-font-bold gl-leading-20 gl-text-gray-900',
              onRefresh && 'gl-pr-5'
            ),
            children: title,
          }),
          onRefresh &&
            jsx('div', {
              onClick: onRefresh,
              className:
                'gl-absolute gl-right-0 gl-top-0 gl-p-2 gl-cursor-pointer',
              children: jsx(GitlabIcon, { icon: 'repeat' }),
            }),
        ],
      }),
      jsxs(Row, {
        className: 'gl-mt-2',
        gap: 2,
        items: 'center',
        children: [
          jsxs(Row, {
            gap: 2,
            items: 'center',
            children: [
              jsx(GitlabIcon, { icon, size: 16 }),
              jsx(Text, {
                size: 'sm',
                variant: 'secondary',
                weight: 'bold',
                children: entityId,
              }),
            ],
          }),
          badge,
        ],
      }),
      jsxs(Row, {
        className: 'gl-mt-1',
        gap: 2,
        items: 'center',
        children: [
          jsx(Text, {
            size: 'sm',
            variant: 'secondary',
            children: 'Created at',
          }),
          jsx(Text, {
            size: 'sm',
            weight: 'bold',
            children: new Date(createdAt).toLocaleDateString(),
          }),
          jsx(Text, { size: 'sm', variant: 'secondary', children: 'by' }),
          jsx(GitlabUser, {
            size: 16,
            user: author,
            smallText: true,
            withLink: true,
          }),
        ],
      }),
    ],
  });
}

// apps/gitlab-plus/src/components/common/GitlabBadge.tsx
function GitlabBadge({ icon, label, title, variant }) {
  return jsxs('span', {
    className: `gl-badge badge badge-pill badge-${variant}`,
    title,
    children: [
      icon && jsx(GitlabIcon, { icon }),
      label &&
        jsx('span', {
          className: 'gl-badge-content',
          children: label,
        }),
    ],
  });
}

// apps/gitlab-plus/src/components/common/IssueStatus.tsx
function IssueStatus({ isOpen }) {
  return jsx(GitlabBadge, {
    icon: isOpen ? 'issue-open-m' : 'issue-close',
    label: isOpen ? 'Open' : 'Closed',
    variant: isOpen ? 'success' : 'info',
  });
}

// apps/gitlab-plus/src/components/epic-preview/blocks/EpicHeading.tsx
function EpicHeader({ epic, onRefresh }) {
  return jsx(HeadingBlock, {
    author: epic.author,
    badge: jsx(IssueStatus, { isOpen: epic.state === 'OPEN' }),
    createdAt: epic.createdAt,
    entityId: `&${epic.iid}`,
    icon: 'epic',
    onRefresh,
    title: epic.title,
  });
}

// apps/gitlab-plus/src/components/common/block/InfoBlock.tsx
function InfoBlock({ children, className, icon, rightTitle, title }) {
  return jsxs('div', {
    class: 'glp-block gl-relative',
    children: [
      jsxs(Row, {
        items: 'center',
        justify: 'between',
        children: [
          jsxs(Row, {
            gap: 2,
            items: 'center',
            children: [
              icon && jsx(GitlabIcon, { icon, size: 16 }),
              jsx('span', {
                className: 'gl-font-bold gl-leading-20 gl-text-gray-900',
                dangerouslySetInnerHTML: { __html: title },
              }),
            ],
          }),
          rightTitle,
        ],
      }),
      jsx('div', { class: className, children }),
    ],
  });
}

// apps/gitlab-plus/src/components/common/block/LabelsBlockChangeStatus.tsx
function LabelsBlockChangeStatus({
  isLoading,
  name: name2,
  onChange,
  options,
}) {
  if (isLoading) {
    return jsx(GitlabLoader, {});
  }
  const getValues = useCallback(
    async (search) => {
      return options.filter((option) => option.title.includes(search));
    },
    [options]
  );
  const renderOption = useCallback((item) => {
    return jsxs('div', {
      class: 'gl-flex gl-flex-1 gl-break-anywhere gl-pb-3 gl-pl-4 gl-pt-3',
      children: [
        jsx('span', {
          class: 'dropdown-label-box gl-top-0 gl-mr-3 gl-shrink-0',
          style: { backgroundColor: item.color },
        }),
        jsx('span', { children: item.title }),
      ],
    });
  }, []);
  return jsx('div', {
    className: 'gl-py-2',
    style: { width: 130 },
    children: jsx(AsyncAutocomplete, {
      hideCheckbox: true,
      buttonSize: 'sm',
      getValues,
      name: name2,
      onChange: ([label]) => label && onChange(label),
      renderLabel: () => 'Change status',
      renderOption,
      value: [],
    }),
  });
}

// apps/gitlab-plus/src/components/common/block/useLabelBlock.ts
const name = 'status-labels';

function useLabelBlock(statusUpdate) {
  const [isLoading, setIsLoading] = useState(false);
  const onSelectStatus = useCallback(async (label) => {
    setIsLoading(true);
    if (statusUpdate) {
      await statusUpdate.update(label);
      new RecentlyProvider(name).add(label);
    }
    setIsLoading(false);
  }, []);
  return {
    isLoading,
    name,
    onSelectStatus,
    showChangeStatusComponent: Boolean(statusUpdate),
    statusLabels: (statusUpdate == null ? void 0 : statusUpdate.labels) || [],
  };
}

// apps/gitlab-plus/src/components/common/block/LabelsBlock.tsx
function LabelsBlock({ labels: labels2, updateStatus }) {
  const {
    isLoading,
    name: name2,
    onSelectStatus,
    showChangeStatusComponent,
    statusLabels,
  } = useLabelBlock(updateStatus);
  if (!labels2.length && !updateStatus) {
    return null;
  }
  return jsx(InfoBlock, {
    className: 'issuable-show-labels',
    icon: 'labels',
    title: 'Labels',
    rightTitle:
      showChangeStatusComponent &&
      jsx(LabelsBlockChangeStatus, {
        isLoading,
        name: name2,
        onChange: onSelectStatus,
        options: statusLabels,
      }),
    children: labels2.map((label) => jsx(GitlabLabel, { label }, label.id)),
  });
}

// apps/gitlab-plus/src/components/epic-preview/blocks/useEpicLabels.ts
function useEpicLabels(epic, refetch) {
  const [statusLabels, setStatusLabels] = useState([]);
  const labels2 = useMemo(() => {
    const labelWidget = epic.widgets.find((widget) => widget.type === 'LABELS');
    if (labelWidget) {
      return labelWidget.labels.nodes;
    }
    return [];
  }, [epic]);
  const onStatusChange = useCallback(
    async (label) => {
      const oldStatus = labels2.filter((l) => l.title.includes('Status::'));
      await new EpicProvider().updateEpicLabels(
        epic.id,
        [label.id],
        oldStatus.map((l) => l.id)
      );
      if (refetch) {
        await refetch();
      }
    },
    [labels2]
  );
  const fetchLabels = useCallback(async (workspacePath) => {
    const response = await new LabelsProvider().getWorkspaceLabels(
      workspacePath,
      'Status::'
    );
    setStatusLabels(response.data.workspace.labels.nodes);
  }, []);
  useEffect(() => {
    fetchLabels(epic.namespace.fullPath);
  }, []);
  return {
    labels: labels2,
    updateStatus: {
      labels: statusLabels,
      update: onStatusChange,
    },
  };
}

// apps/gitlab-plus/src/components/epic-preview/blocks/EpicLabels.tsx
function EpicLabels({ epic, refresh }) {
  const { labels: labels2, updateStatus } = useEpicLabels(epic, refresh);
  if (!labels2.length) {
    return null;
  }
  return jsx(LabelsBlock, { labels: labels2, updateStatus });
}

// apps/gitlab-plus/src/components/common/base/Link.tsx
function Link({ blockHover, children, className, href, inline, title }) {
  const [zIndex, setZIndex] = useState(modalZIndex + 1);
  const ref = useRef(null);
  const onHover = (e) => {
    e.stopPropagation();
    e.preventDefault();
    return false;
  };
  useLayoutEffect(() => {
    let _a;
    const modal =
      (_a = ref.current) == null ? void 0 : _a.closest('.glp-preview-modal');
    setZIndex(
      (modal == null ? void 0 : modal.style.zIndex)
        ? Number(modal.style.zIndex) + 1
        : modalZIndex + 1
    );
  }, []);
  return jsx('a', {
    'data-z-index': zIndex,
    href,
    onMouseOver: blockHover ? onHover : void 0,
    ref,
    target: '_blank',
    title,
    class: clsx(
      inline ? 'gl-inline' : 'gl-block',
      'gl-link sortable-link',
      className
    ),
    style: {
      overflow: 'hidden',
      textOverflow: 'ellipsis',
    },
    children,
  });
}

// apps/gitlab-plus/src/components/epic-preview/blocks/EpicRelatedIssues.tsx
function EpicRelatedIssues({ epic }) {
  const issues = useMemo(() => {
    const hierarchyWidget = epic.widgets.find(
      (widget) => widget.type === 'HIERARCHY'
    );
    if (!hierarchyWidget) {
      return [];
    }
    return hierarchyWidget.children.nodes;
  }, [epic]);
  if (!issues.length) {
    return null;
  }
  return jsx(InfoBlock, {
    icon: 'issue-type-issue',
    title: `Child issues (${issues.length})`,
    children: issues.map((issue) =>
      jsxs(
        Link,
        {
          href: issue.webUrl,
          title: issue.title,
          children: ['#', issue.iid, ' ', issue.title],
        },
        issue.iid
      )
    ),
  });
}

// apps/gitlab-plus/src/components/common/useFetchEntity.ts
function useFetchEntity(fetcher) {
  const [entityData, setEntityData] = useState(null);
  const [isLoading, setIsLoading] = useState(false);
  const [isRefreshing, setIsRefreshing] = useState(false);
  const fetch2 = async (link, force = false) => {
    if (force) {
      setIsRefreshing(true);
    } else {
      setIsLoading(true);
    }
    const entity = await fetcher(link, force);
    setEntityData({ entity, link });
    setIsRefreshing(false);
    setIsLoading(false);
  };
  const reset = () => {
    setEntityData(null);
    setIsRefreshing(false);
    setIsLoading(false);
  };
  return {
    entityData,
    fetch: fetch2,
    isLoading,
    isRefreshing,
    reset,
  };
}

// apps/gitlab-plus/src/components/epic-preview/useFetchEpic.ts
function useFetchEpic() {
  return useFetchEntity(async (link, force = false) => {
    const response = await new EpicProvider(force).getEpic(
      link.workspacePath,
      link.epic
    );
    return response.data.workspace.workItem;
  });
}

// apps/gitlab-plus/src/components/epic-preview/EpicPreviewModal.tsx
function EpicPreviewModal() {
  const {
    entityData,
    fetch: fetch2,
    isLoading,
    isRefreshing,
    reset,
  } = useFetchEpic();
  return jsx(PreviewModal, {
    validator: LinkParser.validateEpicLink,
    fetch: fetch2,
    isError: !entityData,
    isLoading,
    isRefreshing,
    parser: LinkParser.parseEpicLink,
    reset,
    children:
      entityData &&
      jsxs(Fragment, {
        children: [
          jsx(EpicHeader, {
            epic: entityData.entity,
            onRefresh: () => fetch2(entityData.link, true),
          }),
          jsx(EpicLabels, {
            epic: entityData.entity,
            refresh: () => fetch2(entityData.link, true),
          }),
          jsx(EpicRelatedIssues, { epic: entityData.entity }),
        ],
      }),
  });
}

// apps/gitlab-plus/src/services/EpicPreview.tsx
class EpicPreview extends BaseService {
  constructor() {
    super(...arguments);
    __publicField(this, 'name', ServiceName.EpicPreview);
  }

  init() {
    render(jsx(EpicPreviewModal, {}), this.rootBody('glp-epic-preview-root'));
  }
}

// apps/gitlab-plus/src/components/image-preview/useImagePreviewModal.ts
function useImagePreviewModal() {
  const [src, setSrc] = useState('');
  const validate = (element) => {
    return (
      element.classList.contains('no-attachment-icon') &&
      /\.(png|jpg|jpeg|heic)$/.test(element.href.toLowerCase())
    );
  };
  const getAnchor = (element) => {
    if (!element) {
      return void 0;
    }
    if (element instanceof HTMLAnchorElement) {
      return validate(element) ? element : void 0;
    }
    if (
      element instanceof HTMLImageElement &&
      element.parentElement instanceof HTMLAnchorElement
    ) {
      return validate(element.parentElement) ? element.parentElement : void 0;
    }
    return void 0;
  };
  useEffect(() => {
    document.body.addEventListener('click', (ev) => {
      const anchor = getAnchor(ev.target);
      if (anchor) {
        setSrc(anchor.href);
        ev.preventDefault();
        ev.stopPropagation();
        return false;
      }
    });
  }, []);
  return {
    onClose: () => setSrc(''),
    src,
  };
}

// apps/gitlab-plus/src/components/image-preview/ImagePreviewModal.tsx
function ImagePreviewModal() {
  const { onClose, src } = useImagePreviewModal();
  return jsxs('div', {
    className: clsx(
      'glp-image-preview-modal',
      Boolean(src) && 'glp-modal-visible'
    ),
    children: [
      jsx('img', { alt: 'Image preview', className: 'glp-modal-img', src }),
      jsx('div', {
        className: 'glp-modal-close',
        onClick: onClose,
        children: jsx(GitlabIcon, { icon: 'close-xs', size: 24 }),
      }),
    ],
  });
}

// apps/gitlab-plus/src/services/ImagePreview.tsx
class ImagePreview extends BaseService {
  constructor() {
    super(...arguments);
    __publicField(this, 'name', ServiceName.ImagePreview);
  }

  init() {
    render(jsx(ImagePreviewModal, {}), this.rootBody('glp-image-preview-root'));
  }
}

// apps/gitlab-plus/src/components/common/block/UsersBlock.tsx
function UsersBlock({ assignees, icon, label, pluralIcon, pluralLabel }) {
  if (!assignees || !assignees.length) {
    return null;
  }
  if (assignees.length === 1) {
    return jsx(InfoBlock, {
      className: 'gl-flex gl-flex-col gl-gap-3',
      icon: icon || 'user',
      rightTitle: jsx(GitlabUser, { user: assignees[0], withLink: true }),
      title: `${label}:`,
    });
  }
  return jsx(InfoBlock, {
    className: 'gl-flex gl-flex-col gl-gap-3',
    icon: pluralIcon || icon || 'users',
    title: pluralLabel || `${label}s`,
    children: assignees.map((assignee) =>
      jsx(GitlabUser, { user: assignee, withLink: true }, assignee.id)
    ),
  });
}

// apps/gitlab-plus/src/components/issue-preview/blocks/IssueAssignee.tsx
function IssueAssignee({ issue }) {
  return jsx(UsersBlock, {
    assignees: issue.assignees.nodes,
    icon: 'assignee',
    label: 'Assignee',
  });
}

// apps/gitlab-plus/src/components/issue-preview/blocks/IssueEpic.tsx
function IssueEpic({ issue }) {
  if (!issue.epic) {
    return null;
  }
  return jsx(InfoBlock, {
    icon: 'epic',
    title: 'Epic',
    children: jsx(Link, {
      href: issue.epic.webUrl,
      title: issue.epic.title,
      children: issue.epic.title,
    }),
  });
}

// apps/gitlab-plus/src/components/issue-preview/blocks/IssueHeading.tsx
function IssueHeader({ issue, onRefresh }) {
  return jsx(HeadingBlock, {
    author: issue.author,
    badge: jsx(IssueStatus, { isOpen: issue.state === 'opened' }),
    createdAt: issue.createdAt,
    entityId: `#${issue.iid}`,
    icon: 'issue-type-issue',
    onRefresh,
    title: issue.title,
  });
}

// apps/gitlab-plus/src/components/issue-preview/blocks/IssueIteration.tsx
function IssueIteration({ issue }) {
  const label = useMemo(() => {
    let _a;
    const date = (date2) => {
      return new Intl.DateTimeFormat('en-US', {
        day: 'numeric',
        month: 'short',
      }).format(new Date(date2));
    };
    if (!issue.iteration) {
      return '';
    }
    return [
      (_a = issue.iteration.iterationCadence) == null ? void 0 : _a.title,
      ': ',
      date(issue.iteration.startDate),
      ' - ',
      date(issue.iteration.dueDate),
    ].join('');
  }, [issue]);
  if (!issue.iteration) {
    return null;
  }
  return jsx(InfoBlock, {
    icon: 'iteration',
    rightTitle: label,
    title: 'Iteration',
  });
}

// apps/gitlab-plus/src/components/issue-preview/blocks/useIssueLabels.ts
function useIssueLabels(issue, link, refetch) {
  const [statusLabels, setStatusLabels] = useState([]);
  const onStatusChange = useCallback(
    async (label) => {
      const statusLabel = issue.labels.nodes.find((l) =>
        l.title.includes('Status::')
      );
      const labels2 = statusLabel
        ? issue.labels.nodes.map((l) => (l.id === statusLabel.id ? label : l))
        : [...issue.labels.nodes, label];
      await new IssueProvider().issueSetLabels({
        iid: issue.iid,
        labelIds: labels2.map((l) => l.id),
        projectPath: link.projectPath,
      });
      if (refetch) {
        await refetch();
      }
    },
    [issue]
  );
  const fetchLabels = useCallback(async (projectPath) => {
    const response = await new LabelsProvider().getProjectLabels(
      projectPath,
      'Status::'
    );
    setStatusLabels(response.data.workspace.labels.nodes);
  }, []);
  useEffect(() => {
    fetchLabels(link.projectPath);
  }, []);
  return {
    labels: issue.labels.nodes,
    updateStatus: {
      labels: statusLabels,
      update: onStatusChange,
    },
  };
}

// apps/gitlab-plus/src/components/issue-preview/blocks/IssueLabels.tsx
function IssueLabels({ issue, link, refetch }) {
  const { labels: labels2, updateStatus } = useIssueLabels(
    issue,
    link,
    refetch
  );
  if (!labels2.length) {
    return null;
  }
  return jsx(LabelsBlock, { labels: labels2, updateStatus });
}

// apps/gitlab-plus/src/components/common/MrStatus.tsx
const iconMap = {
  closed: 'merge-request-close',
  locked: 'search',
  merged: 'merge',
  opened: 'merge-request',
};
const classMap = {
  closed: 'danger',
  locked: 'warning',
  merged: 'info',
  opened: 'success',
};
const labelMap = {
  closed: 'Closed',
  locked: 'Locked',
  merged: 'Merged',
  opened: 'Opened',
};

function MrStatus({ state, withIcon, withLabel }) {
  return jsx(GitlabBadge, {
    icon: withIcon ? iconMap[state] : void 0,
    label: withLabel ? labelMap[state] : void 0,
    variant: classMap[state],
  });
}

// apps/gitlab-plus/src/components/common/GitlabMergeRequest.tsx
function GitlabMergeRequest({ mr }) {
  return jsxs('div', {
    style: { marginTop: 10 },
    children: [
      jsxs(Row, {
        gap: 2,
        children: [
          jsx(MrStatus, { state: mr.state, withIcon: true, withLabel: true }),
          jsxs(Text, {
            variant: 'secondary',
            children: ['!', mr.iid],
          }),
          jsx(GitlabUser, { size: 16, user: mr.author, withLink: true }),
        ],
      }),
      jsx(Link, { href: mr.webUrl, title: mr.title, children: mr.title }),
    ],
  });
}

// apps/gitlab-plus/src/components/issue-preview/blocks/IssueMergeRequests.tsx
function IssueMergeRequests({ issue }) {
  if (!issue.relatedMergeRequests.nodes.length) {
    return null;
  }
  return jsx(InfoBlock, {
    icon: 'merge-request',
    title: 'Merge requests',
    children: issue.relatedMergeRequests.nodes.map((mr) =>
      jsx(GitlabMergeRequest, { mr }, mr.iid)
    ),
  });
}

// apps/gitlab-plus/src/components/issue-preview/blocks/IssueMilestone.tsx
function IssueMilestone({ issue }) {
  if (!issue.milestone) {
    return null;
  }
  return jsx(InfoBlock, {
    icon: 'milestone',
    rightTitle: issue.milestone.title,
    title: 'Milestone',
  });
}

// apps/gitlab-plus/src/components/issue-preview/blocks/IssueRelatedIssue.tsx
const relationMap = {
  blocks: 'Blocks:',
  is_blocked_by: 'Is blocked by:',
  relates_to: 'Related to:',
};

function IssueRelatedIssue({ issue }) {
  const groups = useMemo(() => {
    const initValue = {
      blocks: [],
      is_blocked_by: [],
      relates_to: [],
    };
    return Object.entries(
      issue.linkedWorkItems.nodes.reduce(
        (acc, issue2) => ({
          ...acc,
          [issue2.linkType]: [...acc[issue2.linkType], issue2],
        }),
        initValue
      )
    ).filter(([_, issues]) => issues.length);
  }, [issue]);
  if (!issue.linkedWorkItems.nodes.length) {
    return null;
  }
  return jsx(InfoBlock, {
    title: '',
    children: groups.map(([key, issues]) =>
      jsxs(
        'div',
        {
          style: { marginTop: 10 },
          children: [
            jsx('div', {
              class: 'item-title gl-flex gl-min-w-0 gl-gap-3',
              children: jsx('span', { children: relationMap[key] }),
            }),
            issues.map((issue2) =>
              jsxs(
                Link,
                {
                  href: issue2.workItem.webUrl,
                  blockHover: true,
                  children: [
                    '#',
                    issue2.workItem.iid,
                    ' ',
                    issue2.workItem.title,
                  ],
                },
                issue2.workItem.iid
              )
            ),
          ],
        },
        key
      )
    ),
  });
}

// apps/gitlab-plus/src/components/issue-preview/useFetchIssue.ts
function useFetchIssue() {
  return useFetchEntity(async (link, force = false) => {
    const response = await new IssueProvider(force).getIssue(
      link.projectPath,
      link.issue
    );
    console.log(response);
    return response.data.project.issue;
  });
}

// apps/gitlab-plus/src/components/issue-preview/IssuePreviewModal.tsx
function IssuePreviewModal() {
  const {
    entityData,
    fetch: fetch2,
    isLoading,
    isRefreshing,
    reset,
  } = useFetchIssue();
  return jsx(PreviewModal, {
    validator: LinkParser.validateIssueLink,
    fetch: fetch2,
    isError: !entityData,
    isLoading,
    isRefreshing,
    parser: LinkParser.parseIssueLink,
    reset,
    children:
      entityData &&
      jsxs(Fragment, {
        children: [
          jsx(IssueHeader, {
            issue: entityData.entity,
            onRefresh: () => fetch2(entityData.link, true),
          }),
          jsx(IssueAssignee, { issue: entityData.entity }),
          jsx(IssueLabels, {
            issue: entityData.entity,
            link: entityData.link,
            refetch: () => fetch2(entityData.link, true),
          }),
          jsx(IssueEpic, { issue: entityData.entity }),
          jsx(IssueMilestone, { issue: entityData.entity }),
          jsx(IssueIteration, { issue: entityData.entity }),
          jsx(IssueMergeRequests, { issue: entityData.entity }),
          jsx(IssueRelatedIssue, { issue: entityData.entity }),
        ],
      }),
  });
}

// apps/gitlab-plus/src/services/IssuePreview.tsx
class IssuePreview extends BaseService {
  constructor() {
    super(...arguments);
    __publicField(this, 'name', ServiceName.IssuePreview);
  }

  init() {
    render(jsx(IssuePreviewModal, {}), this.rootBody('glp-issue-preview-root'));
  }
}

// apps/gitlab-plus/src/components/mr-preview/blocks/MrApprovedBy.tsx
function MrApprovedBy({ mr }) {
  return jsx(UsersBlock, {
    assignees: mr.approvedBy.nodes,
    label: 'Approved by',
    pluralLabel: 'Approved by',
  });
}

// apps/gitlab-plus/src/components/mr-preview/blocks/MrAssignee.tsx
function MrAssignee({ mr }) {
  return jsx(UsersBlock, {
    assignees: mr.assignees.nodes,
    icon: 'assignee',
    label: 'Assignee',
  });
}

// apps/gitlab-plus/src/components/mr-preview/blocks/MrBranch.tsx
function MrBranch({ mr }) {
  return jsx(InfoBlock, {
    icon: 'branch',
    title: 'Merge',
    children: jsxs('span', {
      children: [
        jsx(Text, { children: mr.sourceBranch }),
        jsx(Text, {
          className: 'gl-mx-2',
          variant: 'secondary',
          children: 'in to',
        }),
        jsx(Text, { children: mr.targetBranch }),
      ],
    }),
  });
}

// apps/gitlab-plus/src/components/mr-preview/blocks/MrDiff.tsx
function MrDiff({ mr }) {
  const label = useMemo(() => {
    if (mr.diffStatsSummary.fileCount === 1) {
      return '1 file';
    }
    return `${mr.diffStatsSummary.fileCount} files`;
  }, [mr.diffStatsSummary.fileCount]);
  return jsx(InfoBlock, {
    icon: 'commit',
    title: `Commit: ${mr.commitCount}`,
    rightTitle: jsxs(Row, {
      gap: 2,
      items: 'center',
      children: [
        jsx(GitlabIcon, { icon: 'doc-code', size: 16 }),
        jsx(Text, {
          size: 'subtle',
          weight: 'bold',
          children: label,
        }),
        jsxs(Text, {
          color: 'success',
          weight: 'bold',
          children: ['+', mr.diffStatsSummary.additions],
        }),
        jsxs(Text, {
          color: 'danger',
          weight: 'bold',
          children: ['-', mr.diffStatsSummary.deletions],
        }),
      ],
    }),
  });
}

// apps/gitlab-plus/src/components/mr-preview/blocks/MrDiscussion.tsx
function MrDiscussion({ mr }) {
  const [resolved, total] = [
    mr.resolvedDiscussionsCount,
    mr.resolvableDiscussionsCount,
  ];
  if (!total) {
    return null;
  }
  const { label, title } = useMemo(() => {
    const plural = total !== 1 ? 's' : '';
    return {
      label: `${resolved} of ${total}`,
      title: `${resolved} of ${total} thread${plural} resolved`,
    };
  }, [mr]);
  return jsx(InfoBlock, {
    title: 'Discussion',
    rightTitle: jsx(GitlabBadge, {
      icon: 'comments',
      label,
      title,
      variant: resolved === total ? 'success' : 'muted',
    }),
  });
}

// libs/share/src/utils/textWithChild.ts
function textWithChild(text, pattern, replacer) {
  const matches = text.match(RegExp(pattern, 'g'));
  const parts = text.split(RegExp(pattern, 'g'));
  if (!(matches == null ? void 0 : matches.length)) {
    return text;
  }
  return parts.reduce((items, text2, index) => {
    const textToReplace = index < matches.length ? matches[index] : void 0;
    return [
      ...items,
      text2,
      ...(textToReplace ? [replacer(textToReplace)] : []),
    ];
  }, []);
}

// apps/gitlab-plus/src/components/mr-preview/blocks/MrHeading.tsx
function MrHeader({ mr, onRefresh }) {
  const title = useMemo(() => {
    const issueLink = (id) =>
      `${mr.project.webUrl}/-/issues/${id.replace(/\D+/g, '')}`;
    return textWithChild(mr.title, /#\d+/, (id) =>
      jsx(Link, { href: issueLink(id), inline: true, children: id })
    );
  }, [mr]);
  return jsx(HeadingBlock, {
    author: mr.author,
    createdAt: mr.createdAt,
    entityId: `!${mr.iid}`,
    icon: 'merge-request',
    onRefresh,
    title,
    badge: jsxs(Row, {
      className: 'gl-gap-2',
      items: 'center',
      children: [
        jsx(MrStatus, {
          state: mr.state,
          withIcon: true,
          withLabel: true,
        }),
        Boolean(mr.approvedBy.nodes.length) &&
          jsx(GitlabBadge, {
            icon: 'check-circle',
            label: 'Approved',
            variant: 'success',
          }),
        mr.conflicts &&
          jsx(GitlabIcon, {
            icon: 'warning-solid',
            size: 16,
            title: 'Merge request can not be merged',
          }),
      ],
    }),
  });
}

// apps/gitlab-plus/src/components/mr-preview/blocks/MrLabels.tsx
function MrLabels({ mr }) {
  if (!mr.labels.nodes.length) {
    return null;
  }
  return jsx(InfoBlock, {
    className: 'issuable-show-labels',
    title: 'Labels',
    children: mr.labels.nodes.map((label) =>
      jsx(GitlabLabel, { label }, label.id)
    ),
  });
}

// apps/gitlab-plus/src/providers/query/mr.ts
const mrQuery = `query MergeRequestQuery($fullPath: ID!, $iid: String!) {
  workspace: project(fullPath: $fullPath) {
    mergeRequest(iid: $iid) {
      id
      iid
      assignees {
        nodes {
          ...User
        }
      }
      approvedBy {
        nodes {
          ...User
        }
      }
      author {
        ...User
      }
      project {
        webUrl
        path
        fullPath
      }
      commitCount
      conflicts
      createdAt
      title
      titleHtml
      diffStatsSummary {
        additions
        changes
        deletions
        fileCount
      }
      draft
      labels {
        nodes {
          ...Label
        }
      }
      mergeable
      resolvedDiscussionsCount
      resolvableDiscussionsCount
      reviewers {
        nodes {
          ...User
        }
      }
      shouldBeRebased
      sourceBranch
      targetBranch
      state
      webUrl
    }
  }
}

${userFragment}
${labelFragment}
`;

// apps/gitlab-plus/src/providers/MrProvider.ts
class MrProvider extends GitlabProvider {
  async getMr(projectPath, mrId) {
    return this.queryCached(
      `mr-${projectPath}-${mrId}`,
      mrQuery,
      {
        iid: mrId,
        fullPath: projectPath,
      },
      2
    );
  }
}

// apps/gitlab-plus/src/components/mr-preview/useFetchMr.ts
function useFetchMr() {
  return useFetchEntity(async (link, force = false) => {
    const response = await new MrProvider(force).getMr(
      link.projectPath,
      link.mr
    );
    return response.data.workspace.mergeRequest;
  });
}

// apps/gitlab-plus/src/components/mr-preview/MrPreviewModal.tsx
function MrPreviewModal() {
  const {
    entityData,
    fetch: fetch2,
    isLoading,
    isRefreshing,
    reset,
  } = useFetchMr();
  return jsx(PreviewModal, {
    validator: LinkParser.validateMrLink,
    fetch: fetch2,
    isError: !entityData,
    isLoading,
    isRefreshing,
    parser: LinkParser.parseMrLink,
    reset,
    children:
      entityData &&
      jsxs(Fragment, {
        children: [
          jsx(MrHeader, {
            mr: entityData.entity,
            onRefresh: () => fetch2(entityData.link, true),
          }),
          jsx(MrBranch, { mr: entityData.entity }),
          jsx(MrAssignee, { mr: entityData.entity }),
          jsx(MrApprovedBy, { mr: entityData.entity }),
          jsx(MrLabels, { mr: entityData.entity }),
          jsx(MrDiff, { mr: entityData.entity }),
          jsx(MrDiscussion, { mr: entityData.entity }),
        ],
      }),
  });
}

// apps/gitlab-plus/src/services/MrPreview.tsx
class MrPreview extends BaseService {
  constructor() {
    super(...arguments);
    __publicField(this, 'name', ServiceName.MrPreview);
  }

  init() {
    render(jsx(MrPreviewModal, {}), this.rootBody('glp-mr-preview-root'));
  }
}

// apps/gitlab-plus/src/components/related-issue-autocomplete/useRelatedIssuesAutocompleteModal.ts
function useRelatedIssuesAutocompleteModal(link, input) {
  const [searchTerm, setSearchTerm] = useState('');
  const [isVisible, setIsVisible] = useState(false);
  const searchIssues = useCallback(async (term) => {
    const response = await new IssueProvider().getIssues(
      link.workspacePath,
      term
    );
    return [
      response.data.workspace.workItems,
      response.data.workspace.workItemsByIid,
      response.data.workspace.workItemsEmpty,
    ].flatMap((item) => (item == null ? void 0 : item.nodes) || []);
  }, []);
  const options = useAsyncAutocompleteOptions(searchTerm, searchIssues);
  const onSelect = (item) => {
    input.value = `${item.project.fullPath}#${item.iid} `;
    input.dispatchEvent(new Event('input'));
    input.dispatchEvent(new Event('change'));
  };
  useEffect(() => {
    document.body.addEventListener('click', (e) => {
      if (e.target !== input && !input.contains(e.target)) {
        setIsVisible(false);
      }
    });
    input.addEventListener('click', () => setIsVisible(true));
  }, []);
  return {
    isVisible,
    onClose: () => setIsVisible(false),
    onSelect,
    options,
    searchTerm,
    setSearchTerm,
  };
}

// apps/gitlab-plus/src/components/related-issue-autocomplete/RelatedIssuesAutocompleteModal.tsx
function RelatedIssuesAutocompleteModal({ input, link }) {
  const { isVisible, onClose, onSelect, options, searchTerm, setSearchTerm } =
    useRelatedIssuesAutocompleteModal(link, input);
  if (!isVisible) {
    return null;
  }
  return jsx('div', {
    class: 'gl-relative gl-w-full gl-new-dropdown !gl-block',
    children: jsx(AsyncAutocompleteDropdown, {
      hideCheckbox: true,
      onClick: onSelect,
      onClose,
      options,
      searchTerm,
      setSearchTerm,
      value: [],
      renderOption: (item) =>
        jsxs('div', {
          class: 'gl-flex gl-gap-x-2 gl-py-2',
          children: [
            jsx(GitlabIcon, {
              icon: 'issue-type-issue',
              size: 16,
            }),
            jsx('small', { children: item.iid }),
            jsx('span', {
              class: 'gl-flex gl-flex-wrap',
              children: item.title,
            }),
          ],
        }),
    }),
  });
}

// apps/gitlab-plus/src/services/RelatedIssueAutocomplete.tsx
class RelatedIssueAutocomplete extends BaseService {
  constructor() {
    super(...arguments);
    __publicField(this, 'name', ServiceName.RelatedIssueAutocomplete);
    __publicField(this, 'ready', false);
    __publicField(this, 'readyClass', 'glp-input-ready');
  }

  init() {
    this.initObserver();
    window.setTimeout(this.initObserver.bind(this), 1e3);
    window.setTimeout(this.initObserver.bind(this), 3e3);
    window.setTimeout(this.initObserver.bind(this), 5e3);
  }

  initAutocomplete(section) {
    const input = section.querySelector('#add-related-issues-form-input');
    const link = LinkParser.parseIssueLink(window.location.href);
    if (!input || this.isMounted(input) || !link) {
      return;
    }
    const container = input.closest('.add-issuable-form-input-wrapper');
    if (!container || document.querySelector('.related-issues-autocomplete')) {
      return;
    }
    const root = this.root('related-issues-autocomplete', container);
    render(jsx(RelatedIssuesAutocompleteModal, { input, link }), root);
  }

  initObserver() {
    const section = document.querySelector('#related-issues');
    if (this.ready || !section) {
      return;
    }
    this.ready = true;
    const observer = new MutationObserver((mutations) => {
      mutations.forEach((mutation) => {
        if (mutation.type === 'childList') {
          this.initAutocomplete(section);
        }
      });
    });
    observer.observe(section, {
      childList: true,
    });
  }

  isMounted(input) {
    return input.classList.contains(this.readyClass);
  }
}

// apps/gitlab-plus/src/services/RelatedIssuesLabelStatus.tsx
class RelatedIssuesLabelStatus extends BaseService {
  constructor() {
    super(...arguments);
    __publicField(this, 'name', ServiceName.RelatedIssuesLabelStatus);
    __publicField(this, 'ready', false);
  }

  init() {
    this.initIssuesList();
    window.setTimeout(this.initIssuesList.bind(this), 1e3);
    window.setTimeout(this.initIssuesList.bind(this), 3e3);
    window.setTimeout(this.initIssuesList.bind(this), 5e3);
  }

  initIssuesList() {
    if (this.ready) {
      return;
    }
    const lists = document.querySelectorAll(
      '#related-issues .related-items-list'
    );
    const link = LinkParser.parseIssueLink(window.location.href);
    if (!lists.length || !link) {
      return;
    }
    this.ready = true;
    const items = [...lists].flatMap((list) => [
      ...list.querySelectorAll('li'),
    ]);
    this.updateIssuesItem(link, items);
  }

  async updateIssuesItem(link, items) {
    const response = await new IssueProvider().getIssueWithRelatedIssuesLabels(
      link.projectPath,
      link.issue
    );
    const getStatusLabel = (item) => {
      const labelsWidget = item.workItem.widgets.find(
        (w) => w.type === 'LABELS'
      );
      return labelsWidget == null
        ? void 0
        : labelsWidget.labels.nodes.find(
            (l) =>
              l.title.toLowerCase().startsWith('status::') ||
              l.title.toLowerCase().startsWith('workflow::')
          );
    };
    const issueStatusMap =
      response.data.project.issue.linkedWorkItems.nodes.reduce((acc, value) => {
        return {
          ...acc,
          [value.workItem.id.replace(/\D/g, '')]: getStatusLabel(value),
        };
      }, {});
    items.forEach((item) => {
      if (!item.dataset.key || !issueStatusMap[item.dataset.key]) {
        return;
      }
      const statusLabel = issueStatusMap[item.dataset.key];
      const infoArea = item.querySelector('.item-attributes-area');
      if (infoArea && statusLabel) {
        render(
          jsx(GitlabLabel, { label: statusLabel }),
          this.root('glp-status-label', infoArea, true)
        );
      }
    });
  }
}

// libs/share/src/ui/Component.ts
class Component {
  constructor(tag, props = {}) {
    this.element = Dom.create({ tag, ...props });
  }

  addClassName(...className) {
    this.element.classList.add(...className);
  }

  event(event, callback) {
    this.element.addEventListener(event, callback);
  }

  getElement() {
    return this.element;
  }

  mount(parent) {
    parent.appendChild(this.element);
  }
}

// libs/share/src/ui/SvgComponent.ts
class SvgComponent {
  constructor(tag, props = {}) {
    this.element = Dom.createSvg({ tag, ...props });
  }

  addClassName(...className) {
    this.element.classList.add(...className);
  }

  event(event, callback) {
    this.element.addEventListener(event, callback);
  }

  getElement() {
    return this.element;
  }

  mount(parent) {
    parent.appendChild(this.element);
  }
}

// libs/share/src/ui/Dom.ts
class Dom {
  static appendChildren(element, children, isSvgMode = false) {
    if (children) {
      element.append(
        ...Dom.array(children).map((item) => {
          if (typeof item === 'string') {
            return document.createTextNode(item);
          }
          if (item instanceof HTMLElement || item instanceof SVGElement) {
            return item;
          }
          if (item instanceof Component || item instanceof SvgComponent) {
            return item.getElement();
          }
          const isSvg =
            'svg' === item.tag
              ? true
              : 'foreignObject' === item.tag
              ? false
              : isSvgMode;
          if (isSvg) {
            return Dom.createSvg(item);
          }
          return Dom.create(item);
        })
      );
    }
  }

  static applyAttrs(element, attrs) {
    if (attrs) {
      Object.entries(attrs).forEach(([key, value]) => {
        if (value === void 0 || value === false) {
          element.removeAttribute(key);
        } else {
          element.setAttribute(key, `${value}`);
        }
      });
    }
  }

  static applyClass(element, classes) {
    if (classes) {
      element.classList.add(...classes.split(' ').filter(Boolean));
    }
  }

  static applyEvents(element, events) {
    if (events) {
      Object.entries(events).forEach(([name2, callback]) => {
        element.addEventListener(name2, callback);
      });
    }
  }

  static applyStyles(element, styles) {
    if (styles) {
      Object.entries(styles).forEach(([key, value]) => {
        const name2 = key.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
        element.style.setProperty(name2, value);
      });
    }
  }

  static array(element) {
    return Array.isArray(element) ? element : [element];
  }

  static create(data) {
    const element = document.createElement(data.tag);
    Dom.appendChildren(element, data.children);
    Dom.applyClass(element, data.classes);
    Dom.applyAttrs(element, data.attrs);
    Dom.applyEvents(element, data.events);
    Dom.applyStyles(element, data.styles);
    return element;
  }

  static createSvg(data) {
    const element = document.createElementNS(
      'http://www.w3.org/2000/svg',
      data.tag
    );
    Dom.appendChildren(element, data.children, true);
    Dom.applyClass(element, data.classes);
    Dom.applyAttrs(element, data.attrs);
    Dom.applyEvents(element, data.events);
    Dom.applyStyles(element, data.styles);
    return element;
  }

  static element(tag, classes, children) {
    return Dom.create({ tag, children, classes });
  }

  static elementSvg(tag, classes, children) {
    return Dom.createSvg({ tag, children, classes });
  }
}

// libs/share/src/ui/Observer.ts
class Observer {
  start(element, callback, options) {
    this.stop();
    this.observer = new MutationObserver(callback);
    this.observer.observe(
      element,
      options || {
        attributeOldValue: true,
        attributes: true,
        characterData: true,
        characterDataOldValue: true,
        childList: true,
        subtree: true,
      }
    );
  }

  stop() {
    if (this.observer) {
      this.observer.disconnect();
    }
  }
}

// apps/gitlab-plus/src/services/SortIssue.ts
const sortWeight = {
  ['issue']: 4,
  ['label']: 0,
  ['ownIssue']: 10,
  ['ownUserStory']: 8,
  ['unknown']: 2,
  ['userStory']: 6,
};

class SortIssue extends BaseService {
  constructor() {
    super(...arguments);
    __publicField(this, 'name', ServiceName.SortIssue);
  }

  init() {
    const observer = new Observer();
    const userName = this.userName();
    const board = document.querySelector('.boards-list');
    if (!userName || !board) {
      return;
    }
    observer.start(board, () => this.run(userName));
  }

  childType(child, userName) {
    if (child instanceof HTMLDivElement) {
      return 'label';
    }
    const title = child.querySelector('[data-testid="board-card-title-link"]');
    if (!title) {
      return 'unknown';
    }
    const isOwn = [...child.querySelectorAll('.gl-avatar-link img')].some(
      (img) => img.alt.includes(userName)
    );
    const isUserStory = [...child.querySelectorAll('.gl-label')].some((span) =>
      span.innerText.includes('User Story')
    );
    if (isUserStory && isOwn) {
      return 'ownUserStory';
    }
    if (isOwn) {
      return 'ownIssue';
    }
    if (isUserStory) {
      return 'userStory';
    }
    return 'issue';
  }

  initBoard(board, userName) {
    Dom.applyClass(board, 'glp-ready');
    const observer = new Observer();
    observer.start(board, () => this.sortBoard(board, userName), {
      childList: true,
    });
  }

  run(userName) {
    [...document.querySelectorAll('.board-list:not(.glp-ready)')].forEach(
      (board) => this.initBoard(board, userName)
    );
  }

  shouldSort(items) {
    return items.some((item) => {
      return ['ownIssue', 'ownUserStory'].includes(item.type);
    });
  }

  sortBoard(board, userName) {
    Dom.applyStyles(board, {
      display: 'flex',
      flexDirection: 'column',
    });
    const children = [...board.children].map((element) => ({
      element,
      type: this.childType(element, userName),
    }));
    if (!this.shouldSort(children)) {
      return;
    }
    this.sortChildren(children).forEach(({ element }, index) => {
      const order =
        index !== children.length - 1 ? index + 1 : children.length + 100;
      element.style.order = `${order}`;
    });
  }

  sortChildren(items) {
    return items.toSorted((a, b) => {
      return Math.sign(sortWeight[b.type] - sortWeight[a.type]);
    });
  }

  userName() {
    const element = document.querySelector(
      '.user-bar-dropdown-toggle .gl-button-text .gl-sr-only'
    );
    const testText = ' user’s menu';
    if (element && element.innerText.includes(testText)) {
      return element.innerText.replace(testText, '');
    }
    return void 0;
  }
}

// apps/gitlab-plus/src/components/user-settings/events.ts
const showUserSettingsModal = 'glp-show-user-settings-modal';
const ShowUserSettingsModalEvent = new CustomEvent(showUserSettingsModal);

// apps/gitlab-plus/src/components/user-settings/UserSettingsButton.tsx
function UserSettingsButton() {
  return jsx('span', {
    className: 'gl-new-dropdown-item-content',
    onClick: () => document.dispatchEvent(ShowUserSettingsModalEvent),
    children: jsxs('span', {
      className: 'gl-new-dropdown-item-text-wrapper',
      children: [
        jsx('span', { style: { color: '#e24329' }, children: 'Gitlab Plus' }),
        ' settings',
      ],
    }),
  });
}

// apps/gitlab-plus/src/components/common/base/Column.tsx
function Column({ children, className, gap, items, justify }) {
  return jsx('div', {
    class: clsx(
      'gl-flex gl-flex-col',
      justify && `gl-justify-${justify}`,
      items && `gl-items-${items}`,
      gap && `gl-gap-${gap}`,
      className
    ),
    children,
  });
}

// apps/gitlab-plus/src/components/common/GitlabSwitch.tsx
function GitlabSwitch({ checked, disabled, onChange }) {
  return jsx('button', {
    'aria-checked': checked,
    'aria-disabled': disabled,
    disabled,
    onClick: () => onChange(!checked),
    role: 'switch',
    type: 'button',
    className: clsx(
      'gl-toggle gl-shrink-0',
      checked && 'is-checked',
      disabled && 'is-disabled'
    ),
    children: jsx('span', {
      className: 'toggle-icon',
      children: jsx(GitlabIcon, { icon: checked ? 'check-xs' : 'close-xs' }),
    }),
  });
}

// apps/gitlab-plus/src/components/user-settings/useUserSettingsModal.tsx
function useUserSettingsModal() {
  const [refreshFlag, setRefreshFlag] = useState(false);
  const services = useMemo(() => {
    return Object.entries(servicesConfig)
      .map(([name2, config]) => ({
        isActive: Boolean(userSettingsStore.isActive(name2)),
        isExperimental: config.experimental,
        isRequired: config.required,
        label: config.label,
        name: name2,
      }))
      .sort((a, b) => {
        if (a.isRequired || b.isRequired) {
          return a.isRequired ? 1 : -1;
        }
        if (a.isExperimental || b.isExperimental) {
          return a.isExperimental ? 1 : -1;
        }
        return a.name.localeCompare(b.name);
      });
  }, [refreshFlag]);
  return {
    services,
    setServiceState: (name2, value) => {
      userSettingsStore.setIsActive(name2, value);
      setRefreshFlag((flag) => !flag);
    },
  };
}

// apps/gitlab-plus/src/components/user-settings/UserSettingsModal.tsx
function UserSettingModal() {
  const { isVisible, onClose } = useGlpModal(showUserSettingsModal);
  const { services, setServiceState } = useUserSettingsModal();
  return jsx(GlpModal, {
    isVisible,
    onClose,
    title: jsxs(Fragment, {
      children: [
        jsx('span', { style: { color: '#e24329' }, children: 'Gitlab Plus' }),
        ' settings',
      ],
    }),
    children: jsx(Column, {
      className: 'gl-p-4',
      gap: 2,
      children: services.map((service) =>
        jsxs(Row, {
          gap: 2,
          items: 'center',
          children: [
            jsx(GitlabSwitch, {
              checked: service.isActive,
              disabled: service.isRequired,
              onChange: (value) => setServiceState(service.name, value),
            }),
            jsx(Text, {
              variant: service.isRequired ? 'secondary' : void 0,
              children: service.label,
            }),
            service.isExperimental &&
              jsx(GitlabBadge, {
                label: 'Experimental',
                variant: 'warning',
              }),
            service.isRequired &&
              jsx(GitlabBadge, { label: 'Required', variant: 'muted' }),
          ],
        })
      ),
    }),
  });
}

// apps/gitlab-plus/src/services/UserSettings.tsx
class UserSettings extends BaseService {
  constructor() {
    super(...arguments);
    __publicField(this, 'name', ServiceName.UserSettings);
    __publicField(this, 'ready', false);
  }

  init() {
    this.initUserSettings();
    window.setTimeout(this.initUserSettings.bind(this), 1e3);
    window.setTimeout(this.initUserSettings.bind(this), 3e3);
    window.setTimeout(this.initUserSettings.bind(this), 5e3);
  }

  getMenuItem() {
    const userMenu = document.querySelector('[data-testid="preferences-item"]');
    if (!userMenu || !userMenu.parentElement) {
      return void 0;
    }
    const li = document.createElement('li');
    li.className = 'gl-new-dropdown-item';
    userMenu.parentElement.append(li);
    return li;
  }

  initUserSettings() {
    if (this.ready) {
      return;
    }
    const userMenu = this.getMenuItem();
    if (!userMenu) {
      return;
    }
    this.ready = true;
    render(jsx(UserSettingsButton, {}), userMenu);
    render(jsx(UserSettingModal, {}), this.rootBody('glp-user-settings-root'));
  }
}

// apps/gitlab-plus/src/main.ts
[
  ClearCacheService,
  ImagePreview,
  MrPreview,
  EpicPreview,
  IssuePreview,
  CreateRelatedIssue,
  CreateChildIssue,
  RelatedIssueAutocomplete,
  RelatedIssuesLabelStatus,
  SortIssue,
  UserSettings,
].forEach((Service) => {
  const service = new Service();
  if (userSettingsStore.isActive(service.name)) {
    service.init();
  }
});