|
|
@ -2,6 +2,7 @@ import { attr, css, customElement, FASTElement, html, observable, ref, volatile, |
|
|
|
import type { SearchQuery } from '../../../../../git/search'; |
|
|
|
import { debounce } from '../../../../../system/function'; |
|
|
|
import '../code-icon'; |
|
|
|
import type { PopMenu } from '../overlays/pop-menu'; |
|
|
|
|
|
|
|
export type SearchOperators = |
|
|
|
| '=:' |
|
|
@ -34,21 +35,56 @@ const operatorsHelpMap = new Map([ |
|
|
|
// match case is disabled unless regex is true
|
|
|
|
const template = html<SearchInput>`
|
|
|
|
<template role="search"> |
|
|
|
<label |
|
|
|
for="search" |
|
|
|
aria-controls="helper" |
|
|
|
aria-expanded="${x => x.showHelp}" |
|
|
|
@click="${(x, c) => x.handleShowHelper(c.event)}" |
|
|
|
> |
|
|
|
<code-icon icon="search" aria-label="${x => x.label}" title="${x => x.label}"></code-icon> |
|
|
|
<code-icon class="icon-small" icon="chevron-down" aria-hidden="true"></code-icon> |
|
|
|
</label> |
|
|
|
<pop-menu ${ref('popmenu')} style="margin-left: -0.25rem;"> |
|
|
|
<button |
|
|
|
type="button" |
|
|
|
class="action-button" |
|
|
|
slot="trigger" |
|
|
|
aria-label="${x => x.label}" |
|
|
|
title="${x => x.label}" |
|
|
|
> |
|
|
|
<code-icon icon="search" aria-hidden="true"></code-icon> |
|
|
|
<code-icon class="action-button__more" icon="chevron-down" aria-hidden="true"></code-icon> |
|
|
|
</button> |
|
|
|
<menu-list slot="content"> |
|
|
|
<menu-label>Search by</menu-label> |
|
|
|
<menu-item role="none"> |
|
|
|
<button class="menu-button" type="button" @click="${(x, _c) => x.handleInsertToken('@me')}"> |
|
|
|
My changes <small>@me</small> |
|
|
|
</button> |
|
|
|
</menu-item> |
|
|
|
<menu-item role="none"> |
|
|
|
<button class="menu-button" type="button" @click="${(x, _c) => x.handleInsertToken('message:')}"> |
|
|
|
Message <small>message: or =:</small> |
|
|
|
</button> |
|
|
|
</menu-item> |
|
|
|
<menu-item role="none"> |
|
|
|
<button class="menu-button" type="button" @click="${(x, _c) => x.handleInsertToken('author:')}"> |
|
|
|
Author <small>author: or @:</small> |
|
|
|
</button> |
|
|
|
</menu-item> |
|
|
|
<menu-item role="none"> |
|
|
|
<button class="menu-button" type="button" @click="${(x, _c) => x.handleInsertToken('commit:')}"> |
|
|
|
Commit SHA <small>commit: or #:</small> |
|
|
|
</button> |
|
|
|
</menu-item> |
|
|
|
<menu-item role="none"> |
|
|
|
<button class="menu-button" type="button" @click="${(x, _c) => x.handleInsertToken('file:')}"> |
|
|
|
File <small>file: or ?:</small> |
|
|
|
</button> |
|
|
|
</menu-item> |
|
|
|
<menu-item role="none"> |
|
|
|
<button class="menu-button" type="button" @click="${(x, _c) => x.handleInsertToken('change:')}"> |
|
|
|
Change <small>change: or ~:</small> |
|
|
|
</button> |
|
|
|
</menu-item> |
|
|
|
</menu-list> |
|
|
|
</pop-menu> |
|
|
|
<div class="field"> |
|
|
|
<input |
|
|
|
${ref('input')} |
|
|
|
id="search" |
|
|
|
part="search" |
|
|
|
class="${x => (x.showHelp ? 'has-helper' : '')}" |
|
|
|
type="text" |
|
|
|
spellcheck="false" |
|
|
|
placeholder="${x => x.placeholder}" |
|
|
@ -145,27 +181,6 @@ const template = html` |
|
|
|
<code-icon icon="regex"></code-icon> |
|
|
|
</button> |
|
|
|
</div> |
|
|
|
<div class="helper" id="helper" tabindex="-1" ${ref('helper')}> |
|
|
|
<p class="helper-label">Search by</p> |
|
|
|
<button class="helper-button" type="button" @click="${(x, _c) => x.handleInsertToken('@me')}"> |
|
|
|
My changes <small>@me</small> |
|
|
|
</button> |
|
|
|
<button class="helper-button" type="button" @click="${(x, _c) => x.handleInsertToken('message:')}"> |
|
|
|
Message <small>message: or =:</small> |
|
|
|
</button> |
|
|
|
<button class="helper-button" type="button" @click="${(x, _c) => x.handleInsertToken('author:')}"> |
|
|
|
Author <small>author: or @:</small> |
|
|
|
</button> |
|
|
|
<button class="helper-button" type="button" @click="${(x, _c) => x.handleInsertToken('commit:')}"> |
|
|
|
Commit SHA <small>commit: or #:</small> |
|
|
|
</button> |
|
|
|
<button class="helper-button" type="button" @click="${(x, _c) => x.handleInsertToken('file:')}"> |
|
|
|
File <small>file: or ?:</small> |
|
|
|
</button> |
|
|
|
<button class="helper-button" type="button" @click="${(x, _c) => x.handleInsertToken('change:')}"> |
|
|
|
Change <small>change: or ~:</small> |
|
|
|
</button> |
|
|
|
</div> |
|
|
|
</template> |
|
|
|
`;
|
|
|
|
|
|
|
@ -327,74 +342,90 @@ const styles = css` |
|
|
|
display: none; |
|
|
|
} |
|
|
|
|
|
|
|
.helper { |
|
|
|
display: none; |
|
|
|
position: absolute; |
|
|
|
top: 100%; |
|
|
|
left: 0; |
|
|
|
z-index: 5000; |
|
|
|
width: fit-content; |
|
|
|
background-color: var(--vscode-menu-background); |
|
|
|
border: 1px solid var(--vscode-menu-border); |
|
|
|
outline: none; |
|
|
|
.action-button { |
|
|
|
position: relative; |
|
|
|
appearance: none; |
|
|
|
font-family: inherit; |
|
|
|
font-size: 1.2rem; |
|
|
|
line-height: 2.2rem; |
|
|
|
// background-color: var(--color-graph-actionbar-background);
|
|
|
|
background-color: transparent; |
|
|
|
border: none; |
|
|
|
color: inherit; |
|
|
|
color: var(--color-foreground); |
|
|
|
padding: 0 0.75rem; |
|
|
|
cursor: pointer; |
|
|
|
border-radius: 3px; |
|
|
|
height: auto; |
|
|
|
|
|
|
|
display: grid; |
|
|
|
grid-auto-flow: column; |
|
|
|
grid-gap: 0.5rem; |
|
|
|
gap: 0.5rem; |
|
|
|
max-width: fit-content; |
|
|
|
} |
|
|
|
label[aria-expanded='true'] ~ .helper { |
|
|
|
display: block; |
|
|
|
|
|
|
|
.action-button[disabled] { |
|
|
|
pointer-events: none; |
|
|
|
cursor: default; |
|
|
|
opacity: 1; |
|
|
|
} |
|
|
|
|
|
|
|
.helper::before { |
|
|
|
font: normal normal normal 14px/1 codicon; |
|
|
|
display: inline-block; |
|
|
|
.action-button:hover { |
|
|
|
background-color: var(--color-graph-actionbar-selectedBackground); |
|
|
|
color: var(--color-foreground); |
|
|
|
text-decoration: none; |
|
|
|
text-rendering: auto; |
|
|
|
text-align: center; |
|
|
|
-webkit-font-smoothing: antialiased; |
|
|
|
-moz-osx-font-smoothing: grayscale; |
|
|
|
user-select: none; |
|
|
|
-webkit-user-select: none; |
|
|
|
-ms-user-select: none; |
|
|
|
} |
|
|
|
|
|
|
|
vertical-align: middle; |
|
|
|
line-height: 2rem; |
|
|
|
letter-spacing: normal; |
|
|
|
.action-button[aria-checked] { |
|
|
|
border: 1px solid transparent; |
|
|
|
} |
|
|
|
|
|
|
|
content: '\\ea76'; |
|
|
|
position: absolute; |
|
|
|
top: 2px; |
|
|
|
right: 5px; |
|
|
|
cursor: pointer; |
|
|
|
pointer-events: all; |
|
|
|
z-index: 10001; |
|
|
|
opacity: 0.6; |
|
|
|
.action-button[aria-checked='true'] { |
|
|
|
background-color: var(--vscode-inputOption-activeBackground); |
|
|
|
color: var(--vscode-inputOption-activeForeground); |
|
|
|
border-color: var(--vscode-inputOption-activeBorder); |
|
|
|
} |
|
|
|
|
|
|
|
.helper-label { |
|
|
|
text-transform: uppercase; |
|
|
|
font-size: 0.84em; |
|
|
|
.action-button code-icon, |
|
|
|
.action-button .codicon[class*='codicon-'], |
|
|
|
.action-button .glicon[class*='glicon-'] { |
|
|
|
line-height: 2.2rem; |
|
|
|
padding-left: 0.6rem; |
|
|
|
padding-right: 0.6rem; |
|
|
|
margin: 0; |
|
|
|
opacity: 0.6; |
|
|
|
user-select: none; |
|
|
|
vertical-align: bottom; |
|
|
|
} |
|
|
|
|
|
|
|
.action-button__more, |
|
|
|
.action-button__more.codicon[class*='codicon-'] { |
|
|
|
font-size: 1rem; |
|
|
|
margin-right: -0.25rem; |
|
|
|
} |
|
|
|
|
|
|
|
.action-button__more::before { |
|
|
|
margin-left: -0.25rem; |
|
|
|
} |
|
|
|
|
|
|
|
.helper-button { |
|
|
|
menu-item { |
|
|
|
padding: 0 0.5rem; |
|
|
|
} |
|
|
|
|
|
|
|
menu-list { |
|
|
|
padding-bottom: 0.5rem; |
|
|
|
} |
|
|
|
|
|
|
|
.menu-button { |
|
|
|
display: block; |
|
|
|
width: 100%; |
|
|
|
padding-left: 0.6rem; |
|
|
|
padding-right: 0.6rem; |
|
|
|
padding: 0.1rem 0.6rem 0 0.6rem; |
|
|
|
line-height: 2.2rem; |
|
|
|
text-align: left; |
|
|
|
color: var(--vscode-menu-foreground); |
|
|
|
border-radius: 3px; |
|
|
|
} |
|
|
|
.helper-button:hover { |
|
|
|
|
|
|
|
.menu-button:hover { |
|
|
|
color: var(--vscode-menu-selectionForeground); |
|
|
|
background-color: var(--vscode-menu-selectionBackground); |
|
|
|
} |
|
|
|
.helper-button small { |
|
|
|
opacity: 0.5; |
|
|
|
} |
|
|
|
`;
|
|
|
|
|
|
|
|
@customElement({ |
|
|
@ -403,8 +434,8 @@ const styles = css` |
|
|
|
styles: styles, |
|
|
|
}) |
|
|
|
export class SearchInput extends FASTElement { |
|
|
|
@observable |
|
|
|
showHelp = false; |
|
|
|
input!: HTMLInputElement; |
|
|
|
popmenu!: PopMenu; |
|
|
|
|
|
|
|
@observable |
|
|
|
errorMessage = ''; |
|
|
@ -435,39 +466,12 @@ export class SearchInput extends FASTElement { |
|
|
|
return this.matchRegex ? this.matchCase : true; |
|
|
|
} |
|
|
|
|
|
|
|
input!: HTMLInputElement; |
|
|
|
helper!: HTMLElement; |
|
|
|
|
|
|
|
override connectedCallback() { |
|
|
|
super.connectedCallback(); |
|
|
|
document.addEventListener('click', this.handleDocumentClick.bind(this)); |
|
|
|
} |
|
|
|
|
|
|
|
override disconnectedCallback() { |
|
|
|
super.disconnectedCallback(); |
|
|
|
document.removeEventListener('click', this.handleDocumentClick.bind(this)); |
|
|
|
} |
|
|
|
|
|
|
|
override focus(options?: FocusOptions): void { |
|
|
|
this.input.focus(options); |
|
|
|
} |
|
|
|
|
|
|
|
handleDocumentClick(e: MouseEvent) { |
|
|
|
if (this.showHelp === false) return; |
|
|
|
|
|
|
|
const composedPath = e.composedPath(); |
|
|
|
if ( |
|
|
|
!composedPath.includes(this) || |
|
|
|
// If the ::before element is clicked and is the close icon, close the menu
|
|
|
|
(e.type === 'click' && |
|
|
|
window.getComputedStyle(composedPath[0] as Element, '::before').content === '"\uEA76"') |
|
|
|
) { |
|
|
|
this.showHelp = false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
handleFocus(_e: Event) { |
|
|
|
this.showHelp = false; |
|
|
|
this.popmenu.close(); |
|
|
|
} |
|
|
|
|
|
|
|
handleClear(_e: Event) { |
|
|
@ -547,15 +551,6 @@ export class SearchInput extends FASTElement { |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
handleShowHelper(_e: Event) { |
|
|
|
this.showHelp = !this.showHelp; |
|
|
|
if (this.showHelp) { |
|
|
|
window.requestAnimationFrame(() => { |
|
|
|
this.helper.focus(); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
handleInsertToken(token: string) { |
|
|
|
this.value += `${this.value.length > 0 ? ' ' : ''}${token}`; |
|
|
|
window.requestAnimationFrame(() => { |
|
|
|