Skip to content
Snippets Groups Projects
Verified Commit 7dabca1b authored by Luke "Jared" Bennett's avatar Luke "Jared" Bennett
Browse files

Merge branch 'master' into droplab-templating-xss-fix

parents a695b855 c7680264
No related branches found
No related tags found
No related merge requests found
Showing
with 912 additions and 575 deletions
Loading
Loading
@@ -33,6 +33,7 @@
/* global Labels */
/* global Shortcuts */
/* global Sidebar */
/* global ShortcutsWiki */
 
import Issue from './issue';
 
Loading
Loading
@@ -46,6 +47,7 @@ import BlobLinePermalinkUpdater from './blob/blob_line_permalink_updater';
import BlobForkSuggestion from './blob/blob_fork_suggestion';
import UserCallout from './user_callout';
import { ProtectedTagCreate, ProtectedTagEditList } from './protected_tags';
import ShortcutsWiki from './shortcuts_wiki';
 
const ShortcutsBlob = require('./shortcuts_blob');
 
Loading
Loading
@@ -89,11 +91,13 @@ const ShortcutsBlob = require('./shortcuts_blob');
fileBlobPermalinkUrl,
});
 
new BlobForkSuggestion(
document.querySelector('.js-edit-blob-link-fork-toggler'),
document.querySelector('.js-cancel-fork-suggestion'),
document.querySelector('.js-file-fork-suggestion-section'),
);
new BlobForkSuggestion({
openButtons: document.querySelectorAll('.js-edit-blob-link-fork-toggler'),
forkButtons: document.querySelectorAll('.js-fork-suggestion-button'),
cancelButtons: document.querySelectorAll('.js-cancel-fork-suggestion-button'),
suggestionSections: document.querySelectorAll('.js-file-fork-suggestion-section'),
actionTextPieces: document.querySelectorAll('.js-file-fork-suggestion-section-action'),
});
}
 
switch (page) {
Loading
Loading
@@ -148,13 +152,13 @@ const ShortcutsBlob = require('./shortcuts_blob');
case 'projects:milestones:new':
case 'projects:milestones:edit':
case 'projects:milestones:update':
case 'groups:milestones:new':
case 'groups:milestones:edit':
case 'groups:milestones:update':
new ZenMode();
new gl.DueDateSelectors();
new gl.GLForm($('.milestone-form'));
break;
case 'groups:milestones:new':
new ZenMode();
break;
case 'projects:compare:show':
new gl.Diff();
break;
Loading
Loading
@@ -365,6 +369,9 @@ const ShortcutsBlob = require('./shortcuts_blob');
case 'admin':
new Admin();
switch (path[1]) {
case 'cohorts':
new gl.UsagePing();
break;
case 'groups':
new UsersSelect();
break;
Loading
Loading
@@ -416,7 +423,7 @@ const ShortcutsBlob = require('./shortcuts_blob');
break;
case 'wikis':
new gl.Wikis();
shortcut_handler = new ShortcutsNavigation();
shortcut_handler = new ShortcutsWiki();
new ZenMode();
new gl.GLForm($('.wiki-form'));
break;
Loading
Loading
Loading
Loading
@@ -3,6 +3,7 @@ const DATA_DROPDOWN = 'data-dropdown';
const SELECTED_CLASS = 'droplab-item-selected';
const ACTIVE_CLASS = 'droplab-item-active';
const TEMPLATE_REGEX = /\{\{(.+?)\}\}/g;
const IGNORE_CLASS = 'droplab-item-ignore';
 
export {
DATA_TRIGGER,
Loading
Loading
@@ -10,4 +11,5 @@ export {
SELECTED_CLASS,
ACTIVE_CLASS,
TEMPLATE_REGEX,
IGNORE_CLASS,
};
/* eslint-disable */
 
import utils from './utils';
import { SELECTED_CLASS } from './constants';
import { SELECTED_CLASS, IGNORE_CLASS } from './constants';
 
var DropDown = function(list) {
this.currentIndex = 0;
Loading
Loading
@@ -36,6 +36,7 @@ Object.assign(DropDown.prototype, {
 
clickEvent: function(e) {
if (e.target.tagName === 'UL') return;
if (e.target.classList.contains(IGNORE_CLASS)) return;
 
var selected = utils.closest(e.target, 'LI');
if (!selected) return;
Loading
Loading
Loading
Loading
@@ -38,6 +38,9 @@ window.DropzoneInput = (function() {
"opacity": 0,
"display": "none"
});
if (!project_uploads_path) return;
dropzone = form_dropzone.dropzone({
url: project_uploads_path,
dictDefaultMessage: "",
Loading
Loading
@@ -130,13 +133,15 @@ window.DropzoneInput = (function() {
var afterSelection, beforeSelection, caretEnd, caretStart, textEnd;
var formattedText = text;
if (shouldPad) formattedText += "\n\n";
caretStart = $(child)[0].selectionStart;
caretEnd = $(child)[0].selectionEnd;
const textarea = child.get(0);
caretStart = textarea.selectionStart;
caretEnd = textarea.selectionEnd;
textEnd = $(child).val().length;
beforeSelection = $(child).val().substring(0, caretStart);
afterSelection = $(child).val().substring(caretEnd, textEnd);
$(child).val(beforeSelection + formattedText + afterSelection);
child.get(0).setSelectionRange(caretStart + formattedText.length, caretEnd + formattedText.length);
textarea.setSelectionRange(caretStart + formattedText.length, caretEnd + formattedText.length);
textarea.style.height = `${textarea.scrollHeight}px`;
return form_textarea.trigger("input");
};
getFilename = function(e) {
Loading
Loading
@@ -180,7 +185,7 @@ window.DropzoneInput = (function() {
};
insertToTextArea = function(filename, url) {
return $(child).val(function(index, val) {
return val.replace("{{" + filename + "}}", url + "\n");
return val.replace("{{" + filename + "}}", url);
});
};
appendToTextArea = function(url) {
Loading
Loading
@@ -215,6 +220,7 @@ window.DropzoneInput = (function() {
form.find(".markdown-selector").click(function(e) {
e.preventDefault();
$(this).closest('.gfm-form').find('.div-dropzone').click();
form_textarea.focus();
});
}
 
Loading
Loading
Loading
Loading
@@ -115,11 +115,13 @@ class DueDateSelect {
this.$dropdown.trigger('loading.gl.dropdown');
this.$selectbox.hide();
this.$value.css('display', '');
const fadeOutLoader = () => {
this.$loading.fadeOut();
};
 
gl.issueBoards.BoardsStore.detail.issue.update(this.$dropdown.attr('data-issue-update'))
.then(() => {
this.$loading.fadeOut();
});
.then(fadeOutLoader)
.catch(fadeOutLoader);
}
 
submitSelectedDate(isDropdown) {
Loading
Loading
@@ -168,8 +170,9 @@ class DueDateSelectors {
const $datePicker = $(this);
const calendar = new Pikaday({
field: $datePicker.get(0),
theme: 'gitlab-theme',
theme: 'gitlab-theme animate-picker',
format: 'yyyy-mm-dd',
container: $datePicker.parent().get(0),
onSelect(dateText) {
$datePicker.val(dateFormat(new Date(dateText), 'yyyy-mm-dd'));
}
Loading
Loading
Loading
Loading
@@ -2,7 +2,7 @@
/* global Flash */
import Vue from 'vue';
import EnvironmentsService from '../services/environments_service';
import EnvironmentTable from './environments_table';
import EnvironmentTable from './environments_table.vue';
import EnvironmentsStore from '../stores/environments_store';
import TablePaginationComponent from '../../vue_shared/components/table_pagination';
import '../../lib/utils/common_utils';
Loading
Loading
<script>
/* global Flash */
/* eslint-disable no-new */
 
Loading
Loading
@@ -35,6 +36,8 @@ export default {
onClickAction(endpoint) {
this.isLoading = true;
 
$(this.$refs.tooltip).tooltip('destroy');
this.service.postAction(endpoint)
.then(() => {
this.isLoading = false;
Loading
Loading
@@ -54,44 +57,47 @@ export default {
return !action.playable;
},
},
};
</script>
<template>
<div
class="btn-group"
role="group">
<button
type="button"
class="dropdown btn btn-default dropdown-new js-dropdown-play-icon-container has-tooltip"
data-container="body"
data-toggle="dropdown"
ref="tooltip"
:title="title"
:aria-label="title"
:disabled="isLoading">
<span>
<span v-html="playIconSvg"></span>
<i
class="fa fa-caret-down"
aria-hidden="true"/>
<i
v-if="isLoading"
class="fa fa-spinner fa-spin"
aria-hidden="true"/>
</span>
</button>
 
template: `
<div class="btn-group" role="group">
<button
type="button"
class="dropdown btn btn-default dropdown-new js-dropdown-play-icon-container has-tooltip"
data-container="body"
data-toggle="dropdown"
:title="title"
:aria-label="title"
:disabled="isLoading">
<span>
<ul class="dropdown-menu dropdown-menu-align-right">
<li v-for="action in actions">
<button
type="button"
class="js-manual-action-link no-btn btn"
@click="onClickAction(action.play_path)"
:class="{ disabled: isActionDisabled(action) }"
:disabled="isActionDisabled(action)">
<span v-html="playIconSvg"></span>
<i
class="fa fa-caret-down"
aria-hidden="true"/>
<i
v-if="isLoading"
class="fa fa-spinner fa-spin"
aria-hidden="true"/>
</span>
</button>
<ul class="dropdown-menu dropdown-menu-align-right">
<li v-for="action in actions">
<button
type="button"
class="js-manual-action-link no-btn btn"
@click="onClickAction(action.play_path)"
:class="{ 'disabled': isActionDisabled(action) }"
:disabled="isActionDisabled(action)">
${playIconSvg}
<span>
{{action.name}}
</span>
</button>
</li>
</ul>
<span>
{{action.name}}
</span>
</button>
</li>
</ul>
</div>
`,
};
</template>
<script>
/**
* Renders the external url link in environments table.
*/
Loading
Loading
@@ -5,7 +6,7 @@ export default {
props: {
externalUrl: {
type: String,
default: '',
required: true,
},
},
 
Loading
Loading
@@ -14,17 +15,19 @@ export default {
return 'Open';
},
},
template: `
<a
class="btn external-url has-tooltip"
data-container="body"
:href="externalUrl"
target="_blank"
rel="noopener noreferrer nofollow"
:title="title"
:aria-label="title">
<i class="fa fa-external-link" aria-hidden="true"></i>
</a>
`,
};
</script>
<template>
<a
class="btn external-url has-tooltip"
data-container="body"
target="_blank"
rel="noopener noreferrer nofollow"
:title="title"
:aria-label="title"
:href="externalUrl">
<i
class="fa fa-external-link"
aria-hidden="true" />
</a>
</template>
<script>
import Timeago from 'timeago.js';
import '../../lib/utils/text_utility';
import ActionsComponent from './environment_actions';
import ExternalUrlComponent from './environment_external_url';
import StopComponent from './environment_stop';
import RollbackComponent from './environment_rollback';
import TerminalButtonComponent from './environment_terminal_button';
import MonitoringButtonComponent from './environment_monitoring';
import ActionsComponent from './environment_actions.vue';
import ExternalUrlComponent from './environment_external_url.vue';
import StopComponent from './environment_stop.vue';
import RollbackComponent from './environment_rollback.vue';
import TerminalButtonComponent from './environment_terminal_button.vue';
import MonitoringButtonComponent from './environment_monitoring.vue';
import CommitComponent from '../../vue_shared/components/commit';
import eventHub from '../event_hub';
 
Loading
Loading
@@ -434,117 +435,140 @@ export default {
eventHub.$emit('toggleFolder', this.model, this.folderUrl);
},
},
template: `
<tr :class="{ 'js-child-row': model.isChildren }">
<td>
<a v-if="!model.isFolder"
class="environment-name"
:class="{ 'prepend-left-default': model.isChildren }"
:href="environmentPath">
{{model.name}}
</a>
<span v-else
class="folder-name"
@click="onClickFolder"
role="button">
<span class="folder-icon">
<i
v-show="model.isOpen"
class="fa fa-caret-down"
aria-hidden="true" />
<i
v-show="!model.isOpen"
class="fa fa-caret-right"
aria-hidden="true"/>
</span>
<span class="folder-icon">
<i class="fa fa-folder" aria-hidden="true"></i>
</span>
<span>
{{model.folderName}}
</span>
<span class="badge">
{{model.size}}
</span>
};
</script>
<template>
<tr :class="{ 'js-child-row': model.isChildren }">
<td>
<a
v-if="!model.isFolder"
class="environment-name"
:class="{ 'prepend-left-default': model.isChildren }"
:href="environmentPath">
{{model.name}}
</a>
<span
v-else
class="folder-name"
@click="onClickFolder"
role="button">
<span class="folder-icon">
<i
v-show="model.isOpen"
class="fa fa-caret-down"
aria-hidden="true" />
<i
v-show="!model.isOpen"
class="fa fa-caret-right"
aria-hidden="true"/>
</span>
</td>
 
<td class="deployment-column">
<span v-if="shouldRenderDeploymentID">
{{deploymentInternalId}}
<span class="folder-icon">
<i
class="fa fa-folder"
aria-hidden="true" />
</span>
 
<span v-if="!model.isFolder && deploymentHasUser">
by
<a :href="deploymentUser.web_url" class="js-deploy-user-container">
<img class="avatar has-tooltip s20"
:src="deploymentUser.avatar_url"
:alt="userImageAltDescription"
:title="deploymentUser.username" />
</a>
<span>
{{model.folderName}}
</span>
</td>
 
<td class="environments-build-cell">
<a v-if="shouldRenderBuildName"
class="build-link"
:href="buildPath">
{{buildName}}
</a>
</td>
<td>
<div v-if="!model.isFolder && hasLastDeploymentKey" class="js-commit-component">
<commit-component
:tag="commitTag"
:commit-ref="commitRef"
:commit-url="commitUrl"
:short-sha="commitShortSha"
:title="commitTitle"
:author="commitAuthor"/>
</div>
<p v-if="!model.isFolder && !hasLastDeploymentKey" class="commit-title">
No deployments yet
</p>
</td>
<td>
<span v-if="!model.isFolder && canShowDate"
class="environment-created-date-timeago">
{{createdDate}}
<span class="badge">
{{model.size}}
</span>
</td>
<td class="environments-actions">
<div v-if="!model.isFolder" class="btn-group pull-right" role="group">
<actions-component v-if="hasManualActions && canCreateDeployment"
:service="service"
:actions="manualActions"/>
<external-url-component v-if="externalURL && canReadEnvironment"
:external-url="externalURL"/>
<monitoring-button-component v-if="monitoringUrl && canReadEnvironment"
:monitoring-url="monitoringUrl"/>
<terminal-button-component v-if="model && model.terminal_path"
:terminal-path="model.terminal_path"/>
<stop-component v-if="hasStopAction && canCreateDeployment"
:stop-url="model.stop_path"
:service="service"/>
<rollback-component v-if="canRetry && canCreateDeployment"
:is-last-deployment="isLastDeployment"
:retry-url="retryUrl"
:service="service"/>
</div>
</td>
</tr>
`,
};
</span>
</td>
<td class="deployment-column">
<span v-if="shouldRenderDeploymentID">
{{deploymentInternalId}}
</span>
<span v-if="!model.isFolder && deploymentHasUser">
by
<a
:href="deploymentUser.web_url"
class="js-deploy-user-container">
<img
class="avatar has-tooltip s20"
:src="deploymentUser.avatar_url"
:alt="userImageAltDescription"
:title="deploymentUser.username" />
</a>
</span>
</td>
<td class="environments-build-cell">
<a
v-if="shouldRenderBuildName"
class="build-link"
:href="buildPath">
{{buildName}}
</a>
</td>
<td>
<div
v-if="!model.isFolder && hasLastDeploymentKey"
class="js-commit-component">
<commit-component
:tag="commitTag"
:commit-ref="commitRef"
:commit-url="commitUrl"
:short-sha="commitShortSha"
:title="commitTitle"
:author="commitAuthor"/>
</div>
<p
v-if="!model.isFolder && !hasLastDeploymentKey"
class="commit-title">
No deployments yet
</p>
</td>
<td>
<span
v-if="!model.isFolder && canShowDate"
class="environment-created-date-timeago">
{{createdDate}}
</span>
</td>
<td class="environments-actions">
<div
v-if="!model.isFolder"
class="btn-group pull-right"
role="group">
<actions-component
v-if="hasManualActions && canCreateDeployment"
:service="service"
:actions="manualActions"/>
<external-url-component
v-if="externalURL && canReadEnvironment"
:external-url="externalURL"/>
<monitoring-button-component
v-if="monitoringUrl && canReadEnvironment"
:monitoring-url="monitoringUrl"/>
<terminal-button-component
v-if="model && model.terminal_path"
:terminal-path="model.terminal_path"/>
<stop-component
v-if="hasStopAction && canCreateDeployment"
:stop-url="model.stop_path"
:service="service"/>
<rollback-component
v-if="canRetry && canCreateDeployment"
:is-last-deployment="isLastDeployment"
:retry-url="retryUrl"
:service="service"/>
</div>
</td>
</tr>
</template>
<script>
/**
* Renders the Monitoring (Metrics) link in environments table.
*/
Loading
Loading
@@ -5,7 +6,6 @@ export default {
props: {
monitoringUrl: {
type: String,
default: '',
required: true,
},
},
Loading
Loading
@@ -15,17 +15,19 @@ export default {
return 'Monitoring';
},
},
template: `
<a
class="btn monitoring-url has-tooltip"
data-container="body"
:href="monitoringUrl"
target="_blank"
rel="noopener noreferrer nofollow"
:title="title"
:aria-label="title">
<i class="fa fa-area-chart" aria-hidden="true"></i>
</a>
`,
};
</script>
<template>
<a
class="btn monitoring-url has-tooltip"
data-container="body"
target="_blank"
rel="noopener noreferrer nofollow"
:href="monitoringUrl"
:title="title"
:aria-label="title">
<i
class="fa fa-area-chart"
aria-hidden="true" />
</a>
</template>
<script>
/* global Flash */
/* eslint-disable no-new */
/**
Loading
Loading
@@ -36,6 +37,8 @@ export default {
onClick() {
this.isLoading = true;
 
$(this.$el).tooltip('destroy');
this.service.postAction(this.retryUrl)
.then(() => {
this.isLoading = false;
Loading
Loading
@@ -47,21 +50,25 @@ export default {
});
},
},
};
</script>
<template>
<button
type="button"
class="btn"
@click="onClick"
:disabled="isLoading">
 
template: `
<button type="button"
class="btn"
@click="onClick"
:disabled="isLoading">
<span v-if="isLastDeployment">
Re-deploy
</span>
<span v-else>
Rollback
</span>
<span v-if="isLastDeployment">
Re-deploy
</span>
<span v-else>
Rollback
</span>
 
<i v-if="isLoading" class="fa fa-spinner fa-spin" aria-hidden="true"></i>
</button>
`,
};
<i
v-if="isLoading"
class="fa fa-spinner fa-spin"
aria-hidden="true" />
</button>
</template>
<script>
/* global Flash */
/* eslint-disable no-new, no-alert */
/**
Loading
Loading
@@ -36,6 +37,8 @@ export default {
if (confirm('Are you sure you want to stop this environment?')) {
this.isLoading = true;
 
$(this.$el).tooltip('destroy');
this.service.postAction(this.retryUrl)
.then(() => {
this.isLoading = false;
Loading
Loading
@@ -48,17 +51,23 @@ export default {
}
},
},
template: `
<button type="button"
class="btn stop-env-link has-tooltip"
data-container="body"
@click="onClick"
:disabled="isLoading"
:title="title"
:aria-label="title">
<i class="fa fa-stop stop-env-icon" aria-hidden="true"></i>
<i v-if="isLoading" class="fa fa-spinner fa-spin" aria-hidden="true"></i>
</button>
`,
};
</script>
<template>
<button
type="button"
class="btn stop-env-link has-tooltip"
data-container="body"
@click="onClick"
:disabled="isLoading"
:title="title"
:aria-label="title">
<i
class="fa fa-stop stop-env-icon"
aria-hidden="true" />
<i
v-if="isLoading"
class="fa fa-spinner fa-spin"
aria-hidden="true" />
</button>
</template>
<script>
/**
* Renders a terminal button to open a web terminal.
* Used in environments table.
Loading
Loading
@@ -24,14 +25,15 @@ export default {
return 'Terminal';
},
},
template: `
<a class="btn terminal-button has-tooltip"
data-container="body"
:title="title"
:aria-label="title"
:href="terminalPath">
${terminalIconSvg}
</a>
`,
};
</script>
<template>
<a
class="btn terminal-button has-tooltip"
data-container="body"
:title="title"
:aria-label="title"
:href="terminalPath"
v-html="terminalIconSvg">
</a>
</template>
<script>
/**
* Render environments table.
*/
import EnvironmentTableRowComponent from './environment_item';
import EnvironmentTableRowComponent from './environment_item.vue';
 
export default {
components: {
Loading
Loading
@@ -44,54 +45,73 @@ export default {
return `${window.location.pathname}/folders/${model.folderName}`;
},
},
};
</script>
<template>
<table class="table ci-table">
<thead>
<tr>
<th class="environments-name">
Environment
</th>
<th class="environments-deploy">
Last deployment
</th>
<th class="environments-build">
Job
</th>
<th class="environments-commit">
Commit
</th>
<th class="environments-date">
Updated
</th>
<th class="environments-actions"></th>
</tr>
</thead>
<tbody>
<template
v-for="model in environments"
v-bind:model="model">
<tr
is="environment-item"
:model="model"
:can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment"
:service="service" />
<template v-if="model.isFolder && model.isOpen && model.children && model.children.length > 0">
<tr v-if="isLoadingFolderContent">
<td colspan="6" class="text-center">
<i
class="fa fa-spin fa-spinner fa-2x"
aria-hidden="true" />
</td>
</tr>
 
template: `
<table class="table ci-table">
<thead>
<tr>
<th class="environments-name">Environment</th>
<th class="environments-deploy">Last deployment</th>
<th class="environments-build">Job</th>
<th class="environments-commit">Commit</th>
<th class="environments-date">Updated</th>
<th class="environments-actions"></th>
</tr>
</thead>
<tbody>
<template v-for="model in environments"
v-bind:model="model">
<tr is="environment-item"
:model="model"
:can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment"
:service="service"></tr>
<template v-else>
<tr
is="environment-item"
v-for="children in model.children"
:model="children"
:can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment"
:service="service" />
 
<template v-if="model.isFolder && model.isOpen && model.children && model.children.length > 0">
<tr v-if="isLoadingFolderContent">
<td colspan="6" class="text-center">
<i class="fa fa-spin fa-spinner fa-2x" aria-hidden="true"/>
<tr>
<td
colspan="6"
class="text-center">
<a
:href="folderUrl(model)"
class="btn btn-default">
Show all
</a>
</td>
</tr>
<template v-else>
<tr is="environment-item"
v-for="children in model.children"
:model="children"
:can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment"
:service="service"></tr>
<tr>
<td colspan="6" class="text-center">
<a :href="folderUrl(model)" class="btn btn-default">
Show all
</a>
</td>
</tr>
</template>
</template>
</template>
</tbody>
</table>
`,
};
</template>
</tbody>
</table>
</template>
Loading
Loading
@@ -2,7 +2,7 @@
/* global Flash */
import Vue from 'vue';
import EnvironmentsService from '../services/environments_service';
import EnvironmentTable from '../components/environments_table';
import EnvironmentTable from '../components/environments_table.vue';
import EnvironmentsStore from '../stores/environments_store';
import TablePaginationComponent from '../../vue_shared/components/table_pagination';
import '../../lib/utils/common_utils';
Loading
Loading
Loading
Loading
@@ -2,82 +2,80 @@ import Filter from '~/droplab/plugins/filter';
 
require('./filtered_search_dropdown');
 
(() => {
class DropdownHint extends gl.FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter) {
super(droplab, dropdown, input, filter);
this.config = {
Filter: {
template: 'hint',
filterFunction: gl.DropdownUtils.filterHint.bind(null, input),
},
};
}
itemClicked(e) {
const { selected } = e.detail;
class DropdownHint extends gl.FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter) {
super(droplab, dropdown, input, filter);
this.config = {
Filter: {
template: 'hint',
filterFunction: gl.DropdownUtils.filterHint.bind(null, input),
},
};
}
 
if (selected.tagName === 'LI') {
if (selected.hasAttribute('data-value')) {
this.dismissDropdown();
} else if (selected.getAttribute('data-action') === 'submit') {
this.dismissDropdown();
this.dispatchFormSubmitEvent();
} else {
const token = selected.querySelector('.js-filter-hint').innerText.trim();
const tag = selected.querySelector('.js-filter-tag').innerText.trim();
itemClicked(e) {
const { selected } = e.detail;
 
if (tag.length) {
// Get previous input values in the input field and convert them into visual tokens
const previousInputValues = this.input.value.split(' ');
const searchTerms = [];
if (selected.tagName === 'LI') {
if (selected.hasAttribute('data-value')) {
this.dismissDropdown();
} else if (selected.getAttribute('data-action') === 'submit') {
this.dismissDropdown();
this.dispatchFormSubmitEvent();
} else {
const token = selected.querySelector('.js-filter-hint').innerText.trim();
const tag = selected.querySelector('.js-filter-tag').innerText.trim();
 
previousInputValues.forEach((value, index) => {
searchTerms.push(value);
if (tag.length) {
// Get previous input values in the input field and convert them into visual tokens
const previousInputValues = this.input.value.split(' ');
const searchTerms = [];
 
if (index === previousInputValues.length - 1
&& token.indexOf(value.toLowerCase()) !== -1) {
searchTerms.pop();
}
});
previousInputValues.forEach((value, index) => {
searchTerms.push(value);
 
if (searchTerms.length > 0) {
gl.FilteredSearchVisualTokens.addSearchVisualToken(searchTerms.join(' '));
if (index === previousInputValues.length - 1
&& token.indexOf(value.toLowerCase()) !== -1) {
searchTerms.pop();
}
});
 
gl.FilteredSearchDropdownManager.addWordToInput(token.replace(':', ''), '', false, this.container);
if (searchTerms.length > 0) {
gl.FilteredSearchVisualTokens.addSearchVisualToken(searchTerms.join(' '));
}
this.dismissDropdown();
this.dispatchInputEvent();
gl.FilteredSearchDropdownManager.addWordToInput(token.replace(':', ''), '', false, this.container);
}
this.dismissDropdown();
this.dispatchInputEvent();
}
}
}
 
renderContent() {
const dropdownData = [];
renderContent() {
const dropdownData = [];
 
[].forEach.call(this.input.closest('.filtered-search-box-input-container').querySelectorAll('.dropdown-menu'), (dropdownMenu) => {
const { icon, hint, tag, type } = dropdownMenu.dataset;
if (icon && hint && tag) {
dropdownData.push(
Object.assign({
icon: `fa-${icon}`,
hint,
tag: `<${tag}>`,
}, type && { type }),
);
}
});
[].forEach.call(this.input.closest('.filtered-search-box-input-container').querySelectorAll('.dropdown-menu'), (dropdownMenu) => {
const { icon, hint, tag, type } = dropdownMenu.dataset;
if (icon && hint && tag) {
dropdownData.push(
Object.assign({
icon: `fa-${icon}`,
hint,
tag: `<${tag}>`,
}, type && { type }),
);
}
});
 
this.droplab.changeHookList(this.hookId, this.dropdown, [Filter], this.config);
this.droplab.setData(this.hookId, dropdownData);
}
this.droplab.changeHookList(this.hookId, this.dropdown, [Filter], this.config);
this.droplab.setData(this.hookId, dropdownData);
}
 
init() {
this.droplab.addHook(this.input, this.dropdown, [Filter], this.config).init();
}
init() {
this.droplab.addHook(this.input, this.dropdown, [Filter], this.config).init();
}
}
 
window.gl = window.gl || {};
gl.DropdownHint = DropdownHint;
})();
window.gl = window.gl || {};
gl.DropdownHint = DropdownHint;
Loading
Loading
@@ -5,48 +5,46 @@ import Filter from '~/droplab/plugins/filter';
 
require('./filtered_search_dropdown');
 
(() => {
class DropdownNonUser extends gl.FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter, endpoint, symbol) {
super(droplab, dropdown, input, filter);
this.symbol = symbol;
this.config = {
Ajax: {
endpoint,
method: 'setData',
loadingTemplate: this.loadingTemplate,
onError() {
/* eslint-disable no-new */
new Flash('An error occured fetching the dropdown data.');
/* eslint-enable no-new */
},
class DropdownNonUser extends gl.FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter, endpoint, symbol) {
super(droplab, dropdown, input, filter);
this.symbol = symbol;
this.config = {
Ajax: {
endpoint,
method: 'setData',
loadingTemplate: this.loadingTemplate,
onError() {
/* eslint-disable no-new */
new Flash('An error occured fetching the dropdown data.');
/* eslint-enable no-new */
},
Filter: {
filterFunction: gl.DropdownUtils.filterWithSymbol.bind(null, this.symbol, input),
template: 'title',
},
};
}
},
Filter: {
filterFunction: gl.DropdownUtils.filterWithSymbol.bind(null, this.symbol, input),
template: 'title',
},
};
}
 
itemClicked(e) {
super.itemClicked(e, (selected) => {
const title = selected.querySelector('.js-data-value').innerText.trim();
return `${this.symbol}${gl.DropdownUtils.getEscapedText(title)}`;
});
}
itemClicked(e) {
super.itemClicked(e, (selected) => {
const title = selected.querySelector('.js-data-value').innerText.trim();
return `${this.symbol}${gl.DropdownUtils.getEscapedText(title)}`;
});
}
 
renderContent(forceShowList = false) {
this.droplab
.changeHookList(this.hookId, this.dropdown, [Ajax, Filter], this.config);
super.renderContent(forceShowList);
}
renderContent(forceShowList = false) {
this.droplab
.changeHookList(this.hookId, this.dropdown, [Ajax, Filter], this.config);
super.renderContent(forceShowList);
}
 
init() {
this.droplab
.addHook(this.input, this.dropdown, [Ajax, Filter], this.config).init();
}
init() {
this.droplab
.addHook(this.input, this.dropdown, [Ajax, Filter], this.config).init();
}
}
 
window.gl = window.gl || {};
gl.DropdownNonUser = DropdownNonUser;
})();
window.gl = window.gl || {};
gl.DropdownNonUser = DropdownNonUser;
Loading
Loading
@@ -4,69 +4,67 @@ import AjaxFilter from '~/droplab/plugins/ajax_filter';
 
require('./filtered_search_dropdown');
 
(() => {
class DropdownUser extends gl.FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter) {
super(droplab, dropdown, input, filter);
this.config = {
AjaxFilter: {
endpoint: `${gon.relative_url_root || ''}/autocomplete/users.json`,
searchKey: 'search',
params: {
per_page: 20,
active: true,
project_id: this.getProjectId(),
current_user: true,
},
searchValueFunction: this.getSearchInput.bind(this),
loadingTemplate: this.loadingTemplate,
onError() {
/* eslint-disable no-new */
new Flash('An error occured fetching the dropdown data.');
/* eslint-enable no-new */
},
class DropdownUser extends gl.FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter) {
super(droplab, dropdown, input, filter);
this.config = {
AjaxFilter: {
endpoint: `${gon.relative_url_root || ''}/autocomplete/users.json`,
searchKey: 'search',
params: {
per_page: 20,
active: true,
project_id: this.getProjectId(),
current_user: true,
},
};
}
itemClicked(e) {
super.itemClicked(e,
selected => selected.querySelector('.dropdown-light-content').innerText.trim());
}
renderContent(forceShowList = false) {
this.droplab.changeHookList(this.hookId, this.dropdown, [AjaxFilter], this.config);
super.renderContent(forceShowList);
}
searchValueFunction: this.getSearchInput.bind(this),
loadingTemplate: this.loadingTemplate,
onError() {
/* eslint-disable no-new */
new Flash('An error occured fetching the dropdown data.');
/* eslint-enable no-new */
},
},
};
}
 
getProjectId() {
return this.input.getAttribute('data-project-id');
}
itemClicked(e) {
super.itemClicked(e,
selected => selected.querySelector('.dropdown-light-content').innerText.trim());
}
 
getSearchInput() {
const query = gl.DropdownUtils.getSearchInput(this.input);
const { lastToken } = gl.FilteredSearchTokenizer.processTokens(query);
renderContent(forceShowList = false) {
this.droplab.changeHookList(this.hookId, this.dropdown, [AjaxFilter], this.config);
super.renderContent(forceShowList);
}
 
let value = lastToken || '';
getProjectId() {
return this.input.getAttribute('data-project-id');
}
 
if (value[0] === '@') {
value = value.slice(1);
}
getSearchInput() {
const query = gl.DropdownUtils.getSearchInput(this.input);
const { lastToken } = gl.FilteredSearchTokenizer.processTokens(query);
 
// Removes the first character if it is a quotation so that we can search
// with multiple words
if (value[0] === '"' || value[0] === '\'') {
value = value.slice(1);
}
let value = lastToken || '';
 
return value;
if (value[0] === '@') {
value = value.slice(1);
}
 
init() {
this.droplab.addHook(this.input, this.dropdown, [AjaxFilter], this.config).init();
// Removes the first character if it is a quotation so that we can search
// with multiple words
if (value[0] === '"' || value[0] === '\'') {
value = value.slice(1);
}
return value;
}
init() {
this.droplab.addHook(this.input, this.dropdown, [AjaxFilter], this.config).init();
}
}
 
window.gl = window.gl || {};
gl.DropdownUser = DropdownUser;
})();
window.gl = window.gl || {};
gl.DropdownUser = DropdownUser;
import FilteredSearchContainer from './container';
 
(() => {
class DropdownUtils {
static getEscapedText(text) {
let escapedText = text;
const hasSpace = text.indexOf(' ') !== -1;
const hasDoubleQuote = text.indexOf('"') !== -1;
// Encapsulate value with quotes if it has spaces
// Known side effect: values's with both single and double quotes
// won't escape properly
if (hasSpace) {
if (hasDoubleQuote) {
escapedText = `'${text}'`;
} else {
// Encapsulate singleQuotes or if it hasSpace
escapedText = `"${text}"`;
}
class DropdownUtils {
static getEscapedText(text) {
let escapedText = text;
const hasSpace = text.indexOf(' ') !== -1;
const hasDoubleQuote = text.indexOf('"') !== -1;
// Encapsulate value with quotes if it has spaces
// Known side effect: values's with both single and double quotes
// won't escape properly
if (hasSpace) {
if (hasDoubleQuote) {
escapedText = `'${text}'`;
} else {
// Encapsulate singleQuotes or if it hasSpace
escapedText = `"${text}"`;
}
return escapedText;
}
 
static filterWithSymbol(filterSymbol, input, item) {
const updatedItem = item;
const searchInput = gl.DropdownUtils.getSearchInput(input);
return escapedText;
}
static filterWithSymbol(filterSymbol, input, item) {
const updatedItem = item;
const searchInput = gl.DropdownUtils.getSearchInput(input);
 
const title = updatedItem.title.toLowerCase();
let value = searchInput.toLowerCase();
let symbol = '';
const title = updatedItem.title.toLowerCase();
let value = searchInput.toLowerCase();
let symbol = '';
 
// Remove the symbol for filter
if (value[0] === filterSymbol) {
symbol = value[0];
value = value.slice(1);
}
// Remove the symbol for filter
if (value[0] === filterSymbol) {
symbol = value[0];
value = value.slice(1);
}
 
// Removes the first character if it is a quotation so that we can search
// with multiple words
if ((value[0] === '"' || value[0] === '\'') && title.indexOf(' ') !== -1) {
value = value.slice(1);
}
// Removes the first character if it is a quotation so that we can search
// with multiple words
if ((value[0] === '"' || value[0] === '\'') && title.indexOf(' ') !== -1) {
value = value.slice(1);
}
// Eg. filterSymbol = ~ for labels
const matchWithoutSymbol = symbol === filterSymbol && title.indexOf(value) !== -1;
const match = title.indexOf(`${symbol}${value}`) !== -1;
 
// Eg. filterSymbol = ~ for labels
const matchWithoutSymbol = symbol === filterSymbol && title.indexOf(value) !== -1;
const match = title.indexOf(`${symbol}${value}`) !== -1;
updatedItem.droplab_hidden = !match && !matchWithoutSymbol;
 
updatedItem.droplab_hidden = !match && !matchWithoutSymbol;
return updatedItem;
}
 
return updatedItem;
static filterHint(input, item) {
const updatedItem = item;
const searchInput = gl.DropdownUtils.getSearchQuery(input);
const { lastToken, tokens } = gl.FilteredSearchTokenizer.processTokens(searchInput);
const lastKey = lastToken.key || lastToken || '';
const allowMultiple = item.type === 'array';
const itemInExistingTokens = tokens.some(t => t.key === item.hint);
if (!allowMultiple && itemInExistingTokens) {
updatedItem.droplab_hidden = true;
} else if (!lastKey || searchInput.split('').last() === ' ') {
updatedItem.droplab_hidden = false;
} else if (lastKey) {
const split = lastKey.split(':');
const tokenName = split[0].split(' ').last();
const match = updatedItem.hint.indexOf(tokenName.toLowerCase()) === -1;
updatedItem.droplab_hidden = tokenName ? match : false;
}
 
static filterHint(input, item) {
const updatedItem = item;
const searchInput = gl.DropdownUtils.getSearchQuery(input);
const { lastToken, tokens } = gl.FilteredSearchTokenizer.processTokens(searchInput);
const lastKey = lastToken.key || lastToken || '';
const allowMultiple = item.type === 'array';
const itemInExistingTokens = tokens.some(t => t.key === item.hint);
if (!allowMultiple && itemInExistingTokens) {
updatedItem.droplab_hidden = true;
} else if (!lastKey || searchInput.split('').last() === ' ') {
updatedItem.droplab_hidden = false;
} else if (lastKey) {
const split = lastKey.split(':');
const tokenName = split[0].split(' ').last();
const match = updatedItem.hint.indexOf(tokenName.toLowerCase()) === -1;
updatedItem.droplab_hidden = tokenName ? match : false;
}
return updatedItem;
}
static setDataValueIfSelected(filter, selected) {
const dataValue = selected.getAttribute('data-value');
 
return updatedItem;
if (dataValue) {
gl.FilteredSearchDropdownManager.addWordToInput(filter, dataValue, true);
}
 
static setDataValueIfSelected(filter, selected) {
const dataValue = selected.getAttribute('data-value');
// Return boolean based on whether it was set
return dataValue !== null;
}
 
if (dataValue) {
gl.FilteredSearchDropdownManager.addWordToInput(filter, dataValue, true);
}
// Determines the full search query (visual tokens + input)
static getSearchQuery(untilInput = false) {
const container = FilteredSearchContainer.container;
const tokens = [].slice.call(container.querySelectorAll('.tokens-container li'));
const values = [];
 
// Return boolean based on whether it was set
return dataValue !== null;
if (untilInput) {
const inputIndex = _.findIndex(tokens, t => t.classList.contains('input-token'));
// Add one to include input-token to the tokens array
tokens.splice(inputIndex + 1);
}
 
// Determines the full search query (visual tokens + input)
static getSearchQuery(untilInput = false) {
const container = FilteredSearchContainer.container;
const tokens = [].slice.call(container.querySelectorAll('.tokens-container li'));
const values = [];
tokens.forEach((token) => {
if (token.classList.contains('js-visual-token')) {
const name = token.querySelector('.name');
const value = token.querySelector('.value');
const symbol = value && value.dataset.symbol ? value.dataset.symbol : '';
let valueText = '';
 
if (untilInput) {
const inputIndex = _.findIndex(tokens, t => t.classList.contains('input-token'));
// Add one to include input-token to the tokens array
tokens.splice(inputIndex + 1);
}
if (value && value.innerText) {
valueText = value.innerText;
}
 
tokens.forEach((token) => {
if (token.classList.contains('js-visual-token')) {
const name = token.querySelector('.name');
const value = token.querySelector('.value');
const symbol = value && value.dataset.symbol ? value.dataset.symbol : '';
let valueText = '';
if (value && value.innerText) {
valueText = value.innerText;
}
if (token.className.indexOf('filtered-search-token') !== -1) {
values.push(`${name.innerText.toLowerCase()}:${symbol}${valueText}`);
} else {
values.push(name.innerText);
}
} else if (token.classList.contains('input-token')) {
const { isLastVisualTokenValid } =
gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
const input = FilteredSearchContainer.container.querySelector('.filtered-search');
const inputValue = input && input.value;
if (isLastVisualTokenValid) {
values.push(inputValue);
} else {
const previous = values.pop();
values.push(`${previous}${inputValue}`);
}
if (token.className.indexOf('filtered-search-token') !== -1) {
values.push(`${name.innerText.toLowerCase()}:${symbol}${valueText}`);
} else {
values.push(name.innerText);
}
});
} else if (token.classList.contains('input-token')) {
const { isLastVisualTokenValid } =
gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
 
return values
.map(value => value.trim())
.join(' ');
}
const input = FilteredSearchContainer.container.querySelector('.filtered-search');
const inputValue = input && input.value;
 
static getSearchInput(filteredSearchInput) {
const inputValue = filteredSearchInput.value;
const { right } = gl.DropdownUtils.getInputSelectionPosition(filteredSearchInput);
if (isLastVisualTokenValid) {
values.push(inputValue);
} else {
const previous = values.pop();
values.push(`${previous}${inputValue}`);
}
}
});
 
return inputValue.slice(0, right);
}
return values
.map(value => value.trim())
.join(' ');
}
 
static getInputSelectionPosition(input) {
const selectionStart = input.selectionStart;
let inputValue = input.value;
// Replace all spaces inside quote marks with underscores
// (will continue to match entire string until an end quote is found if any)
// This helps with matching the beginning & end of a token:key
inputValue = inputValue.replace(/(('[^']*'{0,1})|("[^"]*"{0,1})|:\s+)/g, str => str.replace(/\s/g, '_'));
// Get the right position for the word selected
// Regex matches first space
let right = inputValue.slice(selectionStart).search(/\s/);
if (right >= 0) {
right += selectionStart;
} else if (right < 0) {
right = inputValue.length;
}
static getSearchInput(filteredSearchInput) {
const inputValue = filteredSearchInput.value;
const { right } = gl.DropdownUtils.getInputSelectionPosition(filteredSearchInput);
 
// Get the left position for the word selected
// Regex matches last non-whitespace character
let left = inputValue.slice(0, right).search(/\S+$/);
return inputValue.slice(0, right);
}
 
if (selectionStart === 0) {
left = 0;
} else if (selectionStart === inputValue.length && left < 0) {
left = inputValue.length;
} else if (left < 0) {
left = selectionStart;
}
static getInputSelectionPosition(input) {
const selectionStart = input.selectionStart;
let inputValue = input.value;
// Replace all spaces inside quote marks with underscores
// (will continue to match entire string until an end quote is found if any)
// This helps with matching the beginning & end of a token:key
inputValue = inputValue.replace(/(('[^']*'{0,1})|("[^"]*"{0,1})|:\s+)/g, str => str.replace(/\s/g, '_'));
// Get the right position for the word selected
// Regex matches first space
let right = inputValue.slice(selectionStart).search(/\s/);
if (right >= 0) {
right += selectionStart;
} else if (right < 0) {
right = inputValue.length;
}
// Get the left position for the word selected
// Regex matches last non-whitespace character
let left = inputValue.slice(0, right).search(/\S+$/);
 
return {
left,
right,
};
if (selectionStart === 0) {
left = 0;
} else if (selectionStart === inputValue.length && left < 0) {
left = inputValue.length;
} else if (left < 0) {
left = selectionStart;
}
return {
left,
right,
};
}
}
 
window.gl = window.gl || {};
gl.DropdownUtils = DropdownUtils;
})();
window.gl = window.gl || {};
gl.DropdownUtils = DropdownUtils;
(() => {
const DATA_DROPDOWN_TRIGGER = 'data-dropdown-trigger';
class FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter) {
this.droplab = droplab;
this.hookId = input && input.id;
this.input = input;
this.filter = filter;
this.dropdown = dropdown;
this.loadingTemplate = `<div class="filter-dropdown-loading">
<i class="fa fa-spinner fa-spin"></i>
</div>`;
this.bindEvents();
}
bindEvents() {
this.itemClickedWrapper = this.itemClicked.bind(this);
this.dropdown.addEventListener('click.dl', this.itemClickedWrapper);
}
const DATA_DROPDOWN_TRIGGER = 'data-dropdown-trigger';
class FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter) {
this.droplab = droplab;
this.hookId = input && input.id;
this.input = input;
this.filter = filter;
this.dropdown = dropdown;
this.loadingTemplate = `<div class="filter-dropdown-loading">
<i class="fa fa-spinner fa-spin"></i>
</div>`;
this.bindEvents();
}
 
unbindEvents() {
this.dropdown.removeEventListener('click.dl', this.itemClickedWrapper);
}
bindEvents() {
this.itemClickedWrapper = this.itemClicked.bind(this);
this.dropdown.addEventListener('click.dl', this.itemClickedWrapper);
}
 
getCurrentHook() {
return this.droplab.hooks.filter(h => h.id === this.hookId)[0] || null;
}
unbindEvents() {
this.dropdown.removeEventListener('click.dl', this.itemClickedWrapper);
}
 
itemClicked(e, getValueFunction) {
const { selected } = e.detail;
getCurrentHook() {
return this.droplab.hooks.filter(h => h.id === this.hookId)[0] || null;
}
 
if (selected.tagName === 'LI' && selected.innerHTML) {
const dataValueSet = gl.DropdownUtils.setDataValueIfSelected(this.filter, selected);
itemClicked(e, getValueFunction) {
const { selected } = e.detail;
 
if (!dataValueSet) {
const value = getValueFunction(selected);
gl.FilteredSearchDropdownManager.addWordToInput(this.filter, value, true);
}
if (selected.tagName === 'LI' && selected.innerHTML) {
const dataValueSet = gl.DropdownUtils.setDataValueIfSelected(this.filter, selected);
 
this.resetFilters();
this.dismissDropdown();
this.dispatchInputEvent();
if (!dataValueSet) {
const value = getValueFunction(selected);
gl.FilteredSearchDropdownManager.addWordToInput(this.filter, value, true);
}
}
 
setAsDropdown() {
this.input.setAttribute(DATA_DROPDOWN_TRIGGER, `#${this.dropdown.id}`);
this.resetFilters();
this.dismissDropdown();
this.dispatchInputEvent();
}
}
 
setOffset(offset = 0) {
if (window.innerWidth > 480) {
this.dropdown.style.left = `${offset}px`;
} else {
this.dropdown.style.left = '0px';
}
setAsDropdown() {
this.input.setAttribute(DATA_DROPDOWN_TRIGGER, `#${this.dropdown.id}`);
}
setOffset(offset = 0) {
if (window.innerWidth > 480) {
this.dropdown.style.left = `${offset}px`;
} else {
this.dropdown.style.left = '0px';
}
}
 
renderContent(forceShowList = false) {
const currentHook = this.getCurrentHook();
if (forceShowList && currentHook && currentHook.list.hidden) {
currentHook.list.show();
}
renderContent(forceShowList = false) {
const currentHook = this.getCurrentHook();
if (forceShowList && currentHook && currentHook.list.hidden) {
currentHook.list.show();
}
}
 
render(forceRenderContent = false, forceShowList = false) {
this.setAsDropdown();
render(forceRenderContent = false, forceShowList = false) {
this.setAsDropdown();
 
const currentHook = this.getCurrentHook();
const firstTimeInitialized = currentHook === null;
const currentHook = this.getCurrentHook();
const firstTimeInitialized = currentHook === null;
 
if (firstTimeInitialized || forceRenderContent) {
this.renderContent(forceShowList);
} else if (currentHook.list.list.id !== this.dropdown.id) {
this.renderContent(forceShowList);
}
if (firstTimeInitialized || forceRenderContent) {
this.renderContent(forceShowList);
} else if (currentHook.list.list.id !== this.dropdown.id) {
this.renderContent(forceShowList);
}
}
 
dismissDropdown() {
// Focusing on the input will dismiss dropdown
// (default droplab functionality)
this.input.focus();
}
dismissDropdown() {
// Focusing on the input will dismiss dropdown
// (default droplab functionality)
this.input.focus();
}
 
dispatchInputEvent() {
// Propogate input change to FilteredSearchDropdownManager
// so that it can determine which dropdowns to open
this.input.dispatchEvent(new CustomEvent('input', {
bubbles: true,
cancelable: true,
}));
}
dispatchInputEvent() {
// Propogate input change to FilteredSearchDropdownManager
// so that it can determine which dropdowns to open
this.input.dispatchEvent(new CustomEvent('input', {
bubbles: true,
cancelable: true,
}));
}
 
dispatchFormSubmitEvent() {
// dispatchEvent() is necessary as form.submit() does not
// trigger event handlers
this.input.form.dispatchEvent(new Event('submit'));
}
dispatchFormSubmitEvent() {
// dispatchEvent() is necessary as form.submit() does not
// trigger event handlers
this.input.form.dispatchEvent(new Event('submit'));
}
 
hideDropdown() {
const currentHook = this.getCurrentHook();
if (currentHook) {
currentHook.list.hide();
}
hideDropdown() {
const currentHook = this.getCurrentHook();
if (currentHook) {
currentHook.list.hide();
}
}
 
resetFilters() {
const hook = this.getCurrentHook();
if (hook) {
const data = hook.list.data || [];
const results = data.map((o) => {
const updated = o;
updated.droplab_hidden = false;
return updated;
});
hook.list.render(results);
}
resetFilters() {
const hook = this.getCurrentHook();
if (hook) {
const data = hook.list.data || [];
const results = data.map((o) => {
const updated = o;
updated.droplab_hidden = false;
return updated;
});
hook.list.render(results);
}
}
}
 
window.gl = window.gl || {};
gl.FilteredSearchDropdown = FilteredSearchDropdown;
})();
window.gl = window.gl || {};
gl.FilteredSearchDropdown = FilteredSearchDropdown;
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment