mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-11 20:27:43 +02:00
Merge branch 'develop' of https://github.com/hcengineering/platform into staging-new
Signed-off-by: Artem Savchenko <armisav@gmail.com>
This commit is contained in:
@@ -97,8 +97,8 @@ jobs:
|
||||
node common/scripts/install-run-rush.js install
|
||||
fi
|
||||
|
||||
# - name: Cheking model is updated...
|
||||
# run: node common/scripts/check_model_version.js
|
||||
- name: Checking model version is updated...
|
||||
run: node common/scripts/check_model_version.js
|
||||
|
||||
- name: Checking for mis-matching dependencies...
|
||||
run: node common/scripts/install-run-rush.js check
|
||||
|
||||
@@ -27,7 +27,7 @@ const pagesWithFoundInPageListener = new WeakSet<WebContents>()
|
||||
const overlayViewsByWindowId = new Map<number, BrowserView>()
|
||||
const overlayVisibleByWindowId = new Map<number, boolean>()
|
||||
|
||||
const OVERLAY_WIDTH = 392
|
||||
const OVERLAY_WIDTH = 432
|
||||
const OVERLAY_HEIGHT = 64
|
||||
|
||||
function resolveFindTarget (sender: WebContents): WebContents {
|
||||
|
||||
@@ -20,7 +20,8 @@ const DEBOUNCE_MS = 300
|
||||
/**
|
||||
* Find UI is loaded in a separate BrowserView (see `findInPageOverlayHost.ts`) so
|
||||
* `findInPage` on the page webContents does not search the query in this input.
|
||||
* Shadow DOM keeps chrome text minimal. Match counts are not shown.
|
||||
* Shadow DOM keeps chrome text minimal. Match count / current index come from
|
||||
* `found-in-page` forwarded as `onFindInPageResult`.
|
||||
*
|
||||
* Chromium moves focus to the matched text in the **page** webContents after
|
||||
* `findInPage`, so the overlay stops receiving keystrokes unless we put focus back
|
||||
@@ -49,6 +50,11 @@ export function setupDesktopFindInPageBar (electronApi: IPCMainExposed): void {
|
||||
input.spellcheck = false
|
||||
input.setAttribute('aria-label', 'Find in page')
|
||||
|
||||
const matchCount = document.createElement('span')
|
||||
matchCount.className = 'match-count'
|
||||
matchCount.setAttribute('aria-live', 'polite')
|
||||
matchCount.setAttribute('aria-atomic', 'true')
|
||||
|
||||
const nav = document.createElement('div')
|
||||
nav.className = 'nav'
|
||||
nav.setAttribute('role', 'group')
|
||||
@@ -76,6 +82,7 @@ export function setupDesktopFindInPageBar (electronApi: IPCMainExposed): void {
|
||||
closeBtn.title = 'Close'
|
||||
|
||||
root.appendChild(input)
|
||||
root.appendChild(matchCount)
|
||||
root.appendChild(nav)
|
||||
root.appendChild(closeBtn)
|
||||
shadow.appendChild(style)
|
||||
@@ -89,6 +96,19 @@ export function setupDesktopFindInPageBar (electronApi: IPCMainExposed): void {
|
||||
return input.value.trim()
|
||||
}
|
||||
|
||||
function clearMatchCount (): void {
|
||||
matchCount.textContent = ''
|
||||
matchCount.removeAttribute('title')
|
||||
}
|
||||
|
||||
function describeMatchCount (matches: number, activeOrdinal: number): { label: string, title: string } {
|
||||
if (matches <= 0) {
|
||||
return { label: '0/0', title: 'No matches' }
|
||||
}
|
||||
const cur = activeOrdinal > 0 ? Math.min(activeOrdinal, matches) : 1
|
||||
return { label: `${cur}/${matches}`, title: `Match ${cur} of ${matches}` }
|
||||
}
|
||||
|
||||
function updateNavState (): void {
|
||||
const q = getQuery()
|
||||
prevBtn.disabled = q === ''
|
||||
@@ -114,6 +134,7 @@ export function setupDesktopFindInPageBar (electronApi: IPCMainExposed): void {
|
||||
function hide (): void {
|
||||
visible = false
|
||||
host.style.display = 'none'
|
||||
clearMatchCount()
|
||||
prevBtn.disabled = true
|
||||
nextBtn.disabled = true
|
||||
try {
|
||||
@@ -165,6 +186,7 @@ export function setupDesktopFindInPageBar (electronApi: IPCMainExposed): void {
|
||||
debounceTimer = undefined
|
||||
const q = getQuery()
|
||||
if (q === '') {
|
||||
clearMatchCount()
|
||||
void electronApi.stopFindInPage('clearSelection').catch(() => {})
|
||||
updateNavState()
|
||||
return
|
||||
@@ -226,6 +248,20 @@ export function setupDesktopFindInPageBar (electronApi: IPCMainExposed): void {
|
||||
},
|
||||
true
|
||||
)
|
||||
|
||||
electronApi.onFindInPageResult((result) => {
|
||||
if (!visible) {
|
||||
return
|
||||
}
|
||||
const q = getQuery()
|
||||
if (q === '') {
|
||||
clearMatchCount()
|
||||
return
|
||||
}
|
||||
const { label, title } = describeMatchCount(result.matches, result.activeMatchOrdinal)
|
||||
matchCount.textContent = label
|
||||
matchCount.title = title
|
||||
})
|
||||
}
|
||||
|
||||
/** Left-pointing chevron; `.next::after` mirrors with `scaleX(-1)` for identical vertical alignment. */
|
||||
@@ -314,6 +350,28 @@ const shadowStyles = `
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.match-count {
|
||||
min-width: 1.5rem;
|
||||
padding: 0 2px;
|
||||
font-size: 12px;
|
||||
line-height: 1.35;
|
||||
font-variant-numeric: tabular-nums;
|
||||
text-align: right;
|
||||
color: rgba(0, 0, 0, 0.52);
|
||||
user-select: none;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.match-count {
|
||||
color: rgba(255, 255, 255, 0.48);
|
||||
}
|
||||
}
|
||||
|
||||
:host-context([data-theme='theme-dark']) .match-count {
|
||||
color: rgba(255, 255, 255, 0.48);
|
||||
}
|
||||
|
||||
.nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -311,6 +311,7 @@ services:
|
||||
- COMMUNICATION_API_ENABLED=true
|
||||
- BACKUP_URL=http://huly.local:4039/api/backup,
|
||||
- EXCLUDED_APPLICATIONS_FOR_ANONYMOUS=[]
|
||||
- DISABLED_FEATURES=auto-translate,mailboxes
|
||||
# - DISABLE_SIGNUP=true
|
||||
- OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4318/v1/traces
|
||||
- PULSE_URL=ws://huly.local:8099/ws
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
import BottomActionComponent from './BottomAction.svelte'
|
||||
import Providers from './Providers.svelte'
|
||||
import Tabs from './Tabs.svelte'
|
||||
import { loginFormMinHeight, loginFormPadding } from '../loginFormLayout'
|
||||
|
||||
interface Action {
|
||||
i18n: IntlString
|
||||
@@ -125,8 +126,8 @@
|
||||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||
<form
|
||||
class="container"
|
||||
style:padding={$deviceInfo.docWidth <= 480 ? '.25rem 1.25rem' : '4rem 5rem'}
|
||||
style:min-height={$deviceInfo.docHeight > 720 ? '42rem' : '0'}
|
||||
style:padding={loginFormPadding($deviceInfo.docWidth, $deviceInfo.docHeight)}
|
||||
style:min-height={loginFormMinHeight($deviceInfo.docHeight)}
|
||||
on:keydown={(evt) => {
|
||||
if (evt.key === 'Enter') {
|
||||
evt.preventDefault()
|
||||
@@ -225,7 +226,8 @@
|
||||
|
||||
<style lang="scss">
|
||||
.container {
|
||||
overflow: hidden;
|
||||
overflow-x: hidden;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -252,31 +254,14 @@
|
||||
grid-column-end: 3;
|
||||
}
|
||||
|
||||
.hint {
|
||||
margin-top: 1rem;
|
||||
font-size: 0.8rem;
|
||||
color: var(--theme-content-color);
|
||||
}
|
||||
|
||||
.send {
|
||||
margin-top: 0rem;
|
||||
}
|
||||
}
|
||||
.grow-separator {
|
||||
flex-grow: 1;
|
||||
}
|
||||
.footer {
|
||||
margin-top: 1.75rem;
|
||||
font-size: 0.8rem;
|
||||
color: var(--theme-content-color);
|
||||
span {
|
||||
color: var(--theme-darker-color);
|
||||
}
|
||||
a {
|
||||
font-weight: 500;
|
||||
text-decoration: underline;
|
||||
color: var(--theme-content-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
import { logIn, workbenchId } from '@hcengineering/workbench'
|
||||
import { onMount } from 'svelte'
|
||||
import { loginAction, recoveryAction } from '../actions'
|
||||
import { loginFormMinHeight, loginFormPadding } from '../loginFormLayout'
|
||||
import login from '../plugin'
|
||||
|
||||
const location = getCurrentLocation()
|
||||
@@ -259,8 +260,8 @@
|
||||
{:else if showJoinWithAccount}
|
||||
<div
|
||||
class="join-with-account-container"
|
||||
style:padding={$deviceInfo.docWidth <= 480 ? '.25rem 1.25rem' : '4rem 5rem'}
|
||||
style:min-height={$deviceInfo.docHeight > 720 ? '42rem' : '0'}
|
||||
style:padding={loginFormPadding($deviceInfo.docWidth, $deviceInfo.docHeight)}
|
||||
style:min-height={loginFormMinHeight($deviceInfo.docHeight)}
|
||||
>
|
||||
<div class="join-with-account">
|
||||
<div class="join-title">
|
||||
|
||||
@@ -16,8 +16,11 @@
|
||||
<script lang="ts">
|
||||
import { type IntlString, Severity, Status } from '@hcengineering/platform'
|
||||
import { signupStore } from '@hcengineering/analytics-providers'
|
||||
import { deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
|
||||
import { onMount } from 'svelte'
|
||||
|
||||
import { loginFormPaddingInline } from '../loginFormLayout'
|
||||
|
||||
import { type BottomAction, doLoginAsGuest, doLoginNavigate, LoginMethods } from '../index'
|
||||
import LoginPasswordForm from './LoginPasswordForm.svelte'
|
||||
import LoginOtpForm from './LoginOtpForm.svelte'
|
||||
@@ -88,7 +91,7 @@
|
||||
{:else}
|
||||
<LoginPasswordForm {navigateUrl} {signUpDisabled} {email} {caption} {subtitle} {onLogin} on:change={changeMethod} />
|
||||
{/if}
|
||||
<div class="actions">
|
||||
<div class="actions" style:margin-inline-start={loginFormPaddingInline($deviceInfo.docWidth, $deviceInfo.docHeight)}>
|
||||
<BottomActionComponent action={method === LoginMethods.Otp ? loginWithPasswordAction : loginWithCodeAction} />
|
||||
<div class="login-as-guest">
|
||||
<BottomActionComponent action={loginAsGuest} />
|
||||
@@ -96,9 +99,6 @@
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.actions {
|
||||
margin-left: 5rem;
|
||||
}
|
||||
.login-as-guest {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
import { LoginInfo } from '@hcengineering/account-client'
|
||||
|
||||
import Tabs from './Tabs.svelte'
|
||||
import { loginFormMinHeight, loginFormPadding } from '../loginFormLayout'
|
||||
import { BottomAction, doLoginNavigate, doValidateOtp, OtpLoginSteps, loginOtp } from '../index'
|
||||
import login from '../plugin'
|
||||
import BottomActionComponent from './BottomAction.svelte'
|
||||
@@ -238,8 +239,8 @@
|
||||
<form
|
||||
bind:this={formElement}
|
||||
class="container"
|
||||
style:padding={$deviceInfo.docWidth <= 480 ? '.25rem 1.25rem' : '4rem 5rem'}
|
||||
style:min-height={$deviceInfo.docHeight > 720 ? '42rem' : '0'}
|
||||
style:padding={loginFormPadding($deviceInfo.docWidth, $deviceInfo.docHeight)}
|
||||
style:min-height={loginFormMinHeight($deviceInfo.docHeight)}
|
||||
>
|
||||
<div class="header">
|
||||
<Tabs {loginState} {signUpDisabled} />
|
||||
@@ -334,7 +335,8 @@
|
||||
}
|
||||
|
||||
.container {
|
||||
overflow: hidden;
|
||||
overflow-x: hidden;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
|
||||
@@ -15,13 +15,14 @@
|
||||
<script lang="ts">
|
||||
import { deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
|
||||
|
||||
import { loginFormMinHeight, loginFormPadding } from '../loginFormLayout'
|
||||
import Tabs from './Tabs.svelte'
|
||||
import Providers from './Providers.svelte'
|
||||
</script>
|
||||
|
||||
<div
|
||||
style:padding={$deviceInfo.docWidth <= 480 ? '.25rem 1.25rem' : '4rem 5rem'}
|
||||
style:min-height={$deviceInfo.docHeight > 720 ? '42rem' : '0'}
|
||||
style:padding={loginFormPadding($deviceInfo.docWidth, $deviceInfo.docHeight)}
|
||||
style:min-height={loginFormMinHeight($deviceInfo.docHeight)}
|
||||
>
|
||||
<Tabs loginState={'login'} signUpDisabled={true} />
|
||||
<Providers />
|
||||
|
||||
@@ -17,7 +17,9 @@
|
||||
import { OK, Severity, Status } from '@hcengineering/platform'
|
||||
import { logIn } from '@hcengineering/workbench'
|
||||
import { signupStore } from '@hcengineering/analytics-providers'
|
||||
import { deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
|
||||
|
||||
import { loginFormPaddingInline } from '../loginFormLayout'
|
||||
import BottomActionComponent from './BottomAction.svelte'
|
||||
import login from '../plugin'
|
||||
import { getPasswordValidationRules } from '../validations'
|
||||
@@ -146,7 +148,7 @@
|
||||
{/if}
|
||||
|
||||
{#if useOTP}
|
||||
<div class="action">
|
||||
<div class="action" style:margin-inline-start={loginFormPaddingInline($deviceInfo.docWidth, $deviceInfo.docHeight)}>
|
||||
<BottomActionComponent action={withPasswordAction} />
|
||||
</div>
|
||||
{:else}
|
||||
@@ -154,10 +156,6 @@
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.action {
|
||||
margin-left: 5rem;
|
||||
}
|
||||
|
||||
// TODO: Refactor me please
|
||||
.placeholder {
|
||||
height: 1.125rem;
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// Copyright © 2026 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.
|
||||
//
|
||||
|
||||
/** Padding for the form and the horizontal amount used to line up sibling rows (e.g. bottom links). */
|
||||
export interface LoginFormLayout {
|
||||
padding: string
|
||||
/** Same as the horizontal part of `padding`; use for `margin-inline-start` on rows below the form. */
|
||||
paddingInline: string
|
||||
}
|
||||
|
||||
export function getLoginFormLayout (docWidth: number, docHeight: number): LoginFormLayout {
|
||||
if (docWidth <= 480) {
|
||||
return { padding: '.25rem 1.25rem', paddingInline: '1.25rem' }
|
||||
}
|
||||
if (docHeight <= 820) {
|
||||
return { padding: '1rem 2rem', paddingInline: '2rem' }
|
||||
}
|
||||
return { padding: '4rem 5rem', paddingInline: '5rem' }
|
||||
}
|
||||
|
||||
export function loginFormPadding (docWidth: number, docHeight: number): string {
|
||||
return getLoginFormLayout(docWidth, docHeight).padding
|
||||
}
|
||||
|
||||
export function loginFormPaddingInline (docWidth: number, docHeight: number): string {
|
||||
return getLoginFormLayout(docWidth, docHeight).paddingInline
|
||||
}
|
||||
|
||||
export function loginFormMinHeight (docHeight: number): string {
|
||||
if (docHeight <= 880) {
|
||||
return '0'
|
||||
}
|
||||
return 'min(42rem, calc(100dvh - 12rem))'
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright © 2026 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 { readFile } from 'fs/promises'
|
||||
import { join } from 'path'
|
||||
import { extract } from '../extractors'
|
||||
|
||||
const DOCX_TYPE = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
||||
|
||||
describe('docx extractor smoke', () => {
|
||||
it('extracts plain text from a minimal docx', async () => {
|
||||
const fName = join(__dirname, 'fixtures', 'minimal-smoke.docx')
|
||||
const data = await readFile(fName)
|
||||
|
||||
const { matched, content, error } = await extract('minimal-smoke.docx', DOCX_TYPE, data)
|
||||
|
||||
expect(error).toBeUndefined()
|
||||
expect(matched).toBe(true)
|
||||
expect(content).toContain('Hello DOCX smoke test')
|
||||
})
|
||||
})
|
||||
Binary file not shown.
@@ -1,3 +1,15 @@
|
||||
// Copyright © 2026 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 { convertToHtml, images } from 'mammoth'
|
||||
import { contentType } from 'mime-types'
|
||||
import { DocumentExtractor } from './types'
|
||||
@@ -19,13 +31,7 @@ export const docxExtractor: DocumentExtractor = {
|
||||
const htmlData = await convertToHtml(
|
||||
{ buffer: data },
|
||||
{
|
||||
convertImage: images.imgElement((image) => {
|
||||
return image.read('base64').then(function (imageBuffer) {
|
||||
return {
|
||||
src: ''
|
||||
}
|
||||
})
|
||||
})
|
||||
convertImage: images.imgElement(async () => ({ src: '' }))
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user