UBERF-4165: Add search to actions popup (#4057)

Signed-off-by: Maxim Karmatskikh <mkarmatskih@gmail.com>
This commit is contained in:
Maksim Karmatskikh
2023-11-27 16:16:46 +07:00
committed by GitHub
parent 1fe85ceba8
commit 79054bc121
15 changed files with 472 additions and 182 deletions
+2 -2
View File
@@ -741,7 +741,7 @@ export function createModel (builder: Builder): void {
label: contact.string.SearchPerson,
title: contact.string.People,
query: contact.completion.PersonQuery,
context: ['search', 'mention'],
context: ['search', 'mention', 'spotlight'],
classToSearch: contact.class.Person
},
contact.completion.PersonCategory
@@ -755,7 +755,7 @@ export function createModel (builder: Builder): void {
label: contact.string.SearchOrganization,
title: contact.string.Organizations,
query: contact.completion.OrganizationQuery,
context: ['search', 'mention'],
context: ['search', 'mention', 'spotlight'],
classToSearch: contact.class.Organization
},
contact.completion.OrganizationCategory
+2 -2
View File
@@ -1200,7 +1200,7 @@ export function createModel (builder: Builder): void {
label: recruit.string.SearchApplication,
title: recruit.string.Applications,
query: recruit.completion.ApplicationQuery,
context: ['search', 'mention'],
context: ['search', 'mention', 'spotlight'],
classToSearch: recruit.class.Applicant
},
recruit.completion.ApplicationCategory
@@ -1214,7 +1214,7 @@ export function createModel (builder: Builder): void {
label: recruit.string.SearchVacancy,
title: recruit.string.Vacancies,
query: recruit.completion.VacancyQuery,
context: ['search', 'mention'],
context: ['search', 'mention', 'spotlight'],
classToSearch: recruit.class.Vacancy
},
recruit.completion.VacancyCategory
+1 -1
View File
@@ -509,7 +509,7 @@ export function createModel (builder: Builder): void {
label: tracker.string.SearchIssue,
title: tracker.string.Issues,
query: tracker.completion.IssueQuery,
context: ['search', 'mention'],
context: ['search', 'mention', 'spotlight'],
classToSearch: tracker.class.Issue
},
tracker.completion.IssueCategory
+1 -1
View File
@@ -56,7 +56,7 @@ export type ObjectSearchFactory = (
* search - show in search popup
* mention - show in mentions
*/
export type ObjectSearchContext = 'search' | 'mention'
export type ObjectSearchContext = 'search' | 'mention' | 'spotlight'
/**
* @public
@@ -16,23 +16,16 @@
<script lang="ts">
import { Label, ListView, resizeObserver } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import presentation, { getClient, ObjectSearchCategory } from '@hcengineering/presentation'
import presentation, { getClient, type ObjectSearchCategory } from '@hcengineering/presentation'
import { Class, Ref, Doc, SearchResultDoc } from '@hcengineering/core'
import { type SearchItem, packSearchResultsForListView, doFulltextSearch } from '../search'
import MentionResult from './MentionResult.svelte'
export let query: string = ''
interface SearchSection {
category: ObjectSearchCategory
items: SearchResultDoc[]
}
interface SearchItem {
num: number
item: SearchResultDoc
category: ObjectSearchCategory
}
let items: SearchItem[] = []
let categories: ObjectSearchCategory[] = []
@@ -92,67 +85,6 @@
return false
}
function packSearchResultsForListView (sections: SearchSection[]): SearchItem[] {
let results: SearchItem[] = []
for (const section of sections) {
const category = section.category
const items = section.items
if (category.classToSearch !== undefined) {
results = results.concat(
items.map((item, num) => {
return { num, category, item }
})
)
}
}
return results
}
function findCategoryByClass (
categories: ObjectSearchCategory[],
_class: Ref<Class<Doc>>
): ObjectSearchCategory | undefined {
for (const category of categories) {
if (category.classToSearch === _class) {
return category
}
}
return undefined
}
async function doFulltextSearch (classes: Array<Ref<Class<Doc>>>, query: string): Promise<SearchSection[]> {
const result = await client.searchFulltext(
{
query: `${query}*`,
classes
},
{
limit: 10
}
)
const itemsByClass = new Map<Ref<Class<Doc>>, SearchResultDoc[]>()
for (const item of result.docs) {
const list = itemsByClass.get(item.doc._class)
if (list === undefined) {
itemsByClass.set(item.doc._class, [item])
} else {
list.push(item)
}
}
const sections: SearchSection[] = []
for (const [_class, items] of itemsByClass.entries()) {
const category = findCategoryByClass(categories, _class)
if (category !== undefined) {
sections.push({ category, items })
}
}
return sections
}
async function updateItems (query: string): Promise<void> {
const classesToSearch: Array<Ref<Class<Doc>>> = []
for (const cat of categories) {
@@ -161,7 +93,7 @@
}
}
const sections = await doFulltextSearch(classesToSearch, query)
const sections = await doFulltextSearch(client, classesToSearch, query, categories)
items = packSearchResultsForListView(sections)
}
$: void updateItems(query)
@@ -191,7 +123,7 @@
{@const doc = item.item}
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class="ap-menuItem withComp"
class="ap-menuItem withComp h-8"
on:click={() => {
dispatchItem(doc)
}}
@@ -24,7 +24,7 @@
$: icon = value.icon !== undefined ? value.icon : undefined
</script>
<div class="flex-row-center h-8">
<div class="flex-row-center">
<div class="flex-center p-1 content-dark-color flex-no-shrink">
{#if icon !== undefined}
<Icon {icon} size={'medium'} />
+2
View File
@@ -30,10 +30,12 @@ export { default as TextEditor } from './components/TextEditor.svelte'
export { default as TextEditorStyleToolbar } from './components/TextEditorStyleToolbar.svelte'
export { default as AttachIcon } from './components/icons/Attach.svelte'
export { default as TableOfContents } from './components/toc/TableOfContents.svelte'
export { default as MentionResult } from './components/MentionResult.svelte'
export * from './components/node-view'
export { default } from './plugin'
export * from './types'
export * from './utils'
export * from './search'
export { FocusExtension, type FocusOptions, type FocusStorage } from './components/extension/focus'
export { HeadingsExtension, type HeadingsOptions, type HeadingsStorage } from './components/extension/headings'
+100
View File
@@ -0,0 +1,100 @@
//
// Copyright © 2023 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import type { Class, Ref, Doc, SearchResultDoc, TxOperations } from '@hcengineering/core'
import { type ObjectSearchCategory } from '@hcengineering/presentation'
/**
* @public
*/
export interface SearchSection {
category: ObjectSearchCategory
items: SearchResultDoc[]
}
/**
* @public
*/
export interface SearchItem {
num: number
item: SearchResultDoc
category: ObjectSearchCategory
}
export function findCategoryByClass (
categories: ObjectSearchCategory[],
_class: Ref<Class<Doc>>
): ObjectSearchCategory | undefined {
for (const category of categories) {
if (category.classToSearch === _class) {
return category
}
}
return undefined
}
export function packSearchResultsForListView (sections: SearchSection[]): SearchItem[] {
let results: SearchItem[] = []
for (const section of sections) {
const category = section.category
const items = section.items
if (category.classToSearch !== undefined) {
results = results.concat(
items.map((item, num) => {
return { num, category, item }
})
)
}
}
return results
}
export async function doFulltextSearch (
client: TxOperations,
classes: Array<Ref<Class<Doc>>>,
query: string,
categories: ObjectSearchCategory[]
): Promise<SearchSection[]> {
const result = await client.searchFulltext(
{
query: `${query}*`,
classes
},
{
limit: 10
}
)
const itemsByClass = new Map<Ref<Class<Doc>>, SearchResultDoc[]>()
for (const item of result.docs) {
const list = itemsByClass.get(item.doc._class)
if (list === undefined) {
itemsByClass.set(item.doc._class, [item])
} else {
list.push(item)
}
}
const sections: SearchSection[] = []
for (const [_class, items] of itemsByClass.entries()) {
const category = findCategoryByClass(categories, _class)
if (category !== undefined) {
sections.push({ category, items })
}
}
return sections
}
+5 -3
View File
@@ -36,11 +36,11 @@
"Assigned": "Assigned",
"Created": "Created",
"Subscribed": "Subscribed",
"ShowPreview": "Show document preview",
"ShowActions": "Show actions popup",
"ActionPlaceholder": "type to filter...",
"ActionPlaceholder": "Search or run a command...",
"General": "General",
"Navigation": "Navigation",
"Editor": "Editor",
@@ -111,6 +111,8 @@
"ChooseIcon": "Choose icon",
"IconColor": "Choose icon color",
"IconCategory": "Icon",
"EmojiCategory": "Emoji"
"EmojiCategory": "Emoji",
"Type": "Type",
"ToViewCommands": "to view available commands"
}
}
+5 -2
View File
@@ -23,7 +23,7 @@
"Open": "Открыть",
"Assignees": "Исполнители",
"Labels": "Метки",
"ActionPlaceholder": "введите для фильтрации...",
"ActionPlaceholder": "Поиск или команда...",
"General": "Общее",
"Navigation": "Навигация",
"Editor": "Редактор",
@@ -107,6 +107,9 @@
"ChooseIcon": "Выбрать иконку",
"IconColor": "Выберите цвет",
"IconCategory": "Иконка",
"EmojiCategory": "Эмодзи"
"EmojiCategory": "Эмодзи",
"Type": "Введите",
"ToViewCommands": "чтобы увидеть команды"
}
}
@@ -13,9 +13,14 @@
// limitations under the License.
-->
<script lang="ts">
import { WithLookup, Doc, Ref } from '@hcengineering/core'
import { WithLookup, Doc, Ref, Class, SearchResultDoc } from '@hcengineering/core'
import { getResource, translate } from '@hcengineering/platform'
import { createQuery, getClient, ActionContext } from '@hcengineering/presentation'
import presentation, {
createQuery,
getClient,
ActionContext,
ObjectSearchCategory
} from '@hcengineering/presentation'
import ui, {
Button,
closePopup,
@@ -23,27 +28,35 @@
Icon,
IconArrowLeft,
Label,
EditWithIcon,
IconSearch,
deviceOptionsStore,
capitalizeFirstLetter,
formatKey,
themeStore
} from '@hcengineering/ui'
import { Action, ViewContext } from '@hcengineering/view'
import { Action, ActionCategory, ViewContext } from '@hcengineering/view'
import { filterActions, getSelection } from '../actions'
import view from '../plugin'
import { focusStore, selectionStore } from '../selection'
import { openDoc } from '../utils'
import { ListView, resizeObserver } from '@hcengineering/ui'
import ObjectPresenter from './ObjectPresenter.svelte'
import { tick } from 'svelte'
import { createEventDispatcher } from 'svelte'
import { createEventDispatcher, tick } from 'svelte'
import {
type SearchSection,
type SearchItem,
packSearchResultsForListView,
doFulltextSearch,
MentionResult
} from '@hcengineering/text-editor'
import ChevronDown from './icons/ChevronDown.svelte'
import ChevronUp from './icons/ChevronUp.svelte'
export let viewContext: ViewContext
let search: string = ''
let actions: Array<WithLookup<Action>> = []
let input: EditWithIcon
const query = createQuery()
@@ -66,11 +79,14 @@
let supportedActions: Array<WithLookup<Action>> = []
let filteredActions: Array<WithLookup<Action>> = []
async function filterVisibleActions (actions: Array<WithLookup<Action>>, docs: Doc[]) {
async function filterVisibleActions (
actions: Array<WithLookup<Action>>,
docs: Doc[]
): Promise<Array<WithLookup<Action>>> {
const resultActions: Array<WithLookup<Action>> = []
for (const action of actions) {
if (!action.visibilityTester) {
if (action.visibilityTester === undefined) {
resultActions.push(action)
} else {
const visibilityTester = await getResource(action.visibilityTester)
@@ -85,7 +101,7 @@
const client = getClient()
async function getSupportedActions (actions: Array<WithLookup<Action>>) {
async function getSupportedActions (actions: Array<WithLookup<Action>>): Promise<void> {
const docs = getSelection($focusStore, $selectionStore)
let fActions: Array<WithLookup<Action>> = actions
@@ -114,15 +130,18 @@
supportedActions = fActions.sort((a, b) => a.category.localeCompare(b.category))
}
$: getSupportedActions(actions)
$: void getSupportedActions(actions)
async function filterSearchActions (actions: Array<WithLookup<Action>>, search: string): Promise<void> {
const res: Array<WithLookup<Action>> = []
search = search.trim().toLowerCase()
if (search.length > 0) {
let preparedSearch = search.trim().toLowerCase()
if (preparedSearch.charAt(0) === '/') {
preparedSearch = preparedSearch.substring(1)
}
if (preparedSearch.length > 0) {
for (const a of actions) {
const tr = await translate(a.label, {}, $themeStore.language)
if (tr.toLowerCase().includes(search)) {
if (tr.toLowerCase().includes(preparedSearch)) {
res.push(a)
}
}
@@ -131,7 +150,7 @@
filteredActions = actions
}
}
$: filterSearchActions(supportedActions, search)
$: void filterSearchActions(supportedActions, search)
let selection = 0
let list: ListView
@@ -140,24 +159,35 @@
let activeAction: Action | undefined
async function handleSelection (evt: Event, selection: number): Promise<void> {
const action = filteredActions[selection]
if (action.actionPopup !== undefined) {
activeAction = action
return
}
const docs = getSelection($focusStore, $selectionStore)
if (action.input === 'focus') {
const impl = await getResource(action.action)
if (impl !== undefined) {
closePopup()
impl(docs[0], evt, { ...action.actionProps, action })
const item = items[selection]
if (item.item !== undefined) {
const doc = item.item.doc
void client.findOne(doc._class, { _id: doc._id }).then((value) => {
if (value !== undefined) {
void openDoc(client.getHierarchy(), value)
}
})
} else if (item.action !== undefined) {
const action = item.action
if (action.actionPopup !== undefined) {
activeAction = action
return
}
}
if (action.input === 'selection' || action.input === 'any' || action.input === 'none') {
const impl = await getResource(action.action)
if (impl !== undefined) {
closePopup()
impl(docs, evt, { ...action.actionProps, action })
const docs = getSelection($focusStore, $selectionStore)
if (action.input === 'focus') {
const impl = await getResource(action.action)
if (impl !== undefined) {
closePopup()
void impl(docs[0], evt, { ...action.actionProps, action })
}
}
if (action.input === 'selection' || action.input === 'any' || action.input === 'none') {
const impl = await getResource(action.action)
if (impl !== undefined) {
closePopup()
void impl(docs, evt, { ...action.actionProps, action })
}
}
}
}
@@ -176,11 +206,87 @@
if (key.code === 'Enter') {
key.preventDefault()
key.stopPropagation()
handleSelection(key, selection)
void handleSelection(key, selection)
}
}
const dispatch = createEventDispatcher()
interface SearchActionItem {
num: number
item?: SearchResultDoc
category?: ObjectSearchCategory
action?: WithLookup<Action>
actionCategory?: ActionCategory | WithLookup<ActionCategory>
}
function packSearchAndActions (
searchItems: SearchItem[],
filteredActions: Array<WithLookup<Action>>
): SearchActionItem[] {
let iter = -1
const mappedActions: SearchActionItem[] = filteredActions.map((action, num: number) => {
if (num > 0 && filteredActions[num - 1].$lookup?.category?.label !== action.$lookup?.category?.label) {
iter = 0
} else {
iter++
}
return {
num: iter,
action,
actionCategory: action.$lookup?.category
}
})
return ([] as SearchActionItem[]).concat(searchItems).concat(mappedActions)
}
let items: SearchActionItem[] = []
let categories: ObjectSearchCategory[] = []
client
.findAll(presentation.class.ObjectSearchCategory, { context: 'spotlight' })
.then(async (results) => {
categories = results
await updateItems(search, filteredActions)
})
.catch((e) => {
console.error(e)
})
async function updateItems (query: string, filteredActions: Array<WithLookup<Action>>): Promise<void> {
const classesToSearch: Array<Ref<Class<Doc>>> = []
for (const cat of categories) {
if (cat.classToSearch !== undefined) {
classesToSearch.push(cat.classToSearch)
}
}
let sections: SearchSection[] = []
if (query !== '' && query.indexOf('/') !== 0) {
sections = await doFulltextSearch(client, classesToSearch, query, categories)
}
const searchItems = packSearchResultsForListView(sections)
items = packSearchAndActions(searchItems, filteredActions)
}
$: void updateItems(search, filteredActions)
let textHTML: HTMLInputElement
let phTraslate: string = ''
let autoFocus = !$deviceOptionsStore.isMobile
export function focus (): void {
textHTML.focus()
autoFocus = false
}
$: if (textHTML !== undefined) {
if (autoFocus) focus()
}
$: void translate(view.string.ActionPlaceholder, {}).then((res) => {
phTraslate = res
})
</script>
<ActionContext
@@ -191,13 +297,13 @@
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class="selectPopup width-40"
class="selectPopup width-40 actionsPopup"
style:width="15rem"
on:keydown={onKeydown}
use:resizeObserver={() => dispatch('changeContent')}
>
{#if $selectionStore.docs.length > 0 || $focusStore.focus !== undefined || activeAction?.actionPopup !== undefined}
<div class="mt-2 ml-2 flex-between flex-no-shrink">
<div class="actionsDoc flex-between flex-no-shrink">
{#if $selectionStore.docs.length > 0}
<div class="item-box">
<Label label={view.string.NumberItems} params={{ count: $selectionStore.docs.length }} />
@@ -241,82 +347,189 @@
on:close={async () => {
activeAction = undefined
await tick()
input?.focus()
textHTML?.focus()
}}
/>
{:else}
<div class="header">
<EditWithIcon
bind:this={input}
icon={IconSearch}
size={'large'}
width={'100%'}
autoFocus={!$deviceOptionsStore.isMobile}
<div class="header actionsHeader">
<input
class="actionsInput"
bind:this={textHTML}
type="text"
bind:value={search}
placeholder={view.string.ActionPlaceholder}
placeholder={phTraslate}
on:change
on:input
on:keydown
/>
</div>
<div class="scroll">
<div class="box">
<ListView
bind:this={list}
count={filteredActions.length}
count={items.length}
bind:selection
on:click={async (evt) => {
await handleSelection(evt, evt.detail)
}}
>
<svelte:fragment slot="category" let:item>
{@const action = filteredActions[item]}
{#if item === 0 || (item > 0 && filteredActions[item - 1].$lookup?.category?.label !== action.$lookup?.category?.label)}
<!--Category for first item-->
{#if action.$lookup?.category}
<div class="category-box">
<Label label={action.$lookup.category.label} />
<svelte:fragment slot="category" let:item={num}>
{@const item = items[num]}
{#if item.num === 0}
{#if item.category !== undefined}
<div class="actionsCategory">
<Label label={item.category.title} />
</div>
{/if}
{#if item.actionCategory}
<div class="actionsCategory">
<Label label={item.actionCategory.label} />
</div>
{/if}
{/if}
</svelte:fragment>
<svelte:fragment slot="item" let:item>
{@const action = filteredActions[item]}
<div class="flex-row-center flex-between flex-grow ml-2 p-3 text-base cursor-pointer">
<div class="mr-4 {selection === item ? 'caption-color' : 'content-dark-color'}">
<Icon icon={action.icon ?? IconArrowLeft} size={'small'} />
<svelte:fragment slot="item" let:item={num}>
{@const item = items[num]}
{#if item.item !== undefined}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="ap-menuItem withComp actionsSearchItem">
<MentionResult value={item.item} />
</div>
<div class="flex-grow {selection === item ? 'caption-color' : 'content-color'}">
<Label label={action.label} />
</div>
<div class="mr-2 text-md flex-row-center">
{#if action.keyBinding}
{#each action.keyBinding as key, i}
{#if i !== 0}
<div class="ml-2 mr-2 lower"><Label label={view.string.Or} /></div>
{/if}
<div class="flex-row-center">
{#each formatKey(key) as k, jj}
{#if jj !== 0}
<div class="ml-1 mr-1 lower"><Label label={view.string.Then} /></div>
{/if}
{#each k as kk, j}
<div class="flex-center text-sm key-box">
{capitalizeFirstLetter(kk.trim())}
</div>
{/if}
{#if item.action !== undefined}
{@const action = item.action}
<div class="flex-row-center flex-between flex-grow ml-2 text-base cursor-pointer actionsitem">
<div class="mr-4 {selection === num ? 'caption-color' : 'content-dark-color'}">
<Icon icon={action.icon ?? IconArrowLeft} size={'small'} />
</div>
<div class="flex-grow {selection === num ? 'caption-color' : 'content-color'}">
<Label label={action.label} />
</div>
<div class="mr-2 text-md flex-row-center">
{#if action.keyBinding}
{#each action.keyBinding as key, i}
{#if i !== 0}
<div class="ml-2 mr-2 lower"><Label label={view.string.Or} /></div>
{/if}
<div class="flex-row-center">
{#each formatKey(key) as k, jj}
{#if jj !== 0}
<div class="ml-1 mr-1 lower"><Label label={view.string.Then} /></div>
{/if}
{#each k as kk, j}
<div class="flex-center text-sm key-box">
{capitalizeFirstLetter(kk.trim())}
</div>
{/each}
{/each}
{/each}
</div>
{/each}
{/if}
</div>
{/each}
{/if}
</div>
</div>
</div>
{/if}
</svelte:fragment>
</ListView>
</div>
</div>
<div class="actionsHint">
<div class="actionsHintLable">
<span class="hintNav">
<ChevronUp size={'small'} />
</span>
<span class="hintNav">
<ChevronDown size={'small'} />
</span>
<span class="ml mr">
<Label label={view.string.Type} />
</span><span class="hintNav">/</span>
<span class="ml"><Label label={view.string.ToViewCommands} /></span>
</div>
</div>
{/if}
<div class="menu-space" />
</div>
<style lang="scss">
.actionsHint {
border-top: 1px solid var(--theme-popup-divider);
padding: 0 1.25rem;
font-size: 0.8125rem;
.actionsHintLable {
height: 2.75rem;
display: flex;
flex-direction: row;
align-items: center;
.hintNav {
width: 1rem;
height: 1rem;
align-items: center;
justify-content: center;
display: flex;
background-color: rgba(0, 0, 0, 0.05);
margin: 0.13rem;
font-size: 0.75rem;
}
.ml {
margin-left: 0.62rem;
}
.mr {
margin-right: 0.62rem;
}
}
}
.actionsDoc {
margin-top: 1rem;
margin-left: 1.25rem;
margin-right: 1.25rem;
}
.actionsInput {
width: 100%;
caret-color: var(--theme-caret-color);
border: none;
border-radius: 0.25rem;
font-size: 1.125rem;
&::placeholder {
color: var(--theme-dark-color);
}
}
.header.actionsHeader {
padding-top: 1rem;
padding-left: 1.25rem;
padding-right: 1.25rem;
}
.selectPopup.actionsPopup {
max-height: 30rem;
.actionsCategory {
padding: 0.5rem 1.25rem;
font-size: 0.625rem;
letter-spacing: 0.0625rem;
color: var(--theme-dark-color);
text-transform: uppercase;
line-height: 1rem;
}
.actionsSearchItem {
height: 2.25rem;
display: flex;
margin: 0.25rem;
}
.actionsitem {
font-size: 0.875rem;
padding: 0.375rem;
margin: 0.25rem 0.5rem;
}
}
.key-box {
padding: 0 0.5rem;
min-width: 1.5rem;
@@ -325,9 +538,11 @@
border: 1px solid var(--theme-button-border);
border-radius: 0.25rem;
}
.key-box + .key-box {
margin-left: 0.5rem;
}
.item-box {
display: inline-block;
padding: 0.25rem 0.5rem;
@@ -335,14 +550,4 @@
background-color: var(--theme-divider-color);
border-radius: 0.25rem;
}
.category-box {
display: flex;
align-items: center;
padding: 0.5rem 1rem;
min-height: 2rem;
text-transform: uppercase;
font-weight: 500;
font-size: 0.625rem;
color: var(--theme-caption-color);
}
</style>
@@ -0,0 +1,22 @@
<!--
// Copyright © 2023 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
export let size: 'small' | 'medium' | 'large'
const fill: string = 'currentColor'
</script>
<svg class="svg-{size}" {fill} viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">
<path d="M6 8.24999L2.25 4.49999L2.775 3.97499L6 7.19999L9.225 3.97499L9.75 4.49999L6 8.24999Z" fill-opacity="0.6" />
</svg>
@@ -0,0 +1,22 @@
<!--
// Copyright © 2023 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
export let size: 'small' | 'medium' | 'large'
const fill: string = 'currentColor'
</script>
<svg class="svg-{size}" {fill} viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">
<path d="M6 3.75L2.25 7.5L2.78033 8.03033L6 4.81066L9.21967 8.03033L9.75 7.5L6 3.75Z" fill-opacity="0.6" />
</svg>
+3 -1
View File
@@ -98,7 +98,9 @@ export default mergeIds(viewId, view, {
IconColor: '' as IntlString,
IconCategory: '' as IntlString,
EmojiCategory: '' as IntlString,
NumberItems: '' as IntlString
NumberItems: '' as IntlString,
Type: '' as IntlString,
ToViewCommands: '' as IntlString
},
function: {
CreateDocMiddleware: '' as Resource<PresentationMiddlewareCreator>
+3 -3
View File
@@ -21,7 +21,7 @@ test.describe('actions tests', () => {
await page.click('td:has-text("Frontend Engineer")')
await page.press('body', 'Meta+k')
await page.fill('[placeholder="type\\ to\\ filter\\.\\.\\."]', 'Talent')
await page.fill('[placeholder="Search\\ or\\ run\\ a\\ command\\.\\.\\."]', 'Talent')
expect(await page.locator('div.selectPopup :text("New Talent")').count()).toBe(1)
await page.click('div.selectPopup :text("New Talent")')
await page.click('button#card-close')
@@ -34,7 +34,7 @@ test.describe('actions tests', () => {
await expect(page).toHaveURL(`${PlatformURI}/workbench/sanity-ws/recruit/talents`)
await page.press('body', 'Meta+k')
await page.fill('[placeholder="type\\ to\\ filter\\.\\.\\."]', 'go to')
await page.fill('[placeholder="Search\\ or\\ run\\ a\\ command\\.\\.\\."]', 'go to')
expect(await page.locator('div.selectPopup :text("Go To Vacancies")').count()).toBe(1)
await page.click('div.selectPopup :text("Go To Vacancies")')
@@ -48,7 +48,7 @@ test.describe('actions tests', () => {
await expect(page).toHaveURL(`${PlatformURI}/workbench/sanity-ws/recruit/talents`)
await page.press('body', 'Meta+k')
await page.fill('[placeholder="type\\ to\\ filter\\.\\.\\."]', 'go to')
await page.fill('[placeholder="Search\\ or\\ run\\ a\\ command\\.\\.\\."]', 'go to')
expect(await page.locator('div.selectPopup :text("Go To Applications")').count()).toBe(1)
await page.click('div.selectPopup :text("Go To Applications")')