diff --git a/.github/workflows/baseimage.yaml b/.github/workflows/baseimage.yaml index d4c28a7968..0d33f7f07f 100644 --- a/.github/workflows/baseimage.yaml +++ b/.github/workflows/baseimage.yaml @@ -31,7 +31,7 @@ jobs: "containerd-snapshotter": true } } - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: fetch-depth: 1 filter: tree:0 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 753708009e..c71da5d6e5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -68,7 +68,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: ref: ${{ github.event.inputs.ref || github.ref }} fetch-depth: 0 @@ -78,7 +78,7 @@ jobs: with: node-version-file: '.nvmrc' - name: Cache node modules - uses: actions/cache@v5 + uses: actions/cache@v6 env: cache-name: cache-node-platform with: @@ -127,7 +127,7 @@ jobs: run: node common/scripts/install-run-rush.js validate - name: Cache build results - uses: actions/cache@v5 + uses: actions/cache@v6 env: cache-name: cache-build-results with: @@ -139,7 +139,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: fetch-depth: 0 filter: tree:0 @@ -150,7 +150,7 @@ jobs: node-version-file: '.nvmrc' - name: Cache build results - uses: actions/cache@v5 + uses: actions/cache@v6 env: cache-name: cache-build-results with: @@ -165,7 +165,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: fetch-depth: 0 submodules: recursive @@ -173,7 +173,7 @@ jobs: with: node-version-file: '.nvmrc' - name: Cache build results - uses: actions/cache@v5 + uses: actions/cache@v6 env: cache-name: cache-build-results with: @@ -196,7 +196,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: fetch-depth: 0 filter: tree:0 @@ -205,7 +205,7 @@ jobs: with: node-version-file: '.nvmrc' - name: Cache build results - uses: actions/cache@v5 + uses: actions/cache@v6 env: cache-name: cache-build-results with: @@ -234,7 +234,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: fetch-depth: 0 filter: tree:0 @@ -243,7 +243,7 @@ jobs: with: node-version-file: '.nvmrc' - name: Cache node modules - uses: actions/cache@v5 + uses: actions/cache@v6 env: cache-name: cache-node-platform with: @@ -322,7 +322,7 @@ jobs: name: playwright-results path: ./tests/sanity/playwright-report/ # - name: Get Allure history - # uses: actions/checkout@v6 + # uses: actions/checkout@v7 # if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') }} # continue-on-error: true # with: @@ -366,7 +366,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: fetch-depth: 0 filter: tree:0 @@ -375,7 +375,7 @@ jobs: with: node-version-file: '.nvmrc' - name: Cache node modules - uses: actions/cache@v5 + uses: actions/cache@v6 env: cache-name: cache-node-platform with: @@ -463,7 +463,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: fetch-depth: 0 filter: tree:0 @@ -472,7 +472,7 @@ jobs: with: node-version-file: '.nvmrc' - name: Cache node modules - uses: actions/cache@v5 + uses: actions/cache@v6 env: cache-name: cache-node-platform with: @@ -544,7 +544,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: fetch-depth: 0 filter: tree:0 @@ -553,7 +553,7 @@ jobs: with: node-version-file: '.nvmrc' - name: Cache node modules - uses: actions/cache@v5 + uses: actions/cache@v6 env: cache-name: cache-node-platform with: @@ -641,7 +641,7 @@ jobs: timeout-minutes: 60 steps: # https://github.com/actions/runner-images/issues/2840#issuecomment-790492173 - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: fetch-depth: 0 filter: tree:0 @@ -674,7 +674,7 @@ jobs: with: version: 10 - name: Cache node modules - uses: actions/cache@v5 + uses: actions/cache@v6 env: cache-name: cache-node-platform with: @@ -744,7 +744,7 @@ jobs: runs-on: macos-latest timeout-minutes: 60 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: fetch-depth: 0 filter: tree:0 @@ -755,7 +755,7 @@ jobs: node-version-file: '.nvmrc' - name: Cache node modules - uses: actions/cache@v5 + uses: actions/cache@v6 env: cache-name: node with: @@ -847,7 +847,7 @@ jobs: runs-on: macos-latest timeout-minutes: 60 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: fetch-depth: 0 filter: tree:0 @@ -858,7 +858,7 @@ jobs: node-version-file: '.nvmrc' - name: Cache node modules - uses: actions/cache@v5 + uses: actions/cache@v6 env: cache-name: node with: @@ -949,7 +949,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: ref: ${{ github.event.inputs.ref || github.ref }} fetch-depth: 0 @@ -957,7 +957,7 @@ jobs: with: node-version-file: '.nvmrc' - name: Cache node modules - uses: actions/cache@v5 + uses: actions/cache@v6 env: cache-name: cache-node-platform with: diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml index 617a7671c2..7321d99ac0 100644 --- a/.github/workflows/publish-npm.yml +++ b/.github/workflows/publish-npm.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: ref: ${{ github.event.inputs.ref }} fetch-depth: 0 @@ -23,7 +23,7 @@ jobs: with: node-version-file: '.nvmrc' - name: Cache node modules - uses: actions/cache@v5 + uses: actions/cache@v6 env: cache-name: cache-node-platform with: diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000000..6f1b1e345e --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,27 @@ +# Security Policy + +## Reporting a Vulnerability + +We take security seriously. If you discover a security vulnerability in the +Huly Platform, please report it privately — **do not open a public issue.** + +Use GitHub's private vulnerability reporting: + +1. Go to the [Security tab](https://github.com/hcengineering/platform/security) of this repository. +2. Click **"Report a vulnerability"**. +3. Fill in the advisory with as much detail as possible (affected component, + reproduction steps, impact, suggested fix if any). + +We'll acknowledge your report, investigate, and coordinate a fix and +disclosure timeline with you before any public disclosure. + +## Supported Versions + +Security fixes are applied to the latest release on the `main`/`develop` +branch. Older versions are not guaranteed to receive patches. + +## Scope + +This policy covers the code in this repository. Vulnerabilities in +third-party dependencies should be reported upstream, though we're happy to +hear about them too if they affect the Platform directly. diff --git a/desktop/src/ui/findInPageBar.ts b/desktop/src/ui/findInPageBar.ts index b25d33358f..f2b592e9d3 100644 --- a/desktop/src/ui/findInPageBar.ts +++ b/desktop/src/ui/findInPageBar.ts @@ -358,6 +358,7 @@ const shadowStyles = ` font-variant-numeric: tabular-nums; text-align: right; color: rgba(0, 0, 0, 0.52); + -webkit-user-select: none; user-select: none; flex-shrink: 0; } diff --git a/desktop/src/ui/index.ejs b/desktop/src/ui/index.ejs index 9c5c3a23da..6a67df792d 100644 --- a/desktop/src/ui/index.ejs +++ b/desktop/src/ui/index.ejs @@ -96,6 +96,7 @@ cursor: pointer; font-size: 13px; position: relative; + -webkit-user-select: none; user-select: none; align-items: normal; /* explicitly set this up to prevent values from above */ } @@ -151,6 +152,7 @@ text-align: left; cursor: pointer; font-size: 13px; + -webkit-user-select: none; user-select: none; } @@ -185,6 +187,7 @@ font-size: 13px; line-height: 32px; color: var(--text-primary); + -webkit-user-select: none; user-select: none; -webkit-app-region: drag; white-space: nowrap; @@ -208,6 +211,7 @@ display: flex; align-items: center; justify-content: center; + -webkit-user-select: none; user-select: none; } diff --git a/dev/tool/src/index.ts b/dev/tool/src/index.ts index 2636f0262d..79b18518df 100644 --- a/dev/tool/src/index.ts +++ b/dev/tool/src/index.ts @@ -51,7 +51,7 @@ import { createStorageBackupStorage, restore } from '@hcengineering/server-backup' -import serverClientPlugin, { getAccountClient, getTransactorEndpoint } from '@hcengineering/server-client' +import serverClientPlugin, { BlobClient, getAccountClient, getTransactorEndpoint } from '@hcengineering/server-client' import { createBackupPipeline, createEmptyBroadcastOps, @@ -75,6 +75,7 @@ import { RatingCalculator, ratingEvents, type QueueRatingMessage } from '@hcengi import core, { AccountRole, + generateId, isArchivingMode, isDeletingMode, MeasureMetricsContext, @@ -84,11 +85,14 @@ import core, { systemAccountUuid, TxOperations, type AccountUuid, + type Class, + type Client, type Data, type Doc, type PersonId, type PersonUuid, type Ref, + type Space, type Tx, type Version, type WorkspaceDataId, @@ -1128,6 +1132,173 @@ export function devTool ( await storageAdapter.close() }) + program + .command('validate-workspace ') + .description('Validate a (restored) workspace: connect as system, check model, data counts and blob download') + .option('--blobs ', 'Number of sample blobs to download-check (0 to skip)', '5') + .option('--write', 'Run a write smoke-test (creates and removes a temporary space; use on clones only)', false) + .action(async (workspace: string, cmd: { blobs: string, write: boolean }) => { + await withAccountDatabase(async (db) => { + const ws = await getWorkspace(db, workspace) + if (ws === null) { + throw new Error(`workspace ${workspace} not found`) + } + const wsIds = { uuid: ws.uuid, dataId: ws.dataId, url: ws.url ?? '' } + + const failures: string[] = [] + const pass = (m: string): void => { + console.log(` ✓ ${m}`) + } + const fail = (m: string): void => { + console.error(` ✗ ${m}`) + failures.push(m) + } + const info = (m: string): void => { + console.log(` • ${m}`) + } + const emsg = (err: any): string => (err instanceof Error ? err.message : String(err)) + + console.log(`Validating workspace ${ws.url ?? ws.uuid} (${ws.uuid})`) + + const workspaceStorage: StorageAdapter = buildStorageFromConfig(storageConfigFromEnv()) + let connection: Client | undefined + try { + // 1. Connectivity: resolve transactor endpoint and connect as system user + let endpoint: string + try { + endpoint = await getWorkspaceTransactorEndpoint(ws.uuid) + pass('resolved transactor endpoint') + } catch (err: any) { + fail(`cannot resolve transactor endpoint: ${emsg(err)}`) + process.exitCode = 1 + return + } + + try { + connection = await connect(endpoint, ws.uuid, undefined, { service: 'tool', admin: 'true' }) + pass('connected to transactor as system user') + } catch (err: any) { + fail(`connect failed: ${emsg(err)}`) + process.exitCode = 1 + return + } + + const ops = new TxOperations(connection, core.account.System) + + // 2. Model loads and hierarchy is populated + try { + const classes = ops.getHierarchy().getDescendants(core.class.Doc).length + if (classes > 0) { + pass(`model loaded: ${classes} classes in hierarchy`) + } else { + fail('model loaded but hierarchy is empty') + } + } catch (err: any) { + fail(`model load failed: ${emsg(err)}`) + } + + // 3. Read path: count key domains + const countOf = async (label: string, _class: Ref>): Promise => { + try { + const r = await ops.findAll(_class, {}, { limit: 1, total: true }) + info(`${label}: ${r.total}`) + return r.total + } catch (err: any) { + fail(`findAll ${label} failed: ${emsg(err)}`) + return -1 + } + } + await countOf('transactions (Tx)', core.class.Tx) + const blobTotal = await countOf('blobs (Blob)', core.class.Blob) + await countOf('spaces (Space)', core.class.Space) + + // 4. Blob end-to-end: download a sample and verify bytes match metadata + const nBlobs = parseInt(cmd.blobs) + if (!Number.isNaN(nBlobs) && nBlobs > 0) { + if (blobTotal === 0) { + fail('no blobs found in workspace — attachments/files would be missing') + } else if (blobTotal > 0) { + try { + const blobClient = new BlobClient(workspaceStorage, wsIds) + const sample = await ops.findAll(core.class.Blob, {}, { limit: nBlobs }) + let okCount = 0 + for (const blob of sample) { + try { + let bytes = 0 + await blobClient.writeTo(toolCtx, blob._id, blob.size, { + write: (buffer: Buffer, cb: (err?: any) => void) => { + bytes += buffer.length + cb() + }, + end: (cb: () => void) => { + cb() + } + }) + if (bytes === blob.size) { + okCount++ + } else { + fail(`blob ${blob._id}: downloaded ${bytes} of ${blob.size} bytes`) + } + } catch (err: any) { + fail(`blob ${blob._id} download failed: ${emsg(err)}`) + } + } + if (okCount === sample.length && okCount > 0) { + pass(`downloaded ${okCount}/${sample.length} sample blobs, sizes match metadata`) + } + } catch (err: any) { + fail(`blob download check failed: ${emsg(err)}`) + } + } + } + + // 5. Optional write smoke-test (mutating — clones only) + if (cmd.write) { + const spaceId = generateId() + try { + await ops.createDoc( + core.class.Space, + core.space.Space, + { + name: `__validate_${spaceId}`, + description: 'temporary workspace validation space', + private: true, + archived: false, + members: [] + }, + spaceId + ) + const readBack = await ops.findOne(core.class.Space, { _id: spaceId }) + if (readBack !== undefined) { + pass('write smoke-test: created and read back a temporary space') + } else { + fail('write smoke-test: created space not found on read-back') + } + } catch (err: any) { + fail(`write smoke-test (create) failed: ${emsg(err)}`) + } finally { + try { + await ops.removeDoc(core.class.Space, core.space.Space, spaceId) + pass('write smoke-test: removed temporary space') + } catch (err: any) { + fail(`write smoke-test (cleanup) failed, temporary space ${spaceId} may remain: ${emsg(err)}`) + } + } + } + } finally { + await connection?.close() + await workspaceStorage.close() + } + + if (failures.length > 0) { + console.error(`\nVALIDATION FAILED: ${failures.length} issue(s)`) + process.exitCode = 1 + } else { + console.log('\nVALIDATION PASSED') + } + }) + }) + program .command('backup-restore [date]') .option('-m, --merge', 'Enable merge of remote and backup content.', false) diff --git a/models/hr/src/index.ts b/models/hr/src/index.ts index 9dc5446f38..dd6841ec1f 100644 --- a/models/hr/src/index.ts +++ b/models/hr/src/index.ts @@ -404,14 +404,29 @@ export function createModel (builder: Builder): void { attachTo: hr.mixin.Staff, descriptor: view.viewlet.Table, config: [ - '', + { key: '', props: { showStatus: true } }, + 'department', { key: '$lookup.channels', label: contact.string.ContactInfo, sortingKey: ['$lookup.channels.lastMessage', 'channels'] }, 'modifiedOn' - ] + ], + viewOptions: { + groupBy: [], + orderBy: [], + other: [ + { + key: 'hideInactive', + type: 'toggle', + defaultValue: true, + actionTarget: 'query', + action: contact.function.HideInactive, + label: contact.string.HideInactive + } + ] + } }, hr.viewlet.TableMember ) diff --git a/models/hr/src/migration.ts b/models/hr/src/migration.ts index a0fa8fe9e8..dc6d360aa2 100644 --- a/models/hr/src/migration.ts +++ b/models/hr/src/migration.ts @@ -22,7 +22,8 @@ import { DOMAIN_MODEL_TX, type TxCUD } from '@hcengineering/core' -import { type Department } from '@hcengineering/hr' +import contact, { type Employee, type Person } from '@hcengineering/contact' +import { type Department, type Staff } from '@hcengineering/hr' import { migrateSpace, tryMigrate, @@ -32,6 +33,7 @@ import { type MigrationUpgradeClient } from '@hcengineering/model' import core, { DOMAIN_SPACE, getAccountsFromTxes } from '@hcengineering/model-core' +import { DOMAIN_CONTACT } from '@hcengineering/model-contact' import hr, { DOMAIN_HR, hrId } from './index' @@ -103,6 +105,52 @@ async function migrateDepartmentMembersToEmployee (client: MigrationClient): Pro } } +async function rebuildDepartmentMembersFromStaff (client: MigrationClient): Promise { + const departments = await client.find(DOMAIN_HR, { _class: hr.class.Department }) + const departmentById = new Map(departments.map((department) => [department._id, department])) + const membersByDepartment = new Map, Set>>( + departments.map((department) => [department._id, new Set>()]) + ) + + const getHierarchy = (department: Ref): Ref[] => { + const result: Ref[] = [] + let current: Ref | undefined = department + + while (current !== undefined) { + if (result.includes(current)) { + break + } + + const currentDepartment = departmentById.get(current) + if (currentDepartment === undefined) { + break + } + + result.push(currentDepartment._id) + current = currentDepartment.parent ?? (currentDepartment._id !== hr.ids.Head ? hr.ids.Head : undefined) + } + + return result + } + + const persons = await client.find(DOMAIN_CONTACT, { _class: contact.class.Person }) + for (const person of persons) { + const staff = client.hierarchy.asIf(person, hr.mixin.Staff) + if (staff?.department === undefined) { + continue + } + + for (const department of getHierarchy(staff.department)) { + membersByDepartment.get(department)?.add(person._id as Ref) + } + } + + for (const department of departments) { + const members = Array.from(membersByDepartment.get(department._id) ?? []) + await client.update(DOMAIN_HR, { _id: department._id }, { members }) + } +} + export const hrOperation: MigrateOperation = { async migrate (client: MigrationClient, mode): Promise { await tryMigrate(mode, client, hrId, [ @@ -120,6 +168,14 @@ export const hrOperation: MigrateOperation = { { state: 'migrateDepartmentMembersToEmployee', func: migrateDepartmentMembersToEmployee + }, + { + state: 'rebuildDepartmentMembersFromStaff', + func: rebuildDepartmentMembersFromStaff + }, + { + state: 'rebuildDepartmentMembersFromStaffIncludingInactive', + func: rebuildDepartmentMembersFromStaff } ]) }, diff --git a/models/server-hr/src/index.ts b/models/server-hr/src/index.ts index 5370433f12..d936356bdf 100644 --- a/models/server-hr/src/index.ts +++ b/models/server-hr/src/index.ts @@ -41,6 +41,14 @@ export function createModel (builder: Builder): void { } }) + builder.createDoc(serverCore.class.Trigger, core.space.Model, { + trigger: serverHr.trigger.OnDepartmentUpdate, + txMatch: { + objectClass: hr.class.Department, + _class: core.class.TxUpdateDoc + } + }) + builder.createDoc(serverCore.class.Trigger, core.space.Model, { trigger: serverHr.trigger.OnRequestCreate, txMatch: { diff --git a/packages/presentation/src/components/MessageBox.svelte b/packages/presentation/src/components/MessageBox.svelte index bfec2adb57..b9518517ca 100644 --- a/packages/presentation/src/components/MessageBox.svelte +++ b/packages/presentation/src/components/MessageBox.svelte @@ -107,6 +107,7 @@ max-width: 40rem; background: var(--theme-popup-color); border-radius: 0.5rem; + -webkit-user-select: none; user-select: none; box-shadow: var(--theme-popup-shadow); diff --git a/packages/theme/styles/_layouts.scss b/packages/theme/styles/_layouts.scss index 90f3929ae4..3a0f05bd07 100644 --- a/packages/theme/styles/_layouts.scss +++ b/packages/theme/styles/_layouts.scss @@ -61,6 +61,7 @@ button { border: 1px solid transparent; outline: none; cursor: pointer; + -webkit-user-select: none; user-select: none; } input { @@ -124,6 +125,7 @@ li { } p { + -webkit-user-select: inherit; user-select:inherit; a { @@ -345,6 +347,7 @@ input.search { color: var(--theme-content-color); overflow: hidden; + -webkit-user-select: none; user-select: none; &:not(.nowrap) { @@ -886,6 +889,7 @@ a.no-line { font-size: 1rem; color: var(--theme-caption-color); user-select: none; + -webkit-user-select: none; text-rendering: optimizeLegibility; } .trans-title { @@ -894,6 +898,7 @@ a.no-line { font-size: .75rem; color: var(--theme-dark-color); user-select: none; + -webkit-user-select: none; text-rendering: optimizeLegibility; } .text-16px { font-size: 16px; } @@ -941,6 +946,7 @@ a.no-line { overflow: hidden; white-space: pre-wrap; width: max-content; + -webkit-user-select: none; user-select: none; min-width: auto; } diff --git a/packages/theme/styles/_text-editor.scss b/packages/theme/styles/_text-editor.scss index b88609646a..7657ddb2f0 100644 --- a/packages/theme/styles/_text-editor.scss +++ b/packages/theme/styles/_text-editor.scss @@ -281,6 +281,7 @@ @supports (selector(:has(.text-editor-image-container))) { p:has(> .text-editor-image-container) { + -webkit-user-select: none; user-select: none; } } @@ -429,6 +430,7 @@ color: transparent; background-image: url('data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20256%20256%22%20id%3D%22note%22%3E%3Crect%20width%3D%22256%22%20height%3D%22256%22%20fill%3D%22none%22%3E%3C%2Frect%3E%3Cline%20x1%3D%2296%22%20x2%3D%22160%22%20y1%3D%2296%22%20y2%3D%2296%22%20fill%3D%22none%22%20stroke%3D%22%23313131%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%228%22%3E%3C%2Fline%3E%3Cline%20x1%3D%2296%22%20x2%3D%22160%22%20y1%3D%22128%22%20y2%3D%22128%22%20fill%3D%22none%22%20stroke%3D%22%23313131%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%228%22%3E%3C%2Fline%3E%3Cline%20x1%3D%2296%22%20x2%3D%22128%22%20y1%3D%22160%22%20y2%3D%22160%22%20fill%3D%22none%22%20stroke%3D%22%23313131%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%228%22%3E%3C%2Fline%3E%3Cpath%20fill%3D%22none%22%20stroke%3D%22%23313131%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%228%22%20d%3D%22M156.68629%2C216H48a8%2C8%2C0%2C0%2C1-8-8V48a8%2C8%2C0%2C0%2C1%2C8-8H208a8%2C8%2C0%2C0%2C1%2C8%2C8V156.68629a8%2C8%2C0%2C0%2C1-2.34315%2C5.65686l-51.3137%2C51.3137A8%2C8%2C0%2C0%2C1%2C156.68629%2C216Z%22%3E%3C%2Fpath%3E%3Cpolyline%20fill%3D%22none%22%20stroke%3D%22%23313131%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%228%22%20points%3D%22215.277%20159.992%20160%20159.992%20160%20215.272%22%3E%3C%2Fpolyline%3E%3C%2Fsvg%3E'); background-repeat: no-repeat; + -webkit-user-select: none; user-select: none; cursor: pointer; diff --git a/packages/theme/styles/components.scss b/packages/theme/styles/components.scss index 31f2e487b3..abfa53a5e7 100644 --- a/packages/theme/styles/components.scss +++ b/packages/theme/styles/components.scss @@ -204,6 +204,7 @@ & > * { pointer-events: none; + -webkit-user-select: none; user-select: none; -webkit-user-drag: none; } @@ -1220,6 +1221,7 @@ text-overflow: ellipsis; white-space: nowrap; user-select: none; + -webkit-user-select: none; } .ac-header__counter { flex-shrink: 0; @@ -1241,6 +1243,7 @@ -webkit-line-clamp: 2; line-clamp: 2; user-select: none; + -webkit-user-select: none; } .ac-header__icon-button { @@ -1319,6 +1322,7 @@ color: var(--dark-color); cursor: pointer; user-select: none; + -webkit-user-select: none; &.selected { border-top: .125rem solid transparent; @@ -1764,6 +1768,7 @@ color: var(--theme-dark-color); box-shadow: inset 0 -1px 0 0 var(--theme-table-border-color); user-select: none; + -webkit-user-select: none; // z-index: 5; &.sortable { cursor: pointer; } @@ -2015,6 +2020,7 @@ text-overflow: ellipsis; overflow: hidden; user-select: none; + -webkit-user-select: none; min-width: 0; font-size: .75rem; @@ -2101,6 +2107,7 @@ text-overflow: ellipsis; overflow: hidden; user-select: none; + -webkit-user-select: none; min-width: 0; &.selected { color: var(--caption-color); } diff --git a/packages/theme/styles/dialogs.scss b/packages/theme/styles/dialogs.scss index 44fdd23542..8f0e76b40e 100644 --- a/packages/theme/styles/dialogs.scss +++ b/packages/theme/styles/dialogs.scss @@ -62,6 +62,7 @@ white-space: nowrap; text-overflow: ellipsis; user-select: none; + -webkit-user-select: none; min-width: 0; line-height: 150%; color: var(--theme-caption-color); diff --git a/packages/theme/styles/global.scss b/packages/theme/styles/global.scss index ba5c188e33..351958ba39 100644 --- a/packages/theme/styles/global.scss +++ b/packages/theme/styles/global.scss @@ -156,6 +156,7 @@ body { color: var(--content-color); background-color: var(--theme-bg-color); user-select: none; + -webkit-user-select: none; --status-bar-height: 36px; --status-bar-normal-height: 36px; diff --git a/packages/theme/styles/popups.scss b/packages/theme/styles/popups.scss index 9527798730..7f94e7c7fb 100644 --- a/packages/theme/styles/popups.scss +++ b/packages/theme/styles/popups.scss @@ -363,6 +363,7 @@ min-height: 0; min-width: 0; user-select: none; + -webkit-user-select: none; &:not(.embedded) { max-width: 30rem; diff --git a/packages/theme/styles/prose.scss b/packages/theme/styles/prose.scss index 235628047c..68126e92c3 100644 --- a/packages/theme/styles/prose.scss +++ b/packages/theme/styles/prose.scss @@ -542,6 +542,7 @@ pre.proseCodeBlock>pre.proseCode { } header { + -webkit-user-select: none; user-select: none; position: absolute; right: 0.5rem; diff --git a/packages/ui/src/components/MiniToggle.svelte b/packages/ui/src/components/MiniToggle.svelte index 7c6bc112f3..8cace2e587 100644 --- a/packages/ui/src/components/MiniToggle.svelte +++ b/packages/ui/src/components/MiniToggle.svelte @@ -55,6 +55,7 @@ vertical-align: middle; font-size: inherit; user-select: none; + -webkit-user-select: none; .chBox { position: absolute; width: 1px; diff --git a/packages/ui/src/components/ModernCheckbox.svelte b/packages/ui/src/components/ModernCheckbox.svelte index 0f448f5ca1..0807be28ec 100644 --- a/packages/ui/src/components/ModernCheckbox.svelte +++ b/packages/ui/src/components/ModernCheckbox.svelte @@ -43,6 +43,7 @@ .checkbox-label { color: var(--global-primary-TextColor); user-select: none; + -webkit-user-select: none; } .checkbox { overflow: hidden; diff --git a/packages/ui/src/components/ModernEditbox.svelte b/packages/ui/src/components/ModernEditbox.svelte index d5d796f2e1..e4dca43d6a 100644 --- a/packages/ui/src/components/ModernEditbox.svelte +++ b/packages/ui/src/components/ModernEditbox.svelte @@ -300,6 +300,7 @@ font-size 0.2s; pointer-events: none; user-select: none; + -webkit-user-select: none; } input:focus + .label, input:not(:placeholder-shown) + .label { diff --git a/packages/ui/src/components/ModernRadioButton.svelte b/packages/ui/src/components/ModernRadioButton.svelte index 8d3dd3cc4b..7b8f54955d 100644 --- a/packages/ui/src/components/ModernRadioButton.svelte +++ b/packages/ui/src/components/ModernRadioButton.svelte @@ -43,6 +43,7 @@ .radioButton-label { color: var(--global-primary-TextColor); user-select: none; + -webkit-user-select: none; } .radioButton { overflow: hidden; diff --git a/packages/ui/src/components/ModernToggle.svelte b/packages/ui/src/components/ModernToggle.svelte index e098078439..a56a619034 100644 --- a/packages/ui/src/components/ModernToggle.svelte +++ b/packages/ui/src/components/ModernToggle.svelte @@ -75,6 +75,7 @@ .toggle-label { color: var(--global-primary-TextColor); user-select: none; + -webkit-user-select: none; } .toggle { overflow: hidden; diff --git a/packages/ui/src/components/PopupMenu.svelte b/packages/ui/src/components/PopupMenu.svelte index 69be49d4da..781e0830ea 100644 --- a/packages/ui/src/components/PopupMenu.svelte +++ b/packages/ui/src/components/PopupMenu.svelte @@ -112,6 +112,7 @@ border-radius: 0.75rem; box-shadow: var(--popup-shadow); user-select: none; + -webkit-user-select: none; z-index: 10; } .scrolling { diff --git a/packages/ui/src/components/Scroller.svelte b/packages/ui/src/components/Scroller.svelte index 78ac4a04af..10f0ecca64 100644 --- a/packages/ui/src/components/Scroller.svelte +++ b/packages/ui/src/components/Scroller.svelte @@ -587,6 +587,7 @@ class:sticked={stickedScrollBars} class:thin={thinScrollBars} class:shrink + style:-webkit-user-select={isScrolling ? 'none' : 'inherit'} style:user-select={isScrolling ? 'none' : 'inherit'} style:--scroller-header-height={`${(fade.multipler?.top ?? 0) * fz + 2}px`} style:--scroller-footer-height={`${(fade.multipler?.bottom ?? 0) * fz + (stickedScrollBars ? 0 : 2)}px`} @@ -770,6 +771,7 @@ outline: none; box-shadow: 0 0 0.375rem rgba($color: #000000, $alpha: 0.1); user-select: none; + -webkit-user-select: none; -webkit-tap-highlight-color: transparent; tap-highlight-color: transparent; diff --git a/packages/ui/src/components/ShowMore.svelte b/packages/ui/src/components/ShowMore.svelte index d5831488a9..4e58da97d1 100644 --- a/packages/ui/src/components/ShowMore.svelte +++ b/packages/ui/src/components/ShowMore.svelte @@ -87,6 +87,7 @@ border-radius: 2.5rem; // z-index: 1; user-select: none; + -webkit-user-select: none; cursor: pointer; &.outter { diff --git a/packages/ui/src/components/Status.svelte b/packages/ui/src/components/Status.svelte index 03226be4c3..7b75adbb3e 100644 --- a/packages/ui/src/components/Status.svelte +++ b/packages/ui/src/components/Status.svelte @@ -21,6 +21,7 @@ diff --git a/plugins/calendar-resources/src/components/UpdateRecInstancePopup.svelte b/plugins/calendar-resources/src/components/UpdateRecInstancePopup.svelte index 371137654d..b8b8f37d3d 100644 --- a/plugins/calendar-resources/src/components/UpdateRecInstancePopup.svelte +++ b/plugins/calendar-resources/src/components/UpdateRecInstancePopup.svelte @@ -93,6 +93,7 @@ max-width: 40rem; background: var(--theme-popup-color); border-radius: 0.5rem; + -webkit-user-select: none; user-select: none; box-shadow: var(--theme-popup-shadow); diff --git a/plugins/card-resources/src/components/CardSection.svelte b/plugins/card-resources/src/components/CardSection.svelte index d9fb0e2ddf..9a9709185e 100644 --- a/plugins/card-resources/src/components/CardSection.svelte +++ b/plugins/card-resources/src/components/CardSection.svelte @@ -29,6 +29,7 @@ display: flex; flex-direction: column; cursor: pointer; + -webkit-user-select: none; user-select: none; border-radius: 0.5rem; width: 100%; diff --git a/plugins/controlled-documents-resources/src/components/document/EditDocReasonAndImpact.svelte b/plugins/controlled-documents-resources/src/components/document/EditDocReasonAndImpact.svelte index 6a2fbfb0a0..eb0a593698 100644 --- a/plugins/controlled-documents-resources/src/components/document/EditDocReasonAndImpact.svelte +++ b/plugins/controlled-documents-resources/src/components/document/EditDocReasonAndImpact.svelte @@ -168,6 +168,7 @@ font-weight: 500; font-size: var(--body-font-size); color: var(--theme-caption-color); + -webkit-user-select: none; user-select: none; } diff --git a/plugins/controlled-documents-resources/src/components/print/DocumentPrintTitlePage.svelte b/plugins/controlled-documents-resources/src/components/print/DocumentPrintTitlePage.svelte index f9f7654b12..a521a0f96e 100644 --- a/plugins/controlled-documents-resources/src/components/print/DocumentPrintTitlePage.svelte +++ b/plugins/controlled-documents-resources/src/components/print/DocumentPrintTitlePage.svelte @@ -125,6 +125,7 @@ font-weight: 500; font-size: $font-size; color: var(--theme-caption-color); + -webkit-user-select: none; user-select: none; width: 10rem; flex: 0 0 auto; diff --git a/plugins/devmodel-resources/src/components/ModelView.svelte b/plugins/devmodel-resources/src/components/ModelView.svelte index 2e9e755a71..f2fe357f15 100644 --- a/plugins/devmodel-resources/src/components/ModelView.svelte +++ b/plugins/devmodel-resources/src/components/ModelView.svelte @@ -78,6 +78,7 @@ font-size: 0.75rem; color: var(--dark-color); box-shadow: inset 0 -1px 0 0 var(--trans-content-10); + -webkit-user-select: none; user-select: none; } diff --git a/plugins/document-resources/src/components/sidebar/History.svelte b/plugins/document-resources/src/components/sidebar/History.svelte index 83b94b02f9..fdaeaa8cab 100644 --- a/plugins/document-resources/src/components/sidebar/History.svelte +++ b/plugins/document-resources/src/components/sidebar/History.svelte @@ -95,6 +95,7 @@ flex-grow: 1; font-weight: 500; color: var(--caption-color); + -webkit-user-select: none; user-select: none; } } diff --git a/plugins/document-resources/src/components/sidebar/References.svelte b/plugins/document-resources/src/components/sidebar/References.svelte index 00942f92db..ccf4c0dcec 100644 --- a/plugins/document-resources/src/components/sidebar/References.svelte +++ b/plugins/document-resources/src/components/sidebar/References.svelte @@ -79,6 +79,7 @@ flex-grow: 1; font-weight: 500; color: var(--caption-color); + -webkit-user-select: none; user-select: none; } } diff --git a/plugins/emoji-resources/src/components/EmojiPopup.svelte b/plugins/emoji-resources/src/components/EmojiPopup.svelte index 4f9799080e..2b9371f962 100644 --- a/plugins/emoji-resources/src/components/EmojiPopup.svelte +++ b/plugins/emoji-resources/src/components/EmojiPopup.svelte @@ -330,6 +330,7 @@ min-width: 0; min-height: 29rem; max-height: 29rem; + -webkit-user-select: none; user-select: none; :global(.mobile-theme) & { diff --git a/plugins/hr-resources/src/components/DepartmentCard.svelte b/plugins/hr-resources/src/components/DepartmentCard.svelte index a23c2c6d3f..ddcddf6e9e 100644 --- a/plugins/hr-resources/src/components/DepartmentCard.svelte +++ b/plugins/hr-resources/src/components/DepartmentCard.svelte @@ -77,7 +77,9 @@ $: dragPersonId = dragPerson?._id - $: values = allEmployees.filter((it) => it.department === value._id && it._id !== dragPersonId) + $: members = new Set>(value.members as Ref[]) + $: activeMembers = allEmployees.filter((it) => it.active && members.has(it._id)) + $: values = activeMembers.filter((it) => it._id !== dragPersonId) $: dragging = value._id === dragOver?._id && dragPersonId !== undefined @@ -120,7 +122,7 @@
{value.name}
-