mirror of
https://github.com/immich-app/immich.git
synced 2025-02-11 01:18:24 -05:00
Merge branch 'feat/nullable-dates' of https://github.com/immich-app/immich into feat/inline-offline-check
This commit is contained in:
commit
7f73f2e712
13 changed files with 108 additions and 74 deletions
1
.github/PULL_REQUEST_TEMPLATE/config.yml
vendored
1
.github/PULL_REQUEST_TEMPLATE/config.yml
vendored
|
@ -1,2 +1 @@
|
||||||
blank_issues_enabled: false
|
|
||||||
blank_pull_request_template_enabled: false
|
blank_pull_request_template_enabled: false
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
## Description
|
|
||||||
<!--- Describe your changes in detail -->
|
|
||||||
<!--- Why is this change required? What problem does it solve? -->
|
|
||||||
<!--- If it fixes an open issue, please link to the issue here. -->
|
|
||||||
|
|
||||||
Fixes # (issue)
|
|
||||||
|
|
||||||
|
|
||||||
## How Has This Been Tested?
|
|
||||||
|
|
||||||
<!-- Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration -->
|
|
||||||
|
|
||||||
- [ ] Test A
|
|
||||||
- [ ] Test B
|
|
||||||
|
|
||||||
## Screenshots (if appropriate):
|
|
||||||
|
|
||||||
|
|
||||||
## Checklist:
|
|
||||||
|
|
||||||
- [ ] I have performed a self-review of my own code
|
|
||||||
- [ ] I have made corresponding changes to the documentation if applicable
|
|
36
.github/pull_request_template.md
vendored
Normal file
36
.github/pull_request_template.md
vendored
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
## Description
|
||||||
|
|
||||||
|
<!--- Describe your changes in detail -->
|
||||||
|
<!--- Why is this change required? What problem does it solve? -->
|
||||||
|
<!--- If it fixes an open issue, please link to the issue here. -->
|
||||||
|
|
||||||
|
Fixes # (issue)
|
||||||
|
|
||||||
|
## How Has This Been Tested?
|
||||||
|
|
||||||
|
<!-- Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration -->
|
||||||
|
|
||||||
|
- [ ] Test A
|
||||||
|
- [ ] Test B
|
||||||
|
|
||||||
|
<details><summary><h2>Screenshots (if appropriate)</h2></summary>
|
||||||
|
|
||||||
|
<!-- Images go below this line. -->
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<!-- API endpoint changes (if relevant)
|
||||||
|
## API Changes
|
||||||
|
The `/api/something` endpoint is now `/api/something-else`
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Checklist:
|
||||||
|
|
||||||
|
- [ ] I have performed a self-review of my own code
|
||||||
|
- [ ] I have made corresponding changes to the documentation if applicable
|
||||||
|
- [ ] I have no unrelated changes in the PR.
|
||||||
|
- [ ] I have confirmed that any new dependencies are strictly necessary.
|
||||||
|
- [ ] I have written tests for new code (if applicable)
|
||||||
|
- [ ] I have followed naming conventions/patterns in the surrounding code
|
||||||
|
- [ ] All code in `src/services` uses repositories implementations for database calls, filesystem operations, etc.
|
||||||
|
- [ ] All code in `src/repositories/` is pretty basic/simple and does not have any immich specific logic (that belongs in `src/services`)
|
|
@ -72,7 +72,7 @@ alt="Select Plugins > Compose.Manager > Add New Stack > Label it Immich"
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
5. Click "**Save Changes**", you will be promoted to edit stack UI labels, just leave this blank and click "**Ok**"
|
5. Click "**Save Changes**", you will be prompted to edit stack UI labels, just leave this blank and click "**Ok**"
|
||||||
6. Select the cog ⚙️ next to Immich, click "**Edit Stack**", then click "**Env File**"
|
6. Select the cog ⚙️ next to Immich, click "**Edit Stack**", then click "**Env File**"
|
||||||
7. Paste the entire contents of the [Immich example.env](https://github.com/immich-app/immich/releases/latest/download/example.env) file into the Unraid editor, then **before saving** edit the following:
|
7. Paste the entire contents of the [Immich example.env](https://github.com/immich-app/immich/releases/latest/download/example.env) file into the Unraid editor, then **before saving** edit the following:
|
||||||
|
|
||||||
|
|
|
@ -113,22 +113,12 @@ const hexOrBufferToBase64 = (encoded: string | Buffer) => {
|
||||||
export function mapAsset(entity: AssetEntity, options: AssetMapOptions = {}): AssetResponseDto {
|
export function mapAsset(entity: AssetEntity, options: AssetMapOptions = {}): AssetResponseDto {
|
||||||
const { stripMetadata = false, withStack = false } = options;
|
const { stripMetadata = false, withStack = false } = options;
|
||||||
|
|
||||||
if (entity.localDateTime === null) {
|
|
||||||
throw new Error(`Asset ${entity.id} has no localDateTime`);
|
|
||||||
}
|
|
||||||
if (entity.fileCreatedAt === null) {
|
|
||||||
throw new Error(`Asset ${entity.id} has no fileCreatedAt`);
|
|
||||||
}
|
|
||||||
if (entity.fileModifiedAt === null) {
|
|
||||||
throw new Error(`Asset ${entity.id} has no fileModifiedAt`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stripMetadata) {
|
if (stripMetadata) {
|
||||||
const sanitizedAssetResponse: SanitizedAssetResponseDto = {
|
const sanitizedAssetResponse: SanitizedAssetResponseDto = {
|
||||||
id: entity.id,
|
id: entity.id,
|
||||||
type: entity.type,
|
type: entity.type,
|
||||||
originalMimeType: mimeTypes.lookup(entity.originalFileName),
|
originalMimeType: mimeTypes.lookup(entity.originalFileName),
|
||||||
thumbhash: entity.thumbhash?.toString('base64') ?? null,
|
thumbhash: entity.thumbhash ? hexOrBufferToBase64(entity.thumbhash) : null,
|
||||||
localDateTime: entity.localDateTime,
|
localDateTime: entity.localDateTime,
|
||||||
duration: entity.duration ?? '0:00:00.00000',
|
duration: entity.duration ?? '0:00:00.00000',
|
||||||
livePhotoVideoId: entity.livePhotoVideoId,
|
livePhotoVideoId: entity.livePhotoVideoId,
|
||||||
|
|
|
@ -101,13 +101,13 @@ export class AssetEntity {
|
||||||
|
|
||||||
@Index('idx_asset_file_created_at')
|
@Index('idx_asset_file_created_at')
|
||||||
@Column({ type: 'timestamptz', nullable: true, default: null })
|
@Column({ type: 'timestamptz', nullable: true, default: null })
|
||||||
fileCreatedAt!: Date | null;
|
fileCreatedAt!: Date;
|
||||||
|
|
||||||
@Column({ type: 'timestamptz', nullable: true, default: null })
|
@Column({ type: 'timestamptz', nullable: true, default: null })
|
||||||
localDateTime!: Date | null;
|
localDateTime!: Date;
|
||||||
|
|
||||||
@Column({ type: 'timestamptz', nullable: true, default: null })
|
@Column({ type: 'timestamptz', nullable: true, default: null })
|
||||||
fileModifiedAt!: Date | null;
|
fileModifiedAt!: Date;
|
||||||
|
|
||||||
@Column({ type: 'boolean', default: false })
|
@Column({ type: 'boolean', default: false })
|
||||||
isFavorite!: boolean;
|
isFavorite!: boolean;
|
||||||
|
@ -180,6 +180,12 @@ export class AssetEntity {
|
||||||
duplicateId!: string | null;
|
duplicateId!: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type AssetEntityPlaceholder = AssetEntity & {
|
||||||
|
fileCreatedAt: Date | null;
|
||||||
|
fileModifiedAt: Date | null;
|
||||||
|
localDateTime: Date | null;
|
||||||
|
};
|
||||||
|
|
||||||
export function withExif<O>(qb: SelectQueryBuilder<DB, 'assets', O>) {
|
export function withExif<O>(qb: SelectQueryBuilder<DB, 'assets', O>) {
|
||||||
return qb.leftJoin('exif', 'assets.id', 'exif.assetId').select((eb) => eb.fn.toJson(eb.table('exif')).as('exifInfo'));
|
return qb.leftJoin('exif', 'assets.id', 'exif.assetId').select((eb) => eb.fn.toJson(eb.table('exif')).as('exifInfo'));
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,7 @@ with
|
||||||
and "assets"."deletedAt" is null
|
and "assets"."deletedAt" is null
|
||||||
and "assets"."fileCreatedAt" is not null
|
and "assets"."fileCreatedAt" is not null
|
||||||
and "assets"."fileModifiedAt" is not null
|
and "assets"."fileModifiedAt" is not null
|
||||||
|
and "assets"."localDateTime" is not null
|
||||||
order by
|
order by
|
||||||
(assets."localDateTime" at time zone 'UTC')::date desc
|
(assets."localDateTime" at time zone 'UTC')::date desc
|
||||||
limit
|
limit
|
||||||
|
@ -461,8 +462,6 @@ from
|
||||||
where
|
where
|
||||||
"assets"."ownerId" = any ($1::uuid[])
|
"assets"."ownerId" = any ($1::uuid[])
|
||||||
and "isVisible" = $2
|
and "isVisible" = $2
|
||||||
and "assets"."fileCreatedAt" is not null
|
|
||||||
and "assets"."fileModifiedAt" is not null
|
|
||||||
and "updatedAt" > $3
|
and "updatedAt" > $3
|
||||||
limit
|
limit
|
||||||
$4
|
$4
|
||||||
|
|
22
server/src/queries/map.repository.sql
Normal file
22
server/src/queries/map.repository.sql
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
-- NOTE: This file is auto generated by ./sql-generator
|
||||||
|
|
||||||
|
-- MapRepository.getMapMarkers
|
||||||
|
select
|
||||||
|
"id",
|
||||||
|
"exif"."latitude" as "lat",
|
||||||
|
"exif"."longitude" as "lon",
|
||||||
|
"exif"."city",
|
||||||
|
"exif"."state",
|
||||||
|
"exif"."country"
|
||||||
|
from
|
||||||
|
"assets"
|
||||||
|
inner join "exif" on "assets"."id" = "exif"."assetId"
|
||||||
|
and "exif"."latitude" is not null
|
||||||
|
and "exif"."longitude" is not null
|
||||||
|
left join "albums_assets_assets" on "assets"."id" = "albums_assets_assets"."assetsId"
|
||||||
|
where
|
||||||
|
"isVisible" = $1
|
||||||
|
and "deletedAt" is null
|
||||||
|
and "ownerId" in ($2)
|
||||||
|
order by
|
||||||
|
"fileCreatedAt" desc
|
|
@ -1,5 +1,5 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { Insertable, Kysely, UpdateResult, Updateable, sql } from 'kysely';
|
import { Insertable, Kysely, NotNull, UpdateResult, Updateable, sql } from 'kysely';
|
||||||
import { isEmpty, isUndefined, omitBy } from 'lodash';
|
import { isEmpty, isUndefined, omitBy } from 'lodash';
|
||||||
import { InjectKysely } from 'nestjs-kysely';
|
import { InjectKysely } from 'nestjs-kysely';
|
||||||
import { ASSET_FILE_CONFLICT_KEYS, EXIF_CONFLICT_KEYS, JOB_STATUS_CONFLICT_KEYS } from 'src/constants';
|
import { ASSET_FILE_CONFLICT_KEYS, EXIF_CONFLICT_KEYS, JOB_STATUS_CONFLICT_KEYS } from 'src/constants';
|
||||||
|
@ -7,6 +7,7 @@ import { AssetFiles, AssetJobStatus, Assets, DB, Exif } from 'src/db';
|
||||||
import { Chunked, ChunkedArray, DummyValue, GenerateSql } from 'src/decorators';
|
import { Chunked, ChunkedArray, DummyValue, GenerateSql } from 'src/decorators';
|
||||||
import {
|
import {
|
||||||
AssetEntity,
|
AssetEntity,
|
||||||
|
AssetEntityPlaceholder,
|
||||||
hasPeople,
|
hasPeople,
|
||||||
searchAssetBuilder,
|
searchAssetBuilder,
|
||||||
truncatedDate,
|
truncatedDate,
|
||||||
|
@ -80,8 +81,12 @@ export class AssetRepository implements IAssetRepository {
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
create(asset: Insertable<Assets>): Promise<AssetEntity> {
|
create(asset: Insertable<Assets>): Promise<AssetEntityPlaceholder> {
|
||||||
return this.db.insertInto('assets').values(asset).returningAll().executeTakeFirst() as any as Promise<AssetEntity>;
|
return this.db
|
||||||
|
.insertInto('assets')
|
||||||
|
.values(asset)
|
||||||
|
.returningAll()
|
||||||
|
.executeTakeFirst() as any as Promise<AssetEntityPlaceholder>;
|
||||||
}
|
}
|
||||||
|
|
||||||
createAll(assets: Insertable<Assets>[]): Promise<AssetEntity[]> {
|
createAll(assets: Insertable<Assets>[]): Promise<AssetEntity[]> {
|
||||||
|
@ -128,6 +133,7 @@ export class AssetRepository implements IAssetRepository {
|
||||||
.where('assets.deletedAt', 'is', null)
|
.where('assets.deletedAt', 'is', null)
|
||||||
.where('assets.fileCreatedAt', 'is not', null)
|
.where('assets.fileCreatedAt', 'is not', null)
|
||||||
.where('assets.fileModifiedAt', 'is not', null)
|
.where('assets.fileModifiedAt', 'is not', null)
|
||||||
|
.where('assets.localDateTime', 'is not', null)
|
||||||
.orderBy(sql`(assets."localDateTime" at time zone 'UTC')::date`, 'desc')
|
.orderBy(sql`(assets."localDateTime" at time zone 'UTC')::date`, 'desc')
|
||||||
.limit(20)
|
.limit(20)
|
||||||
.as('a'),
|
.as('a'),
|
||||||
|
@ -135,6 +141,9 @@ export class AssetRepository implements IAssetRepository {
|
||||||
)
|
)
|
||||||
.innerJoin('exif', 'a.id', 'exif.assetId')
|
.innerJoin('exif', 'a.id', 'exif.assetId')
|
||||||
.selectAll('a')
|
.selectAll('a')
|
||||||
|
.$narrowType<{ fileCreatedAt: NotNull }>()
|
||||||
|
.$narrowType<{ fileModifiedAt: NotNull }>()
|
||||||
|
.$narrowType<{ localDateTime: NotNull }>()
|
||||||
.select((eb) => eb.fn.toJson(eb.table('exif')).as('exifInfo')),
|
.select((eb) => eb.fn.toJson(eb.table('exif')).as('exifInfo')),
|
||||||
)
|
)
|
||||||
.selectFrom('res')
|
.selectFrom('res')
|
||||||
|
@ -857,8 +866,6 @@ export class AssetRepository implements IAssetRepository {
|
||||||
.select((eb) => eb.fn.toJson(eb.table('stacked_assets')).as('stack'))
|
.select((eb) => eb.fn.toJson(eb.table('stacked_assets')).as('stack'))
|
||||||
.where('assets.ownerId', '=', anyUuid(options.userIds))
|
.where('assets.ownerId', '=', anyUuid(options.userIds))
|
||||||
.where('isVisible', '=', true)
|
.where('isVisible', '=', true)
|
||||||
.where('assets.fileCreatedAt', 'is not', null)
|
|
||||||
.where('assets.fileModifiedAt', 'is not', null)
|
|
||||||
.where('updatedAt', '>', options.updatedAfter)
|
.where('updatedAt', '>', options.updatedAfter)
|
||||||
.limit(options.limit)
|
.limit(options.limit)
|
||||||
.execute() as any as Promise<AssetEntity[]>;
|
.execute() as any as Promise<AssetEntity[]>;
|
||||||
|
|
|
@ -227,6 +227,7 @@ const getEnv = (): EnvData => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const driverOptions = {
|
const driverOptions = {
|
||||||
|
...parsedOptions,
|
||||||
onnotice: (notice: Notice) => {
|
onnotice: (notice: Notice) => {
|
||||||
if (notice['severity'] !== 'NOTICE') {
|
if (notice['severity'] !== 'NOTICE') {
|
||||||
console.warn('Postgres notice:', notice);
|
console.warn('Postgres notice:', notice);
|
||||||
|
@ -247,7 +248,9 @@ const getEnv = (): EnvData => {
|
||||||
serialize: (value: number) => value.toString(),
|
serialize: (value: number) => value.toString(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
...parsedOptions,
|
connection: {
|
||||||
|
TimeZone: 'UTC',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { readFile } from 'node:fs/promises';
|
||||||
import readLine from 'node:readline';
|
import readLine from 'node:readline';
|
||||||
import { citiesFile } from 'src/constants';
|
import { citiesFile } from 'src/constants';
|
||||||
import { DB, GeodataPlaces, NaturalearthCountries } from 'src/db';
|
import { DB, GeodataPlaces, NaturalearthCountries } from 'src/db';
|
||||||
import { AssetEntity, withExif } from 'src/entities/asset.entity';
|
import { DummyValue, GenerateSql } from 'src/decorators';
|
||||||
import { NaturalEarthCountriesTempEntity } from 'src/entities/natural-earth-countries.entity';
|
import { NaturalEarthCountriesTempEntity } from 'src/entities/natural-earth-countries.entity';
|
||||||
import { LogLevel, SystemMetadataKey } from 'src/enum';
|
import { LogLevel, SystemMetadataKey } from 'src/enum';
|
||||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||||
|
@ -76,17 +76,19 @@ export class MapRepository {
|
||||||
this.logger.log('Geodata import completed');
|
this.logger.log('Geodata import completed');
|
||||||
}
|
}
|
||||||
|
|
||||||
async getMapMarkers(
|
@GenerateSql({ params: [[DummyValue.UUID], []] })
|
||||||
ownerIds: string[],
|
getMapMarkers(ownerIds: string[], albumIds: string[], options: MapMarkerSearchOptions = {}) {
|
||||||
albumIds: string[],
|
|
||||||
options: MapMarkerSearchOptions = {},
|
|
||||||
): Promise<MapMarker[]> {
|
|
||||||
const { isArchived, isFavorite, fileCreatedAfter, fileCreatedBefore } = options;
|
const { isArchived, isFavorite, fileCreatedAfter, fileCreatedBefore } = options;
|
||||||
|
|
||||||
const assets = (await this.db
|
return this.db
|
||||||
.selectFrom('assets')
|
.selectFrom('assets')
|
||||||
.$call(withExif)
|
.innerJoin('exif', (builder) =>
|
||||||
.select('id')
|
builder
|
||||||
|
.onRef('assets.id', '=', 'exif.assetId')
|
||||||
|
.on('exif.latitude', 'is not', null)
|
||||||
|
.on('exif.longitude', 'is not', null),
|
||||||
|
)
|
||||||
|
.select(['id', 'exif.latitude as lat', 'exif.longitude as lon', 'exif.city', 'exif.state', 'exif.country'])
|
||||||
.leftJoin('albums_assets_assets', (join) => join.onRef('assets.id', '=', 'albums_assets_assets.assetsId'))
|
.leftJoin('albums_assets_assets', (join) => join.onRef('assets.id', '=', 'albums_assets_assets.assetsId'))
|
||||||
.where('isVisible', '=', true)
|
.where('isVisible', '=', true)
|
||||||
.$if(isArchived !== undefined, (q) => q.where('isArchived', '=', isArchived!))
|
.$if(isArchived !== undefined, (q) => q.where('isArchived', '=', isArchived!))
|
||||||
|
@ -94,32 +96,21 @@ export class MapRepository {
|
||||||
.$if(fileCreatedAfter !== undefined, (q) => q.where('fileCreatedAt', '>=', fileCreatedAfter!))
|
.$if(fileCreatedAfter !== undefined, (q) => q.where('fileCreatedAt', '>=', fileCreatedAfter!))
|
||||||
.$if(fileCreatedBefore !== undefined, (q) => q.where('fileCreatedAt', '<=', fileCreatedBefore!))
|
.$if(fileCreatedBefore !== undefined, (q) => q.where('fileCreatedAt', '<=', fileCreatedBefore!))
|
||||||
.where('deletedAt', 'is', null)
|
.where('deletedAt', 'is', null)
|
||||||
.where('exif.latitude', 'is not', null)
|
.where((builder) => {
|
||||||
.where('exif.longitude', 'is not', null)
|
const expression: Expression<SqlBool>[] = [];
|
||||||
.where((eb) => {
|
|
||||||
const ors: Expression<SqlBool>[] = [];
|
|
||||||
|
|
||||||
if (ownerIds.length > 0) {
|
if (ownerIds.length > 0) {
|
||||||
ors.push(eb('ownerId', 'in', ownerIds));
|
expression.push(builder('ownerId', 'in', ownerIds));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (albumIds.length > 0) {
|
if (albumIds.length > 0) {
|
||||||
ors.push(eb('albums_assets_assets.albumsId', 'in', albumIds));
|
expression.push(builder('albums_assets_assets.albumsId', 'in', albumIds));
|
||||||
}
|
}
|
||||||
|
|
||||||
return eb.or(ors);
|
return builder.or(expression);
|
||||||
})
|
})
|
||||||
.orderBy('fileCreatedAt', 'desc')
|
.orderBy('fileCreatedAt', 'desc')
|
||||||
.execute()) as any as AssetEntity[];
|
.execute() as Promise<MapMarker[]>;
|
||||||
|
|
||||||
return assets.map((asset) => ({
|
|
||||||
id: asset.id,
|
|
||||||
lat: asset.exifInfo!.latitude!,
|
|
||||||
lon: asset.exifInfo!.longitude!,
|
|
||||||
city: asset.exifInfo!.city,
|
|
||||||
state: asset.exifInfo!.state,
|
|
||||||
country: asset.exifInfo!.country,
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async reverseGeocode(point: GeoPoint): Promise<ReverseGeocodeResult> {
|
async reverseGeocode(point: GeoPoint): Promise<ReverseGeocodeResult> {
|
||||||
|
|
|
@ -37,6 +37,9 @@ export class ViewRepository {
|
||||||
.where('deletedAt', 'is', null)
|
.where('deletedAt', 'is', null)
|
||||||
.where('originalPath', 'like', `%${normalizedPath}/%`)
|
.where('originalPath', 'like', `%${normalizedPath}/%`)
|
||||||
.where('originalPath', 'not like', `%${normalizedPath}/%/%`)
|
.where('originalPath', 'not like', `%${normalizedPath}/%/%`)
|
||||||
|
.$narrowType<{ fileCreatedAt: Date }>()
|
||||||
|
.$narrowType<{ fileModifiedAt: Date }>()
|
||||||
|
.$narrowType<{ localDateTime: Date }>()
|
||||||
.orderBy(
|
.orderBy(
|
||||||
(eb) => eb.fn('regexp_replace', ['assets.originalPath', eb.val('.*/(.+)'), eb.val(String.raw`\1`)]),
|
(eb) => eb.fn('regexp_replace', ['assets.originalPath', eb.val('.*/(.+)'), eb.val(String.raw`\1`)]),
|
||||||
'asc',
|
'asc',
|
||||||
|
|
|
@ -35,12 +35,12 @@ export function clickOutside(node: HTMLElement, options: Options = {}): ActionRe
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener('click', handleClick, true);
|
document.addEventListener('mousedown', handleClick, true);
|
||||||
node.addEventListener('keydown', handleKey, false);
|
node.addEventListener('keydown', handleKey, false);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
destroy() {
|
destroy() {
|
||||||
document.removeEventListener('click', handleClick, true);
|
document.removeEventListener('mousedown', handleClick, true);
|
||||||
node.removeEventListener('keydown', handleKey, false);
|
node.removeEventListener('keydown', handleKey, false);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Reference in a new issue