Added hyperlink type, ScrollerBar (#2353)

Signed-off-by: Alexander Platov <sas_lord@mail.ru>
This commit is contained in:
Alexander Platov 2022-11-04 09:07:27 +03:00 committed by GitHub
parent 47f5237fc9
commit 35e4fcd8aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 311 additions and 182 deletions

View File

@ -143,6 +143,10 @@ export class TType extends TObj implements Type<any> {
@Model(core.class.TypeString, core.class.Type) @Model(core.class.TypeString, core.class.Type)
export class TTypeString extends TType {} export class TTypeString extends TType {}
@UX(core.string.Hyperlink)
@Model(core.class.TypeHyperlink, core.class.Type)
export class TTypeHyperlink extends TType {}
@UX(core.string.IntlString) @UX(core.string.IntlString)
@Model(core.class.TypeIntlString, core.class.Type) @Model(core.class.TypeIntlString, core.class.Type)
export class TTypeIntlString extends TType {} export class TTypeIntlString extends TType {}

View File

@ -38,6 +38,7 @@ import {
TTypeMarkup, TTypeMarkup,
TTypeNumber, TTypeNumber,
TTypeString, TTypeString,
TTypeHyperlink,
TTypeTimestamp, TTypeTimestamp,
TTypeRelatedDocument, TTypeRelatedDocument,
TVersion TVersion
@ -81,6 +82,7 @@ export function createModel (builder: Builder): void {
TTypeNumber, TTypeNumber,
TTypeBoolean, TTypeBoolean,
TTypeString, TTypeString,
TTypeHyperlink,
TCollection, TCollection,
TVersion, TVersion,
TTypeIntlString, TTypeIntlString,

View File

@ -278,6 +278,10 @@ export function createModel (builder: Builder): void {
editor: setting.component.StringTypeEditor editor: setting.component.StringTypeEditor
}) })
builder.mixin(core.class.TypeHyperlink, core.class.Class, view.mixin.ObjectEditor, {
editor: setting.component.HyperlinkTypeEditor
})
builder.mixin(core.class.TypeBoolean, core.class.Class, view.mixin.ObjectEditor, { builder.mixin(core.class.TypeBoolean, core.class.Class, view.mixin.ObjectEditor, {
editor: setting.component.BooleanTypeEditor editor: setting.component.BooleanTypeEditor
}) })

View File

@ -33,6 +33,7 @@ export default mergeIds(settingId, setting, {
component: { component: {
EnumSetting: '' as AnyComponent, EnumSetting: '' as AnyComponent,
StringTypeEditor: '' as AnyComponent, StringTypeEditor: '' as AnyComponent,
HyperlinkTypeEditor: '' as AnyComponent,
BooleanTypeEditor: '' as AnyComponent, BooleanTypeEditor: '' as AnyComponent,
NumberTypeEditor: '' as AnyComponent, NumberTypeEditor: '' as AnyComponent,
DateTypeEditor: '' as AnyComponent, DateTypeEditor: '' as AnyComponent,

View File

@ -295,6 +295,13 @@ export function createModel (builder: Builder): void {
view.component.StringEditor, view.component.StringEditor,
view.component.StringEditorPopup view.component.StringEditorPopup
) )
classPresenter(
builder,
core.class.TypeHyperlink,
view.component.HyperlinkPresenter,
view.component.StringEditor,
view.component.StringEditorPopup
)
classPresenter(builder, core.class.TypeIntlString, view.component.IntlStringPresenter) classPresenter(builder, core.class.TypeIntlString, view.component.IntlStringPresenter)
classPresenter(builder, core.class.TypeNumber, view.component.NumberPresenter, view.component.NumberEditor) classPresenter(builder, core.class.TypeNumber, view.component.NumberPresenter, view.component.NumberEditor)
classPresenter( classPresenter(
@ -520,6 +527,10 @@ export function createModel (builder: Builder): void {
component: view.component.ValueFilter component: view.component.ValueFilter
}) })
builder.mixin(core.class.TypeHyperlink, core.class.Class, view.mixin.AttributeFilter, {
component: view.component.ValueFilter
})
builder.mixin(core.class.TypeBoolean, core.class.Class, view.mixin.AttributeFilter, { builder.mixin(core.class.TypeBoolean, core.class.Class, view.mixin.AttributeFilter, {
component: view.component.ValueFilter component: view.component.ValueFilter
}) })

View File

@ -43,6 +43,7 @@ export default mergeIds(viewId, view, {
StringEditor: '' as AnyComponent, StringEditor: '' as AnyComponent,
StringEditorPopup: '' as AnyComponent, StringEditorPopup: '' as AnyComponent,
StringPresenter: '' as AnyComponent, StringPresenter: '' as AnyComponent,
HyperlinkPresenter: '' as AnyComponent,
IntlStringPresenter: '' as AnyComponent, IntlStringPresenter: '' as AnyComponent,
NumberEditor: '' as AnyComponent, NumberEditor: '' as AnyComponent,
NumberPresenter: '' as AnyComponent, NumberPresenter: '' as AnyComponent,

View File

@ -36,6 +36,11 @@ export type Timestamp = number
*/ */
export type Markup = string export type Markup = string
/**
* @public
*/
export type Hyperlink = string
/** /**
* @public * @public
*/ */
@ -228,6 +233,11 @@ export interface EnumOf extends Type<string> {
of: Ref<Enum> of: Ref<Enum>
} }
/**
* @public
*/
export interface TypeHyperlink extends Type<Hyperlink> {}
/** /**
* @public * @public
*/ */

View File

@ -27,6 +27,7 @@ import type {
Enum, Enum,
EnumOf, EnumOf,
FullTextData, FullTextData,
Hyperlink,
Interface, Interface,
Obj, Obj,
PluginConfiguration, PluginConfiguration,
@ -78,6 +79,7 @@ export default plugin(coreId, {
Type: '' as Ref<Class<Type<any>>>, Type: '' as Ref<Class<Type<any>>>,
TypeString: '' as Ref<Class<Type<string>>>, TypeString: '' as Ref<Class<Type<string>>>,
TypeIntlString: '' as Ref<Class<Type<IntlString>>>, TypeIntlString: '' as Ref<Class<Type<IntlString>>>,
TypeHyperlink: '' as Ref<Class<Type<Hyperlink>>>,
TypeNumber: '' as Ref<Class<Type<number>>>, TypeNumber: '' as Ref<Class<Type<number>>>,
TypeMarkup: '' as Ref<Class<Type<string>>>, TypeMarkup: '' as Ref<Class<Type<string>>>,
TypeBoolean: '' as Ref<Class<Type<boolean>>>, TypeBoolean: '' as Ref<Class<Type<boolean>>>,
@ -131,6 +133,7 @@ export default plugin(coreId, {
Array: '' as IntlString, Array: '' as IntlString,
Name: '' as IntlString, Name: '' as IntlString,
Enum: '' as IntlString, Enum: '' as IntlString,
Description: '' as IntlString Description: '' as IntlString,
Hyperlink: '' as IntlString
} }
}) })

View File

@ -24,6 +24,7 @@
"Collection": "Collection", "Collection": "Collection",
"Array": "Array", "Array": "Array",
"Enum": "Enum", "Enum": "Enum",
"Members": "Members" "Members": "Members",
"Hyperlink": "URL"
} }
} }

View File

@ -24,6 +24,7 @@
"Collection": "Коллекция", "Collection": "Коллекция",
"Array": "Массив", "Array": "Массив",
"Enum": "Справочник", "Enum": "Справочник",
"Members": "Участники" "Members": "Участники",
"Hyperlink": "URL"
} }
} }

View File

@ -28,6 +28,7 @@ import core, {
Enum, Enum,
EnumOf, EnumOf,
generateId, generateId,
Hyperlink,
IndexKind, IndexKind,
Interface, Interface,
Markup, Markup,
@ -354,6 +355,13 @@ export function TypeString (): Type<string> {
return { _class: core.class.TypeString, label: core.string.String } return { _class: core.class.TypeString, label: core.string.String }
} }
/**
* @public
*/
export function TypeHyperlink (): Type<Hyperlink> {
return { _class: core.class.TypeHyperlink, label: core.string.Hyperlink }
}
/** /**
* @public * @public
*/ */

View File

@ -518,6 +518,7 @@ input.search {
.max-h-60 { max-height: 15rem; } .max-h-60 { max-height: 15rem; }
.max-w-30 { max-width: 7.5rem; } .max-w-30 { max-width: 7.5rem; }
.max-w-60 { max-width: 15rem; } .max-w-60 { max-width: 15rem; }
.max-w-80 { max-width: 20rem; }
.max-w-240 { max-width: 60rem; } .max-w-240 { max-width: 60rem; }
.clear-mins { .clear-mins {
min-width: 0; min-width: 0;

View File

@ -0,0 +1,189 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 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">
import { onDestroy, onMount } from 'svelte'
export let scroller: HTMLElement
export let gap: 'none' | 'small' | 'big' = 'small'
let divBar: HTMLElement
let isScrolling: boolean = false
let dX: number
let timer: number
let maskLeft: boolean = false
let maskRight: boolean = false
let mask: 'left' | 'right' | 'both' | 'none' = 'none'
let stepStyle: string
$: stepStyle = gap === 'small' ? 'gap-1' : gap === 'big' ? 'gap-2' : ''
const checkBar = (): void => {
if (divBar && scroller) {
const trackW = scroller.clientWidth
const scrollW = scroller.scrollWidth
const proc = scrollW / trackW
divBar.style.width = scroller.clientWidth / proc + 'px'
divBar.style.left = scroller.scrollLeft / proc + 'px'
if (mask === 'none') divBar.style.visibility = 'hidden'
else {
divBar.style.visibility = 'visible'
if (divBar) {
if (timer) {
clearTimeout(timer)
divBar.style.opacity = '1'
}
timer = setTimeout(() => {
if (divBar) divBar.style.opacity = '0'
}, 2000)
}
}
if (scroller.clientWidth >= scroller.scrollWidth) divBar.style.visibility = 'hidden'
}
}
const onScroll = (event: MouseEvent): void => {
if (isScrolling && divBar && scroller) {
const rectScroll = scroller.getBoundingClientRect()
let X = event.clientX - dX
if (X < rectScroll.left) X = rectScroll.left
if (X > rectScroll.right - divBar.clientWidth) X = rectScroll.right - divBar.clientWidth
divBar.style.left = X - rectScroll.x + 'px'
const leftBar = X - rectScroll.x
const widthScroll = rectScroll.width - divBar.clientWidth
const procBar = leftBar / widthScroll
scroller.scrollLeft = (scroller.scrollWidth - scroller.clientWidth) * procBar
}
}
const onScrollEnd = (event: MouseEvent): void => {
const el: HTMLElement = event.currentTarget as HTMLElement
if (el && isScrolling) {
document.removeEventListener('mousemove', onScroll)
document.body.style.userSelect = 'auto'
document.body.style.webkitUserSelect = 'auto'
}
document.removeEventListener('mouseup', onScrollEnd)
isScrolling = false
}
const onScrollStart = (event: MouseEvent): void => {
const el: HTMLElement = event.currentTarget as HTMLElement
if (el && scroller) {
dX = event.clientX - el.getBoundingClientRect().x
document.addEventListener('mouseup', onScrollEnd)
document.addEventListener('mousemove', onScroll)
document.body.style.userSelect = 'none'
document.body.style.webkitUserSelect = 'none'
isScrolling = true
}
}
const checkMask = (): void => {
maskLeft = !!(scroller && scroller.scrollLeft > 1)
maskRight = !!(scroller && scroller.scrollWidth - scroller.scrollLeft - scroller.clientWidth > 1)
if (maskLeft || maskRight) {
if (maskLeft && maskRight) mask = 'both'
else if (maskLeft) mask = 'left'
else if (maskRight) mask = 'right'
} else mask = 'none'
if (!isScrolling) checkBar()
}
onMount(() => {
if (scroller) {
const observer = new IntersectionObserver(() => checkMask(), { root: null, threshold: 0.1 })
const tempEl = scroller.querySelector('*') as HTMLElement
if (tempEl) observer.observe(tempEl)
checkMask()
scroller.addEventListener('scroll', checkMask)
}
})
onDestroy(() => {
if (scroller) scroller.removeEventListener('scroll', checkMask)
})
const _resize = (): void => checkMask()
</script>
<svelte:window on:resize={_resize} />
<div class="scrollerbar-container">
<div bind:this={scroller} class="antiStatesBar mask-{mask} {stepStyle}">
<slot />
</div>
<div
class="bar"
class:hovered={isScrolling}
bind:this={divBar}
on:mousedown={onScrollStart}
on:mouseleave={checkMask}
/>
<div class="track" class:hovered={isScrolling} />
</div>
<style lang="scss">
.scrollerbar-container {
position: relative;
min-width: 0;
}
.track {
visibility: hidden;
position: absolute;
bottom: -8px;
left: 0;
height: 5px;
transform-origin: center;
transform: scaleX(0);
transition: all 0.1s ease-in-out;
background-color: var(--scrollbar-track-color);
border-radius: 0.25rem;
}
.bar {
visibility: hidden;
position: absolute;
bottom: -8px;
left: 0;
height: 5px;
min-width: 2rem;
max-width: calc(100% - 12px);
transform-origin: center;
transform: scaleY(0.5);
background-color: var(--scrollbar-bar-color);
border-radius: 0.125rem;
opacity: 0;
box-shadow: 0 0 1px 1px var(--board-bg-color);
cursor: pointer;
z-index: 1;
transition: all 0.15s;
&:hover,
&.hovered {
background-color: var(--scrollbar-bar-hover);
transform: scaleY(1);
border-radius: 0.25rem;
opacity: 1 !important;
box-shadow: 0 0 1px black;
& + .track {
visibility: visible;
left: 0;
transform: scaleY(1);
}
}
&.hovered {
transition: none;
}
}
</style>

View File

@ -97,6 +97,7 @@ export { default as Submenu } from './components/Submenu.svelte'
export { default as TimeShiftPicker } from './components/TimeShiftPicker.svelte' export { default as TimeShiftPicker } from './components/TimeShiftPicker.svelte'
export { default as ErrorPresenter } from './components/ErrorPresenter.svelte' export { default as ErrorPresenter } from './components/ErrorPresenter.svelte'
export { default as Scroller } from './components/Scroller.svelte' export { default as Scroller } from './components/Scroller.svelte'
export { default as ScrollerBar } from './components/ScrollerBar.svelte'
export { default as TabList } from './components/TabList.svelte' export { default as TabList } from './components/TabList.svelte'
export { default as IconAdd } from './components/icons/Add.svelte' export { default as IconAdd } from './components/icons/Add.svelte'

View File

@ -0,0 +1,25 @@
<!--
// Copyright © 2022 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">
import { IndexKind } from '@hcengineering/core'
import { TypeHyperlink } from '@hcengineering/model'
import { createEventDispatcher, onMount } from 'svelte'
const dispatch = createEventDispatcher()
onMount(() => {
dispatch('change', { type: TypeHyperlink(), index: IndexKind.FullText })
})
</script>

View File

@ -39,6 +39,7 @@ import EnumTypeEditor from './components/typeEditors/EnumTypeEditor.svelte'
import NumberTypeEditor from './components/typeEditors/NumberTypeEditor.svelte' import NumberTypeEditor from './components/typeEditors/NumberTypeEditor.svelte'
import RefEditor from './components/typeEditors/RefEditor.svelte' import RefEditor from './components/typeEditors/RefEditor.svelte'
import StringTypeEditor from './components/typeEditors/StringTypeEditor.svelte' import StringTypeEditor from './components/typeEditors/StringTypeEditor.svelte'
import HyperlinkTypeEditor from './components/typeEditors/HyperlinkTypeEditor.svelte'
import WorkspaceSettings from './components/WorkspaceSettings.svelte' import WorkspaceSettings from './components/WorkspaceSettings.svelte'
import setting from './plugin' import setting from './plugin'
@ -81,6 +82,7 @@ export default async (): Promise<Resources> => ({
ManageStatuses, ManageStatuses,
ClassSetting, ClassSetting,
StringTypeEditor, StringTypeEditor,
HyperlinkTypeEditor,
BooleanTypeEditor, BooleanTypeEditor,
NumberTypeEditor, NumberTypeEditor,
RefEditor, RefEditor,

View File

@ -17,8 +17,8 @@
import { Ref, SortingOrder } from '@hcengineering/core' import { Ref, SortingOrder } from '@hcengineering/core'
import { createQuery } from '@hcengineering/presentation' import { createQuery } from '@hcengineering/presentation'
import task, { SpaceWithStates, State } from '@hcengineering/task' import task, { SpaceWithStates, State } from '@hcengineering/task'
import { getPlatformColor } from '@hcengineering/ui' import { getPlatformColor, ScrollerBar } from '@hcengineering/ui'
import { createEventDispatcher, onDestroy, onMount } from 'svelte' import { createEventDispatcher } from 'svelte'
import StatesBarElement from './StatesBarElement.svelte' import StatesBarElement from './StatesBarElement.svelte'
import type { StatesBarPosition } from '../..' import type { StatesBarPosition } from '../..'
@ -27,18 +27,7 @@
export let gap: 'none' | 'small' | 'big' = 'small' export let gap: 'none' | 'small' | 'big' = 'small'
let states: State[] = [] let states: State[] = []
let divScroll: HTMLElement let divScroll: HTMLElement
let divBar: HTMLElement
let isScrolling: boolean = false
let dX: number
let timer: number
let maskLeft: boolean = false
let maskRight: boolean = false
let mask: 'left' | 'right' | 'both' | 'none' = 'none'
let stepStyle: string
$: stepStyle = gap === 'small' ? 'gap-1' : gap === 'big' ? 'gap-2' : ''
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
@ -56,77 +45,6 @@
} }
) )
const checkBar = (): void => {
if (divBar && divScroll) {
const trackW = divScroll.clientWidth
const scrollW = divScroll.scrollWidth
const proc = scrollW / trackW
divBar.style.width = divScroll.clientWidth / proc + 'px'
divBar.style.left = divScroll.scrollLeft / proc + 'px'
if (mask === 'none') divBar.style.visibility = 'hidden'
else {
divBar.style.visibility = 'visible'
if (divBar) {
if (timer) {
clearTimeout(timer)
divBar.style.opacity = '1'
}
timer = setTimeout(() => {
if (divBar) divBar.style.opacity = '0'
}, 2000)
}
}
if (divScroll.clientWidth >= divScroll.scrollWidth) divBar.style.visibility = 'hidden'
}
}
const onScroll = (event: MouseEvent): void => {
if (isScrolling && divBar && divScroll) {
const rectScroll = divScroll.getBoundingClientRect()
let X = event.clientX - dX
if (X < rectScroll.left) X = rectScroll.left
if (X > rectScroll.right - divBar.clientWidth) X = rectScroll.right - divBar.clientWidth
divBar.style.left = X - rectScroll.x + 'px'
const leftBar = X - rectScroll.x
const widthScroll = rectScroll.width - divBar.clientWidth
const procBar = leftBar / widthScroll
divScroll.scrollLeft = (divScroll.scrollWidth - divScroll.clientWidth) * procBar
}
}
const onScrollEnd = (event: MouseEvent): void => {
const el: HTMLElement = event.currentTarget as HTMLElement
if (el && isScrolling) {
document.removeEventListener('mousemove', onScroll)
document.body.style.userSelect = 'auto'
document.body.style.webkitUserSelect = 'auto'
}
document.removeEventListener('mouseup', onScrollEnd)
isScrolling = false
}
const onScrollStart = (event: MouseEvent): void => {
const el: HTMLElement = event.currentTarget as HTMLElement
if (el && divScroll) {
dX = event.clientX - el.getBoundingClientRect().x
document.addEventListener('mouseup', onScrollEnd)
document.addEventListener('mousemove', onScroll)
document.body.style.userSelect = 'none'
document.body.style.webkitUserSelect = 'none'
isScrolling = true
}
}
const checkMask = (): void => {
maskLeft = !!(divScroll && divScroll.scrollLeft > 1)
maskRight = !!(divScroll && divScroll.scrollWidth - divScroll.scrollLeft - divScroll.clientWidth > 1)
if (maskLeft || maskRight) {
if (maskLeft && maskRight) mask = 'both'
else if (maskLeft) mask = 'left'
else if (maskRight) mask = 'right'
} else mask = 'none'
if (!isScrolling) checkBar()
}
const selectItem = (ev: Event, item: State): void => { const selectItem = (ev: Event, item: State): void => {
const el: HTMLElement = ev.currentTarget as HTMLElement const el: HTMLElement = ev.currentTarget as HTMLElement
const rect = el.getBoundingClientRect() const rect = el.getBoundingClientRect()
@ -149,100 +67,18 @@
else if (n === states.length - 1) return 'end' else if (n === states.length - 1) return 'end'
else return 'middle' else return 'middle'
} }
onMount(() => {
if (divScroll) {
const observer = new IntersectionObserver(() => checkMask(), { root: null, threshold: 0.1 })
const tempEl = divScroll.querySelector('*') as HTMLElement
if (tempEl) observer.observe(tempEl)
checkMask()
divScroll.addEventListener('scroll', checkMask)
}
})
onDestroy(() => {
if (divScroll) divScroll.removeEventListener('scroll', checkMask)
})
const _resize = (): void => checkMask()
</script> </script>
<svelte:window on:resize={_resize} /> <ScrollerBar {gap} bind:scroller={divScroll}>
<div class="statesbar-container"> {#each states as item, i (item._id)}
<div bind:this={divScroll} class="antiStatesBar mask-{mask} {stepStyle}"> <StatesBarElement
{#each states as item, i (item._id)} label={item.title}
<StatesBarElement position={getPosition(i)}
label={item.title} selected={item._id === state}
position={getPosition(i)} color={getPlatformColor(item.color)}
selected={item._id === state} on:click={(ev) => {
color={getPlatformColor(item.color)} if (item._id !== state) selectItem(ev, item)
on:click={(ev) => { }}
if (item._id !== state) selectItem(ev, item) />
}} {/each}
/> </ScrollerBar>
{/each}
</div>
<div
class="bar"
class:hovered={isScrolling}
bind:this={divBar}
on:mousedown={onScrollStart}
on:mouseleave={checkMask}
/>
<div class="track" class:hovered={isScrolling} />
</div>
<style lang="scss">
.statesbar-container {
position: relative;
min-width: 0;
}
.track {
visibility: hidden;
position: absolute;
bottom: -8px;
left: 0;
height: 5px;
transform-origin: center;
transform: scaleX(0);
transition: all 0.1s ease-in-out;
background-color: var(--scrollbar-track-color);
border-radius: 0.25rem;
}
.bar {
visibility: hidden;
position: absolute;
bottom: -8px;
left: 0;
height: 5px;
min-width: 2rem;
max-width: calc(100% - 12px);
transform-origin: center;
transform: scaleY(0.5);
background-color: var(--scrollbar-bar-color);
border-radius: 0.125rem;
opacity: 0;
box-shadow: 0 0 1px 1px var(--board-bg-color);
cursor: pointer;
z-index: 1;
transition: all 0.15s;
&:hover,
&.hovered {
background-color: var(--scrollbar-bar-hover);
transform: scaleY(1);
border-radius: 0.25rem;
opacity: 1 !important;
box-shadow: 0 0 1px black;
& + .track {
visibility: visible;
left: 0;
transform: scaleY(1);
}
}
&.hovered {
transition: none;
}
}
</style>

View File

@ -0,0 +1,27 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 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">
import { ScrollerBar } from '@hcengineering/ui'
export let value: string
let divScroll: HTMLElement
</script>
<div class="clear-mins max-w-80">
<ScrollerBar bind:scroller={divScroll}>
<a href={value} target="_blank" class="select-text">{value}</a>
</ScrollerBar>
</div>

View File

@ -46,6 +46,7 @@ import RolePresenter from './components/RolePresenter.svelte'
import SpacePresenter from './components/SpacePresenter.svelte' import SpacePresenter from './components/SpacePresenter.svelte'
import StringEditor from './components/StringEditor.svelte' import StringEditor from './components/StringEditor.svelte'
import StringPresenter from './components/StringPresenter.svelte' import StringPresenter from './components/StringPresenter.svelte'
import HyperlinkPresenter from './components/HyperlinkPresenter.svelte'
import Table from './components/Table.svelte' import Table from './components/Table.svelte'
import TableBrowser from './components/TableBrowser.svelte' import TableBrowser from './components/TableBrowser.svelte'
import TimestampPresenter from './components/TimestampPresenter.svelte' import TimestampPresenter from './components/TimestampPresenter.svelte'
@ -123,6 +124,7 @@ export default async (): Promise<Resources> => ({
SpacePresenter, SpacePresenter,
StringEditor, StringEditor,
StringPresenter, StringPresenter,
HyperlinkPresenter,
NumberEditor, NumberEditor,
NumberPresenter, NumberPresenter,
BooleanPresenter, BooleanPresenter,