mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-17 05:47:32 +00:00
UBER-593: hyperlink editor (#3506)
Signed-off-by: Vyacheslav Tumanov <me@slavatumanov.me>
This commit is contained in:
parent
ac4eeb65f7
commit
3fcd9081be
@ -452,8 +452,8 @@ export function createModel (builder: Builder): void {
|
||||
builder,
|
||||
core.class.TypeHyperlink,
|
||||
view.component.HyperlinkPresenter,
|
||||
view.component.StringEditor,
|
||||
view.component.StringEditorPopup
|
||||
view.component.HyperlinkEditor,
|
||||
view.component.HyperlinkEditorPopup
|
||||
)
|
||||
classPresenter(builder, core.class.TypeIntlString, view.component.IntlStringPresenter)
|
||||
classPresenter(builder, core.class.TypeNumber, view.component.NumberPresenter, view.component.NumberEditor)
|
||||
|
@ -47,6 +47,8 @@ export default mergeIds(viewId, view, {
|
||||
StringEditorPopup: '' as AnyComponent,
|
||||
StringPresenter: '' as AnyComponent,
|
||||
HyperlinkPresenter: '' as AnyComponent,
|
||||
HyperlinkEditor: '' as AnyComponent,
|
||||
HyperlinkEditorPopup: '' as AnyComponent,
|
||||
IntlStringPresenter: '' as AnyComponent,
|
||||
NumberEditor: '' as AnyComponent,
|
||||
NumberPresenter: '' as AnyComponent,
|
||||
|
@ -42,7 +42,7 @@
|
||||
"Facebook": "Facebook",
|
||||
"HomepagePlaceholder": "https://jappleseed.com",
|
||||
"Homepage": "Home page",
|
||||
"SocialLinks": "Socail links",
|
||||
"SocialLinks": "Social links",
|
||||
"ViewActivity": "View activity",
|
||||
"PersonAlreadyExists": "Contact already exists...",
|
||||
"Status": "Status",
|
||||
|
@ -103,6 +103,8 @@
|
||||
"Show": "Show",
|
||||
"FilterArrayAll": "includes all",
|
||||
"FilterArrayAny": "includes any",
|
||||
"Or": "Or"
|
||||
"Or": "Or",
|
||||
"HyperlinkPlaceholder": "https://jappleseed.com",
|
||||
"CopyToClipboard": "Copy to clipboard"
|
||||
}
|
||||
}
|
||||
|
@ -99,6 +99,8 @@
|
||||
"Show": "Отображение",
|
||||
"FilterArrayAll": "содержит все",
|
||||
"FilterArrayAny": "содержит любое из",
|
||||
"Or": "Или"
|
||||
"Or": "Или",
|
||||
"HyperlinkPlaceholder": "https://jappleseed.com",
|
||||
"CopyToClipboard": "Скопировать в буфер обмена"
|
||||
}
|
||||
}
|
||||
|
62
plugins/view-resources/src/components/HyperlinkEditor.svelte
Normal file
62
plugins/view-resources/src/components/HyperlinkEditor.svelte
Normal file
@ -0,0 +1,62 @@
|
||||
<!--
|
||||
// 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">
|
||||
import type { IntlString } from '@hcengineering/platform'
|
||||
import type { ButtonSize, ButtonKind } from '@hcengineering/ui'
|
||||
import { Label, showPopup, eventToHTMLElement, Button, parseURL } from '@hcengineering/ui'
|
||||
import HyperlinkEditorPopup from './HyperlinkEditorPopup.svelte'
|
||||
|
||||
export let placeholder: IntlString
|
||||
export let value: string
|
||||
export let onChange: (value: string) => void = () => {}
|
||||
export let kind: ButtonKind | undefined = undefined
|
||||
export let readonly = false
|
||||
export let size: ButtonSize = 'small'
|
||||
export let justify: 'left' | 'center' = 'center'
|
||||
export let width: string | undefined = 'fit-content'
|
||||
|
||||
let shown: boolean = false
|
||||
</script>
|
||||
|
||||
<Button
|
||||
{kind}
|
||||
{size}
|
||||
{justify}
|
||||
{width}
|
||||
on:click={(ev) => {
|
||||
if (!shown) {
|
||||
showPopup(HyperlinkEditorPopup, { value, editable: !readonly }, eventToHTMLElement(ev), (res) => {
|
||||
if (res === 'open') {
|
||||
const url = parseURL(value)
|
||||
if (url.startsWith('http://') || url.startsWith('https://')) {
|
||||
window.open(url)
|
||||
}
|
||||
} else if (res !== undefined) {
|
||||
value = res
|
||||
onChange(value)
|
||||
}
|
||||
shown = false
|
||||
})
|
||||
}
|
||||
}}
|
||||
>
|
||||
<svelte:fragment slot="content">
|
||||
{#if value}
|
||||
<span class="caption-color overflow-label pointer-events-none">{value}</span>
|
||||
{:else}
|
||||
<span class="content-dark-color pointer-events-none"><Label label={placeholder} /></span>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</Button>
|
@ -0,0 +1,143 @@
|
||||
<!--
|
||||
// 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">
|
||||
import type { IntlString } from '@hcengineering/platform'
|
||||
import { translate } from '@hcengineering/platform'
|
||||
import { themeStore, Label } from '@hcengineering/ui'
|
||||
import { Button, IconArrowRight, IconBlueCheck, IconClose } from '@hcengineering/ui'
|
||||
import { createEventDispatcher, onMount } from 'svelte'
|
||||
import view from '../plugin'
|
||||
|
||||
export let value: string = ''
|
||||
export let editable: boolean | undefined = true
|
||||
|
||||
const placeholder: IntlString = view.string.HyperlinkPlaceholder
|
||||
const dispatch = createEventDispatcher()
|
||||
let input: HTMLInputElement
|
||||
let phTranslate: string
|
||||
$: translate(placeholder, {}, $themeStore.language).then((tr) => (phTranslate = tr))
|
||||
|
||||
onMount(() => {
|
||||
if (input) input.focus()
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="editor-container buttons-group xsmall-gap">
|
||||
<div class="cover-channel show">
|
||||
{#if editable}
|
||||
<input
|
||||
bind:this={input}
|
||||
class="search"
|
||||
type="text"
|
||||
bind:value
|
||||
placeholder={phTranslate}
|
||||
style="width: 100%;"
|
||||
on:keypress={(ev) => {
|
||||
if (ev.key === 'Enter') {
|
||||
ev.preventDefault()
|
||||
ev.stopPropagation()
|
||||
dispatch('close', value)
|
||||
}
|
||||
}}
|
||||
on:change
|
||||
/>
|
||||
{:else if value}
|
||||
<span class="overflow-label">{value}</span>
|
||||
{:else}
|
||||
<span class="content-dark-color"><Label label={placeholder} /></span>
|
||||
{/if}
|
||||
</div>
|
||||
<Button
|
||||
focusIndex={2}
|
||||
kind={'ghost'}
|
||||
size={'small'}
|
||||
icon={IconClose}
|
||||
disabled={value === ''}
|
||||
on:click={() => {
|
||||
if (input) {
|
||||
value = ''
|
||||
input.focus()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{#if editable}
|
||||
<Button
|
||||
id="channel-ok"
|
||||
focusIndex={3}
|
||||
kind={'ghost'}
|
||||
size={'small'}
|
||||
icon={IconBlueCheck}
|
||||
on:click={() => {
|
||||
dispatch('close', value)
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
<Button
|
||||
focusIndex={4}
|
||||
kind={'ghost'}
|
||||
size={'small'}
|
||||
icon={IconArrowRight}
|
||||
on:click={() => {
|
||||
dispatch('update', value)
|
||||
dispatch('close', 'open')
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.cover-channel {
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
|
||||
&.show::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 0.25rem;
|
||||
opacity: 0.95;
|
||||
}
|
||||
&.show.copied::before {
|
||||
border-color: var(--theme-divider-color);
|
||||
}
|
||||
&.show::after {
|
||||
content: attr(data-tooltip);
|
||||
position: absolute;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
min-width: 0;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: calc(100% - 0.5rem);
|
||||
text-align: center;
|
||||
font-size: 0.75rem;
|
||||
color: var(--theme-content-color);
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
|
||||
.editor-container {
|
||||
padding: 0.5rem;
|
||||
background-color: var(--theme-popup-color);
|
||||
border: 1px solid var(--theme-popup-divider);
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: var(--theme-popup-shadow);
|
||||
}
|
||||
</style>
|
@ -44,6 +44,8 @@ import ValueFilter from './components/filter/ValueFilter.svelte'
|
||||
import HTMLEditor from './components/HTMLEditor.svelte'
|
||||
import HTMLPresenter from './components/HTMLPresenter.svelte'
|
||||
import HyperlinkPresenter from './components/HyperlinkPresenter.svelte'
|
||||
import HyperlinkEditor from './components/HyperlinkEditor.svelte'
|
||||
import HyperlinkEditorPopup from './components/HyperlinkEditorPopup.svelte'
|
||||
import IntlStringPresenter from './components/IntlStringPresenter.svelte'
|
||||
import GithubPresenter from './components/linkPresenters/GithubPresenter.svelte'
|
||||
import YoutubePresenter from './components/linkPresenters/YoutubePresenter.svelte'
|
||||
@ -202,6 +204,8 @@ export default async (): Promise<Resources> => ({
|
||||
StringEditor,
|
||||
StringPresenter,
|
||||
HyperlinkPresenter,
|
||||
HyperlinkEditor,
|
||||
HyperlinkEditorPopup,
|
||||
NumberEditor,
|
||||
NumberPresenter,
|
||||
BooleanPresenter,
|
||||
|
@ -832,7 +832,9 @@ const view = plugin(viewId, {
|
||||
Delete: '' as IntlString,
|
||||
Then: '' as IntlString,
|
||||
Or: '' as IntlString,
|
||||
Subscribed: '' as IntlString
|
||||
Subscribed: '' as IntlString,
|
||||
HyperlinkPlaceholder: '' as IntlString,
|
||||
CopyToClipboard: '' as IntlString
|
||||
},
|
||||
icon: {
|
||||
Table: '' as Asset,
|
||||
|
Loading…
Reference in New Issue
Block a user