mirror of
				https://codeberg.org/yeentown/barkey.git
				synced 2025-11-04 07:24:13 +00:00 
			
		
		
		
	
							parent
							
								
									705d40ab37
								
							
						
					
					
						commit
						3f71b14637
					
				
					 22 changed files with 249 additions and 214 deletions
				
			
		
							
								
								
									
										14
									
								
								migration/1595075960584-blurhash.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								migration/1595075960584-blurhash.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,14 @@
 | 
				
			||||||
 | 
					import {MigrationInterface, QueryRunner} from "typeorm";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class blurhash1595075960584 implements MigrationInterface {
 | 
				
			||||||
 | 
					    name = 'blurhash1595075960584'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public async up(queryRunner: QueryRunner): Promise<void> {
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "drive_file" ADD "blurhash" character varying(128)`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public async down(queryRunner: QueryRunner): Promise<void> {
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "drive_file" DROP COLUMN "blurhash"`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										20
									
								
								migration/1595077605646-blurhash-for-avatar-banner.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								migration/1595077605646-blurhash-for-avatar-banner.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,20 @@
 | 
				
			||||||
 | 
					import {MigrationInterface, QueryRunner} from "typeorm";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class blurhashForAvatarBanner1595077605646 implements MigrationInterface {
 | 
				
			||||||
 | 
					    name = 'blurhashForAvatarBanner1595077605646'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public async up(queryRunner: QueryRunner): Promise<void> {
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "avatarColor"`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "bannerColor"`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "user" ADD "avatarBlurhash" character varying(128)`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "user" ADD "bannerBlurhash" character varying(128)`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public async down(queryRunner: QueryRunner): Promise<void> {
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "bannerBlurhash"`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "avatarBlurhash"`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "user" ADD "bannerColor" character varying(32)`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "user" ADD "avatarColor" character varying(32)`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -112,6 +112,7 @@
 | 
				
			||||||
		"autwh": "0.1.0",
 | 
							"autwh": "0.1.0",
 | 
				
			||||||
		"aws-sdk": "2.713.0",
 | 
							"aws-sdk": "2.713.0",
 | 
				
			||||||
		"bcryptjs": "2.4.3",
 | 
							"bcryptjs": "2.4.3",
 | 
				
			||||||
 | 
							"blurhash": "1.1.3",
 | 
				
			||||||
		"bull": "3.15.0",
 | 
							"bull": "3.15.0",
 | 
				
			||||||
		"cafy": "15.2.1",
 | 
							"cafy": "15.2.1",
 | 
				
			||||||
		"cbor": "5.0.2",
 | 
							"cbor": "5.0.2",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,15 +1,9 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<span class="eiwwqkts" :class="{ cat }" :title="user | acct" v-if="disableLink && !disablePreview" v-user-preview="user.id" @click="onClick">
 | 
					<span class="eiwwqkts" :class="{ cat }" :title="user | acct" v-if="disableLink" v-user-preview="disablePreview ? undefined : user.id" @click="onClick">
 | 
				
			||||||
	<span class="inner" :style="icon"></span>
 | 
						<img class="inner" :src="url"/>
 | 
				
			||||||
</span>
 | 
					</span>
 | 
				
			||||||
<span class="eiwwqkts" :class="{ cat }" :title="user | acct" v-else-if="disableLink && disablePreview" @click="onClick">
 | 
					<router-link class="eiwwqkts" :class="{ cat }" :to="user | userPage" :title="user | acct" :target="target" v-else v-user-preview="disablePreview ? undefined : user.id">
 | 
				
			||||||
	<span class="inner" :style="icon"></span>
 | 
						<img class="inner" :src="url"/>
 | 
				
			||||||
</span>
 | 
					 | 
				
			||||||
<router-link class="eiwwqkts" :class="{ cat }" :to="user | userPage" :title="user | acct" :target="target" v-else-if="!disableLink && !disablePreview" v-user-preview="user.id">
 | 
					 | 
				
			||||||
	<span class="inner" :style="icon"></span>
 | 
					 | 
				
			||||||
</router-link>
 | 
					 | 
				
			||||||
<router-link class="eiwwqkts" :class="{ cat }" :to="user | userPage" :title="user | acct" :target="target" v-else-if="!disableLink && disablePreview">
 | 
					 | 
				
			||||||
	<span class="inner" :style="icon"></span>
 | 
					 | 
				
			||||||
</router-link>
 | 
					</router-link>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -45,22 +39,6 @@ export default Vue.extend({
 | 
				
			||||||
				? getStaticImageUrl(this.user.avatarUrl)
 | 
									? getStaticImageUrl(this.user.avatarUrl)
 | 
				
			||||||
				: this.user.avatarUrl;
 | 
									: this.user.avatarUrl;
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		icon(): any {
 | 
					 | 
				
			||||||
			return {
 | 
					 | 
				
			||||||
				backgroundColor: this.user.avatarColor,
 | 
					 | 
				
			||||||
				backgroundImage: `url(${this.url})`,
 | 
					 | 
				
			||||||
			};
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	watch: {
 | 
					 | 
				
			||||||
		'user.avatarColor'() {
 | 
					 | 
				
			||||||
			this.$el.style.color = this.user.avatarColor;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	mounted() {
 | 
					 | 
				
			||||||
		if (this.user.avatarColor) {
 | 
					 | 
				
			||||||
			this.$el.style.color = this.user.avatarColor;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	methods: {
 | 
						methods: {
 | 
				
			||||||
		onClick(e) {
 | 
							onClick(e) {
 | 
				
			||||||
| 
						 | 
					@ -102,15 +80,17 @@ export default Vue.extend({
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	.inner {
 | 
						.inner {
 | 
				
			||||||
		background-position: center center;
 | 
							position: absolute;
 | 
				
			||||||
		background-size: cover;
 | 
					 | 
				
			||||||
		bottom: 0;
 | 
							bottom: 0;
 | 
				
			||||||
		left: 0;
 | 
							left: 0;
 | 
				
			||||||
		position: absolute;
 | 
					 | 
				
			||||||
		right: 0;
 | 
							right: 0;
 | 
				
			||||||
		top: 0;
 | 
							top: 0;
 | 
				
			||||||
		border-radius: 100%;
 | 
							border-radius: 100%;
 | 
				
			||||||
		z-index: 1;
 | 
							z-index: 1;
 | 
				
			||||||
 | 
							overflow: hidden;
 | 
				
			||||||
 | 
							object-fit: cover;
 | 
				
			||||||
 | 
							width: 100%;
 | 
				
			||||||
 | 
							height: 100%;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,36 +1,15 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<div class="zdjebgpv" :class="{ detail }" ref="thumbnail" :style="`background-color: ${ background }`">
 | 
					<div class="zdjebgpv" ref="thumbnail">
 | 
				
			||||||
	<img
 | 
						<img-with-blurhash v-if="isThumbnailAvailable" :hash="file.blurhash" :src="file.thumbnailUrl" :alt="file.name" :title="file.name" :style="`object-fit: ${ fit }`"/>
 | 
				
			||||||
		:src="file.url"
 | 
					 | 
				
			||||||
		:alt="file.name"
 | 
					 | 
				
			||||||
		:title="file.name"
 | 
					 | 
				
			||||||
		@load="onThumbnailLoaded"
 | 
					 | 
				
			||||||
		v-if="detail && is === 'image'"/>
 | 
					 | 
				
			||||||
	<video
 | 
					 | 
				
			||||||
		:src="file.url"
 | 
					 | 
				
			||||||
		ref="volumectrl"
 | 
					 | 
				
			||||||
		preload="metadata"
 | 
					 | 
				
			||||||
		controls
 | 
					 | 
				
			||||||
		v-else-if="detail && is === 'video'"/>
 | 
					 | 
				
			||||||
	<img :src="file.thumbnailUrl" @load="onThumbnailLoaded" :style="`object-fit: ${ fit }`" v-else-if="isThumbnailAvailable"/>
 | 
					 | 
				
			||||||
	<fa :icon="faFileImage" class="icon" v-else-if="is === 'image'"/>
 | 
						<fa :icon="faFileImage" class="icon" v-else-if="is === 'image'"/>
 | 
				
			||||||
	<fa :icon="faFileVideo" class="icon" v-else-if="is === 'video'"/>
 | 
						<fa :icon="faFileVideo" class="icon" v-else-if="is === 'video'"/>
 | 
				
			||||||
 | 
					 | 
				
			||||||
	<audio
 | 
					 | 
				
			||||||
		:src="file.url"
 | 
					 | 
				
			||||||
		ref="volumectrl"
 | 
					 | 
				
			||||||
		preload="metadata"
 | 
					 | 
				
			||||||
		controls
 | 
					 | 
				
			||||||
		v-else-if="detail && is === 'audio'"/>
 | 
					 | 
				
			||||||
	<fa :icon="faMusic" class="icon" v-else-if="is === 'audio' || is === 'midi'"/>
 | 
						<fa :icon="faMusic" class="icon" v-else-if="is === 'audio' || is === 'midi'"/>
 | 
				
			||||||
 | 
					 | 
				
			||||||
	<fa :icon="faFileCsv" class="icon" v-else-if="is === 'csv'"/>
 | 
						<fa :icon="faFileCsv" class="icon" v-else-if="is === 'csv'"/>
 | 
				
			||||||
	<fa :icon="faFilePdf" class="icon" v-else-if="is === 'pdf'"/>
 | 
						<fa :icon="faFilePdf" class="icon" v-else-if="is === 'pdf'"/>
 | 
				
			||||||
	<fa :icon="faFileAlt" class="icon" v-else-if="is === 'textfile'"/>
 | 
						<fa :icon="faFileAlt" class="icon" v-else-if="is === 'textfile'"/>
 | 
				
			||||||
	<fa :icon="faFileArchive" class="icon" v-else-if="is === 'archive'"/>
 | 
						<fa :icon="faFileArchive" class="icon" v-else-if="is === 'archive'"/>
 | 
				
			||||||
	<fa :icon="faFile" class="icon" v-else/>
 | 
						<fa :icon="faFile" class="icon" v-else/>
 | 
				
			||||||
 | 
						<fa :icon="faFilm" class="icon-sub" v-if="isThumbnailAvailable && is === 'video'"/>
 | 
				
			||||||
	<fa :icon="faFilm" class="icon-sub" v-if="!detail && isThumbnailAvailable && is === 'video'"/>
 | 
					 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -47,8 +26,12 @@ import {
 | 
				
			||||||
	faFileArchive,
 | 
						faFileArchive,
 | 
				
			||||||
	faFilm
 | 
						faFilm
 | 
				
			||||||
	} from '@fortawesome/free-solid-svg-icons';
 | 
						} from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
 | 
					import ImgWithBlurhash from './img-with-blurhash.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
 | 
						components: {
 | 
				
			||||||
 | 
							ImgWithBlurhash
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
	props: {
 | 
						props: {
 | 
				
			||||||
		file: {
 | 
							file: {
 | 
				
			||||||
			type: Object,
 | 
								type: Object,
 | 
				
			||||||
| 
						 | 
					@ -59,11 +42,6 @@ export default Vue.extend({
 | 
				
			||||||
			required: false,
 | 
								required: false,
 | 
				
			||||||
			default: 'cover'
 | 
								default: 'cover'
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		detail: {
 | 
					 | 
				
			||||||
			type: Boolean,
 | 
					 | 
				
			||||||
			required: false,
 | 
					 | 
				
			||||||
			default: false
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
| 
						 | 
					@ -108,20 +86,12 @@ export default Vue.extend({
 | 
				
			||||||
				? (this.is === 'image' || this.is === 'video')
 | 
									? (this.is === 'image' || this.is === 'video')
 | 
				
			||||||
				: false;
 | 
									: false;
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		background(): string {
 | 
					 | 
				
			||||||
			return this.file.properties.avgColor || 'transparent';
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	mounted() {
 | 
						mounted() {
 | 
				
			||||||
		const audioTag = this.$refs.volumectrl as HTMLAudioElement;
 | 
							const audioTag = this.$refs.volumectrl as HTMLAudioElement;
 | 
				
			||||||
		if (audioTag) audioTag.volume = this.$store.state.device.mediaVolume;
 | 
							if (audioTag) audioTag.volume = this.$store.state.device.mediaVolume;
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	methods: {
 | 
						methods: {
 | 
				
			||||||
		onThumbnailLoaded() {
 | 
					 | 
				
			||||||
			if (this.file.properties.avgColor) {
 | 
					 | 
				
			||||||
				this.$refs.thumbnail.style.backgroundColor = 'transparent';
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		volumechange() {
 | 
							volumechange() {
 | 
				
			||||||
			const audioTag = this.$refs.volumectrl as HTMLAudioElement;
 | 
								const audioTag = this.$refs.volumectrl as HTMLAudioElement;
 | 
				
			||||||
			this.$store.commit('device/set', { key: 'mediaVolume', value: audioTag.volume });
 | 
								this.$store.commit('device/set', { key: 'mediaVolume', value: audioTag.volume });
 | 
				
			||||||
| 
						 | 
					@ -132,14 +102,8 @@ export default Vue.extend({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="scss" scoped>
 | 
					<style lang="scss" scoped>
 | 
				
			||||||
.zdjebgpv {
 | 
					.zdjebgpv {
 | 
				
			||||||
	display: flex;
 | 
					 | 
				
			||||||
	position: relative;
 | 
						position: relative;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	> img,
 | 
					 | 
				
			||||||
	> .icon {
 | 
					 | 
				
			||||||
		pointer-events: none;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	> .icon-sub {
 | 
						> .icon-sub {
 | 
				
			||||||
		position: absolute;
 | 
							position: absolute;
 | 
				
			||||||
		width: 30%;
 | 
							width: 30%;
 | 
				
			||||||
| 
						 | 
					@ -153,37 +117,10 @@ export default Vue.extend({
 | 
				
			||||||
		margin: auto;
 | 
							margin: auto;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	&:not(.detail) {
 | 
						> .icon {
 | 
				
			||||||
		> img {
 | 
							pointer-events: none;
 | 
				
			||||||
			height: 100%;
 | 
							height: 65%;
 | 
				
			||||||
			width: 100%;
 | 
							width: 65%;
 | 
				
			||||||
			object-fit: cover;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		> .icon {
 | 
					 | 
				
			||||||
			height: 65%;
 | 
					 | 
				
			||||||
			width: 65%;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		> video,
 | 
					 | 
				
			||||||
		> audio {
 | 
					 | 
				
			||||||
			width: 100%;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	&.detail {
 | 
					 | 
				
			||||||
		> .icon {
 | 
					 | 
				
			||||||
			height: 100px;
 | 
					 | 
				
			||||||
			width: 100px;
 | 
					 | 
				
			||||||
			margin: 16px;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		> *:not(.icon) {
 | 
					 | 
				
			||||||
			max-height: 300px;
 | 
					 | 
				
			||||||
			max-width: 100%;
 | 
					 | 
				
			||||||
			height: 100%;
 | 
					 | 
				
			||||||
			object-fit: contain;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -126,17 +126,6 @@ export default Vue.extend({
 | 
				
			||||||
			this.browser.isDragSource = false;
 | 
								this.browser.isDragSource = false;
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		onThumbnailLoaded() {
 | 
					 | 
				
			||||||
			if (this.file.properties.avgColor) {
 | 
					 | 
				
			||||||
				anime({
 | 
					 | 
				
			||||||
					targets: this.$refs.thumbnail,
 | 
					 | 
				
			||||||
					backgroundColor: 'transparent', // TODO fade
 | 
					 | 
				
			||||||
					duration: 100,
 | 
					 | 
				
			||||||
					easing: 'linear'
 | 
					 | 
				
			||||||
				});
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		rename() {
 | 
							rename() {
 | 
				
			||||||
			this.$root.dialog({
 | 
								this.$root.dialog({
 | 
				
			||||||
				title: this.$t('renameFile'),
 | 
									title: this.$t('renameFile'),
 | 
				
			||||||
| 
						 | 
					@ -332,7 +321,6 @@ export default Vue.extend({
 | 
				
			||||||
		width: 128px;
 | 
							width: 128px;
 | 
				
			||||||
		height: 128px;
 | 
							height: 128px;
 | 
				
			||||||
		margin: auto;
 | 
							margin: auto;
 | 
				
			||||||
		color: var(--driveFileIcon);
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	> .name {
 | 
						> .name {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										78
									
								
								src/client/components/img-with-blurhash.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								src/client/components/img-with-blurhash.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,78 @@
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					<div class="xubzgfgb" :title="title">
 | 
				
			||||||
 | 
						<canvas ref="canvas" :width="size" :height="size" :title="title" v-if="!loaded"/>
 | 
				
			||||||
 | 
						<img v-if="src" :src="src" :title="title" :alt="alt" @load="onLoad"/>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
					import Vue from 'vue';
 | 
				
			||||||
 | 
					import { decode } from 'blurhash';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Vue.extend({
 | 
				
			||||||
 | 
						props: {
 | 
				
			||||||
 | 
							src: {
 | 
				
			||||||
 | 
								type: String,
 | 
				
			||||||
 | 
								required: false,
 | 
				
			||||||
 | 
								default: null
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							hash: {
 | 
				
			||||||
 | 
								type: String,
 | 
				
			||||||
 | 
								required: true
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							alt: {
 | 
				
			||||||
 | 
								type: String,
 | 
				
			||||||
 | 
								required: false,
 | 
				
			||||||
 | 
								default: '',
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							title: {
 | 
				
			||||||
 | 
								type: String,
 | 
				
			||||||
 | 
								required: false,
 | 
				
			||||||
 | 
								default: null,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							size: {
 | 
				
			||||||
 | 
								type: Number,
 | 
				
			||||||
 | 
								required: false,
 | 
				
			||||||
 | 
								default: 64
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						data() {
 | 
				
			||||||
 | 
							return {
 | 
				
			||||||
 | 
								loaded: false,
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mounted() {
 | 
				
			||||||
 | 
							this.draw();
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						methods: {
 | 
				
			||||||
 | 
							draw() {
 | 
				
			||||||
 | 
								const pixels = decode(this.hash, this.size, this.size);
 | 
				
			||||||
 | 
								const ctx = (this.$refs.canvas as HTMLCanvasElement).getContext('2d');
 | 
				
			||||||
 | 
								const imageData = ctx!.createImageData(this.size, this.size);
 | 
				
			||||||
 | 
								imageData.data.set(pixels);
 | 
				
			||||||
 | 
								ctx!.putImageData(imageData, 0, 0);
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							onLoad() {
 | 
				
			||||||
 | 
								this.loaded = true;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="scss" scoped>
 | 
				
			||||||
 | 
					.xubzgfgb {
 | 
				
			||||||
 | 
						width: 100%;
 | 
				
			||||||
 | 
						height: 100%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						> canvas,
 | 
				
			||||||
 | 
						> img {
 | 
				
			||||||
 | 
							width: 100%;
 | 
				
			||||||
 | 
							height: 100%;
 | 
				
			||||||
 | 
							object-fit: cover;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					@ -1,19 +1,22 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<div class="qjewsnkgzzxlxtzncydssfbgjibiehcy" v-if="hide" @click="hide = false">
 | 
					<div class="qjewsnkg" v-if="hide" @click="hide = false">
 | 
				
			||||||
	<div>
 | 
						<img-with-blurhash class="bg" :hash="image.blurhash" :title="image.name"/>
 | 
				
			||||||
		<b><fa :icon="faExclamationTriangle"/> {{ $t('sensitive') }}</b>
 | 
						<div class="text">
 | 
				
			||||||
		<span>{{ $t('clickToShow') }}</span>
 | 
							<div>
 | 
				
			||||||
 | 
								<b><fa :icon="faExclamationTriangle"/> {{ $t('sensitive') }}</b>
 | 
				
			||||||
 | 
								<span>{{ $t('clickToShow') }}</span>
 | 
				
			||||||
 | 
							</div>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
<div class="gqnyydlzavusgskkfvwvjiattxdzsqlf" v-else>
 | 
					<div class="gqnyydlz" v-else>
 | 
				
			||||||
	<i><fa :icon="faEyeSlash" @click="hide = true"/></i>
 | 
						<i><fa :icon="faEyeSlash" @click="hide = true"/></i>
 | 
				
			||||||
	<a
 | 
						<a
 | 
				
			||||||
		:href="image.url"
 | 
							:href="image.url"
 | 
				
			||||||
		:style="style"
 | 
					 | 
				
			||||||
		:title="image.name"
 | 
							:title="image.name"
 | 
				
			||||||
		@click.prevent="onClick"
 | 
							@click.prevent="onClick"
 | 
				
			||||||
	>
 | 
						>
 | 
				
			||||||
		<div v-if="image.type === 'image/gif'">GIF</div>
 | 
							<img-with-blurhash :hash="image.blurhash" :src="url" :alt="image.name" :title="image.name"/>
 | 
				
			||||||
 | 
							<div class="gif" v-if="image.type === 'image/gif'">GIF</div>
 | 
				
			||||||
	</a>
 | 
						</a>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
| 
						 | 
					@ -23,8 +26,12 @@ import Vue from 'vue';
 | 
				
			||||||
import { faExclamationTriangle, faEyeSlash } from '@fortawesome/free-solid-svg-icons';
 | 
					import { faExclamationTriangle, faEyeSlash } from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
import { getStaticImageUrl } from '../scripts/get-static-image-url';
 | 
					import { getStaticImageUrl } from '../scripts/get-static-image-url';
 | 
				
			||||||
import ImageViewer from './image-viewer.vue';
 | 
					import ImageViewer from './image-viewer.vue';
 | 
				
			||||||
 | 
					import ImgWithBlurhash from './img-with-blurhash.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
 | 
						components: {
 | 
				
			||||||
 | 
							ImgWithBlurhash
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
	props: {
 | 
						props: {
 | 
				
			||||||
		image: {
 | 
							image: {
 | 
				
			||||||
			type: Object,
 | 
								type: Object,
 | 
				
			||||||
| 
						 | 
					@ -42,23 +49,18 @@ export default Vue.extend({
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	computed: {
 | 
						computed: {
 | 
				
			||||||
		style(): any {
 | 
							url(): any {
 | 
				
			||||||
			let url = `url(${
 | 
								let url = this.$store.state.device.disableShowingAnimatedImages
 | 
				
			||||||
				this.$store.state.device.disableShowingAnimatedImages
 | 
									? getStaticImageUrl(this.image.thumbnailUrl)
 | 
				
			||||||
					? getStaticImageUrl(this.image.thumbnailUrl)
 | 
									: this.image.thumbnailUrl;
 | 
				
			||||||
					: this.image.thumbnailUrl
 | 
					 | 
				
			||||||
			})`;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (this.$store.state.device.loadRemoteMedia) {
 | 
								if (this.$store.state.device.loadRemoteMedia) {
 | 
				
			||||||
				url = null;
 | 
									url = null;
 | 
				
			||||||
			} else if (this.raw || this.$store.state.device.loadRawImages) {
 | 
								} else if (this.raw || this.$store.state.device.loadRawImages) {
 | 
				
			||||||
				url = `url(${this.image.url})`;
 | 
									url = this.image.url;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			return {
 | 
								return url;
 | 
				
			||||||
				'background-color': this.image.properties.avgColor || 'transparent',
 | 
					 | 
				
			||||||
				'background-image': url
 | 
					 | 
				
			||||||
			};
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	created() {
 | 
						created() {
 | 
				
			||||||
| 
						 | 
					@ -82,7 +84,38 @@ export default Vue.extend({
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="scss" scoped>
 | 
					<style lang="scss" scoped>
 | 
				
			||||||
.gqnyydlzavusgskkfvwvjiattxdzsqlf {
 | 
					.qjewsnkg {
 | 
				
			||||||
 | 
						position: relative;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						> .bg {
 | 
				
			||||||
 | 
							filter: brightness(0.5);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						> .text {
 | 
				
			||||||
 | 
							position: absolute;
 | 
				
			||||||
 | 
							left: 0;
 | 
				
			||||||
 | 
							top: 0;
 | 
				
			||||||
 | 
							width: 100%;
 | 
				
			||||||
 | 
							height: 100%;
 | 
				
			||||||
 | 
							z-index: 1;
 | 
				
			||||||
 | 
							display: flex;
 | 
				
			||||||
 | 
							justify-content: center;
 | 
				
			||||||
 | 
							align-items: center;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							> div {
 | 
				
			||||||
 | 
								display: table-cell;
 | 
				
			||||||
 | 
								text-align: center;
 | 
				
			||||||
 | 
								font-size: 0.8em;
 | 
				
			||||||
 | 
								color: #fff;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								> * {
 | 
				
			||||||
 | 
									display: block;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.gqnyydlz {
 | 
				
			||||||
	position: relative;
 | 
						position: relative;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	> i {
 | 
						> i {
 | 
				
			||||||
| 
						 | 
					@ -110,7 +143,7 @@ export default Vue.extend({
 | 
				
			||||||
		background-size: contain;
 | 
							background-size: contain;
 | 
				
			||||||
		background-repeat: no-repeat;
 | 
							background-repeat: no-repeat;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		> div {
 | 
							> .gif {
 | 
				
			||||||
			background-color: var(--fg);
 | 
								background-color: var(--fg);
 | 
				
			||||||
			border-radius: 6px;
 | 
								border-radius: 6px;
 | 
				
			||||||
			color: var(--accentLighten);
 | 
								color: var(--accentLighten);
 | 
				
			||||||
| 
						 | 
					@ -126,22 +159,4 @@ export default Vue.extend({
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
.qjewsnkgzzxlxtzncydssfbgjibiehcy {
 | 
					 | 
				
			||||||
	display: flex;
 | 
					 | 
				
			||||||
	justify-content: center;
 | 
					 | 
				
			||||||
	align-items: center;
 | 
					 | 
				
			||||||
	background: #111;
 | 
					 | 
				
			||||||
	color: #fff;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	> div {
 | 
					 | 
				
			||||||
		display: table-cell;
 | 
					 | 
				
			||||||
		text-align: center;
 | 
					 | 
				
			||||||
		font-size: 12px;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		> * {
 | 
					 | 
				
			||||||
			display: block;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -114,7 +114,7 @@ export default Vue.extend({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			> * {
 | 
								> * {
 | 
				
			||||||
				overflow: hidden;
 | 
									overflow: hidden;
 | 
				
			||||||
				border-radius: 4px;
 | 
									border-radius: 6px;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			&[data-count="1"] {
 | 
								&[data-count="1"] {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,8 +10,7 @@
 | 
				
			||||||
				<mfm class="text" v-if="message.text" ref="text" :text="message.text" :i="$store.state.i"/>
 | 
									<mfm class="text" v-if="message.text" ref="text" :text="message.text" :i="$store.state.i"/>
 | 
				
			||||||
				<div class="file" v-if="message.file">
 | 
									<div class="file" v-if="message.file">
 | 
				
			||||||
					<a :href="message.file.url" rel="noopener" target="_blank" :title="message.file.name">
 | 
										<a :href="message.file.url" rel="noopener" target="_blank" :title="message.file.name">
 | 
				
			||||||
						<img v-if="message.file.type.split('/')[0] == 'image'" :src="message.file.url" :alt="message.file.name"
 | 
											<img v-if="message.file.type.split('/')[0] == 'image'" :src="message.file.url" :alt="message.file.name"/>
 | 
				
			||||||
							:style="{ backgroundColor: message.file.properties.avgColor || 'transparent' }"/>
 | 
					 | 
				
			||||||
						<p v-else>{{ message.file.name }}</p>
 | 
											<p v-else>{{ message.file.name }}</p>
 | 
				
			||||||
					</a>
 | 
										</a>
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,7 +8,7 @@
 | 
				
			||||||
	</template>
 | 
						</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<section class="oyyftmcf">
 | 
						<section class="oyyftmcf">
 | 
				
			||||||
		<mk-file-thumbnail class="preview" v-if="file" :file="file" :detail="true" fit="contain" @click="choose()"/>
 | 
							<mk-file-thumbnail class="preview" v-if="file" :file="file" fit="contain" @click="choose()"/>
 | 
				
			||||||
	</section>
 | 
						</section>
 | 
				
			||||||
</x-container>
 | 
					</x-container>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -123,10 +123,6 @@ a {
 | 
				
			||||||
	&:hover {
 | 
						&:hover {
 | 
				
			||||||
		text-decoration: underline;
 | 
							text-decoration: underline;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	* {
 | 
					 | 
				
			||||||
		cursor: pointer;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
hr {
 | 
					hr {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,6 +6,7 @@ import * as fileType from 'file-type';
 | 
				
			||||||
import isSvg from 'is-svg';
 | 
					import isSvg from 'is-svg';
 | 
				
			||||||
import * as probeImageSize from 'probe-image-size';
 | 
					import * as probeImageSize from 'probe-image-size';
 | 
				
			||||||
import * as sharp from 'sharp';
 | 
					import * as sharp from 'sharp';
 | 
				
			||||||
 | 
					import { encode } from 'blurhash';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const pipeline = util.promisify(stream.pipeline);
 | 
					const pipeline = util.promisify(stream.pipeline);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,7 +19,7 @@ export type FileInfo = {
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
	width?: number;
 | 
						width?: number;
 | 
				
			||||||
	height?: number;
 | 
						height?: number;
 | 
				
			||||||
	avgColor?: number[];
 | 
						blurhash?: string;
 | 
				
			||||||
	warnings: string[];
 | 
						warnings: string[];
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -71,12 +72,11 @@ export async function getFileInfo(path: string): Promise<FileInfo> {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// average color
 | 
						let blurhash: string | undefined;
 | 
				
			||||||
	let avgColor: number[] | undefined;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (['image/jpeg', 'image/gif', 'image/png', 'image/apng', 'image/webp', 'image/svg+xml'].includes(type.mime)) {
 | 
						if (['image/jpeg', 'image/gif', 'image/png', 'image/apng', 'image/webp', 'image/svg+xml'].includes(type.mime)) {
 | 
				
			||||||
		avgColor = await calcAvgColor(path).catch(e => {
 | 
							blurhash = await getBlurhash(path).catch(e => {
 | 
				
			||||||
			warnings.push(`calcAvgColor failed: ${e}`);
 | 
								warnings.push(`getBlurhash failed: ${e}`);
 | 
				
			||||||
			return undefined;
 | 
								return undefined;
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -87,7 +87,7 @@ export async function getFileInfo(path: string): Promise<FileInfo> {
 | 
				
			||||||
		type,
 | 
							type,
 | 
				
			||||||
		width,
 | 
							width,
 | 
				
			||||||
		height,
 | 
							height,
 | 
				
			||||||
		avgColor,
 | 
							blurhash,
 | 
				
			||||||
		warnings,
 | 
							warnings,
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -173,18 +173,15 @@ async function detectImageSize(path: string): Promise<{
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Calculate average color of image
 | 
					 * Calculate average color of image
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
async function calcAvgColor(path: string): Promise<number[]> {
 | 
					function getBlurhash(path: string): Promise<string> {
 | 
				
			||||||
	const img = sharp(path);
 | 
						return new Promise((resolve, reject) => {
 | 
				
			||||||
 | 
							sharp(path)
 | 
				
			||||||
	const info = await (img as any).stats();
 | 
								.raw()
 | 
				
			||||||
 | 
								.ensureAlpha()
 | 
				
			||||||
	if (info.isOpaque) {
 | 
								.resize(64, 64, { fit: 'inside' })
 | 
				
			||||||
		const r = Math.round(info.channels[0].mean);
 | 
								.toBuffer((err, buffer, { width, height }) => {
 | 
				
			||||||
		const g = Math.round(info.channels[1].mean);
 | 
									if (err) return reject(err);
 | 
				
			||||||
		const b = Math.round(info.channels[2].mean);
 | 
									resolve(encode(new Uint8ClampedArray(buffer), width, height, 7, 7));
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
		return [r, g, b];
 | 
						});
 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		return [255, 255, 255];
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -67,6 +67,12 @@ export class DriveFile {
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	public comment: string | null;
 | 
						public comment: string | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Column('varchar', {
 | 
				
			||||||
 | 
							length: 128, nullable: true,
 | 
				
			||||||
 | 
							comment: 'The BlurHash string.'
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						public blurhash: string | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Column('jsonb', {
 | 
						@Column('jsonb', {
 | 
				
			||||||
		default: {},
 | 
							default: {},
 | 
				
			||||||
		comment: 'The any properties of the DriveFile. For example, it includes image width/height.'
 | 
							comment: 'The any properties of the DriveFile. For example, it includes image width/height.'
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -106,14 +106,14 @@ export class User {
 | 
				
			||||||
	public bannerUrl: string | null;
 | 
						public bannerUrl: string | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Column('varchar', {
 | 
						@Column('varchar', {
 | 
				
			||||||
		length: 32, nullable: true,
 | 
							length: 128, nullable: true,
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	public avatarColor: string | null;
 | 
						public avatarBlurhash: string | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Column('varchar', {
 | 
						@Column('varchar', {
 | 
				
			||||||
		length: 32, nullable: true,
 | 
							length: 128, nullable: true,
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	public bannerColor: string | null;
 | 
						public bannerBlurhash: string | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Column('boolean', {
 | 
						@Column('boolean', {
 | 
				
			||||||
		default: false,
 | 
							default: false,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -115,6 +115,7 @@ export class DriveFileRepository extends Repository<DriveFile> {
 | 
				
			||||||
			md5: file.md5,
 | 
								md5: file.md5,
 | 
				
			||||||
			size: file.size,
 | 
								size: file.size,
 | 
				
			||||||
			isSensitive: file.isSensitive,
 | 
								isSensitive: file.isSensitive,
 | 
				
			||||||
 | 
								blurhash: file.blurhash,
 | 
				
			||||||
			properties: file.properties,
 | 
								properties: file.properties,
 | 
				
			||||||
			url: opts.self ? file.url : this.getPublicUrl(file, false, meta),
 | 
								url: opts.self ? file.url : this.getPublicUrl(file, false, meta),
 | 
				
			||||||
			thumbnailUrl: this.getPublicUrl(file, true, meta),
 | 
								thumbnailUrl: this.getPublicUrl(file, true, meta),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -165,7 +165,8 @@ export class UserRepository extends Repository<User> {
 | 
				
			||||||
			username: user.username,
 | 
								username: user.username,
 | 
				
			||||||
			host: user.host,
 | 
								host: user.host,
 | 
				
			||||||
			avatarUrl: user.avatarUrl ? user.avatarUrl : config.url + '/avatar/' + user.id,
 | 
								avatarUrl: user.avatarUrl ? user.avatarUrl : config.url + '/avatar/' + user.id,
 | 
				
			||||||
			avatarColor: user.avatarColor,
 | 
								avatarBlurhash: user.avatarBlurhash,
 | 
				
			||||||
 | 
								avatarColor: null, // 後方互換性のため
 | 
				
			||||||
			isAdmin: user.isAdmin || falsy,
 | 
								isAdmin: user.isAdmin || falsy,
 | 
				
			||||||
			isModerator: user.isModerator || falsy,
 | 
								isModerator: user.isModerator || falsy,
 | 
				
			||||||
			isBot: user.isBot || falsy,
 | 
								isBot: user.isBot || falsy,
 | 
				
			||||||
| 
						 | 
					@ -196,7 +197,8 @@ export class UserRepository extends Repository<User> {
 | 
				
			||||||
				createdAt: user.createdAt.toISOString(),
 | 
									createdAt: user.createdAt.toISOString(),
 | 
				
			||||||
				updatedAt: user.updatedAt ? user.updatedAt.toISOString() : null,
 | 
									updatedAt: user.updatedAt ? user.updatedAt.toISOString() : null,
 | 
				
			||||||
				bannerUrl: user.bannerUrl,
 | 
									bannerUrl: user.bannerUrl,
 | 
				
			||||||
				bannerColor: user.bannerColor,
 | 
									bannerBlurhash: user.bannerBlurhash,
 | 
				
			||||||
 | 
									bannerColor: null, // 後方互換性のため
 | 
				
			||||||
				isLocked: user.isLocked,
 | 
									isLocked: user.isLocked,
 | 
				
			||||||
				isModerator: user.isModerator || falsy,
 | 
									isModerator: user.isModerator || falsy,
 | 
				
			||||||
				isSilenced: user.isSilenced || falsy,
 | 
									isSilenced: user.isSilenced || falsy,
 | 
				
			||||||
| 
						 | 
					@ -331,7 +333,7 @@ export const packedUserSchema = {
 | 
				
			||||||
			format: 'url',
 | 
								format: 'url',
 | 
				
			||||||
			nullable: true as const, optional: false as const,
 | 
								nullable: true as const, optional: false as const,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		avatarColor: {
 | 
							avatarBlurhash: {
 | 
				
			||||||
			type: 'any' as const,
 | 
								type: 'any' as const,
 | 
				
			||||||
			nullable: true as const, optional: false as const,
 | 
								nullable: true as const, optional: false as const,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
| 
						 | 
					@ -340,7 +342,7 @@ export const packedUserSchema = {
 | 
				
			||||||
			format: 'url',
 | 
								format: 'url',
 | 
				
			||||||
			nullable: true as const, optional: true as const,
 | 
								nullable: true as const, optional: true as const,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		bannerColor: {
 | 
							bannerBlurhash: {
 | 
				
			||||||
			type: 'any' as const,
 | 
								type: 'any' as const,
 | 
				
			||||||
			nullable: true as const, optional: true as const,
 | 
								nullable: true as const, optional: true as const,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -226,24 +226,24 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
 | 
				
			||||||
	const bannerId = banner ? banner.id : null;
 | 
						const bannerId = banner ? banner.id : null;
 | 
				
			||||||
	const avatarUrl = avatar ? DriveFiles.getPublicUrl(avatar, true) : null;
 | 
						const avatarUrl = avatar ? DriveFiles.getPublicUrl(avatar, true) : null;
 | 
				
			||||||
	const bannerUrl = banner ? DriveFiles.getPublicUrl(banner) : null;
 | 
						const bannerUrl = banner ? DriveFiles.getPublicUrl(banner) : null;
 | 
				
			||||||
	const avatarColor = avatar && avatar.properties.avgColor ? avatar.properties.avgColor : null;
 | 
						const avatarBlurhash = avatar ? avatar.blurhash : null;
 | 
				
			||||||
	const bannerColor = banner && banner.properties.avgColor ? banner.properties.avgColor : null;
 | 
						const bannerBlurhash = banner ? banner.blurhash : null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	await Users.update(user!.id, {
 | 
						await Users.update(user!.id, {
 | 
				
			||||||
		avatarId,
 | 
							avatarId,
 | 
				
			||||||
		bannerId,
 | 
							bannerId,
 | 
				
			||||||
		avatarUrl,
 | 
							avatarUrl,
 | 
				
			||||||
		bannerUrl,
 | 
							bannerUrl,
 | 
				
			||||||
		avatarColor,
 | 
							avatarBlurhash,
 | 
				
			||||||
		bannerColor
 | 
							bannerBlurhash
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	user!.avatarId = avatarId;
 | 
						user!.avatarId = avatarId;
 | 
				
			||||||
	user!.bannerId = bannerId;
 | 
						user!.bannerId = bannerId;
 | 
				
			||||||
	user!.avatarUrl = avatarUrl;
 | 
						user!.avatarUrl = avatarUrl;
 | 
				
			||||||
	user!.bannerUrl = bannerUrl;
 | 
						user!.bannerUrl = bannerUrl;
 | 
				
			||||||
	user!.avatarColor = avatarColor;
 | 
						user!.avatarBlurhash = avatarBlurhash;
 | 
				
			||||||
	user!.bannerColor = bannerColor;
 | 
						user!.bannerBlurhash = bannerBlurhash;
 | 
				
			||||||
	//#endregion
 | 
						//#endregion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	//#region カスタム絵文字取得
 | 
						//#region カスタム絵文字取得
 | 
				
			||||||
| 
						 | 
					@ -341,13 +341,13 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
 | 
				
			||||||
	if (avatar) {
 | 
						if (avatar) {
 | 
				
			||||||
		updates.avatarId = avatar.id;
 | 
							updates.avatarId = avatar.id;
 | 
				
			||||||
		updates.avatarUrl = DriveFiles.getPublicUrl(avatar, true);
 | 
							updates.avatarUrl = DriveFiles.getPublicUrl(avatar, true);
 | 
				
			||||||
		updates.avatarColor = avatar.properties.avgColor ? avatar.properties.avgColor : null;
 | 
							updates.avatarBlurhash = avatar.blurhash;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (banner) {
 | 
						if (banner) {
 | 
				
			||||||
		updates.bannerId = banner.id;
 | 
							updates.bannerId = banner.id;
 | 
				
			||||||
		updates.bannerUrl = DriveFiles.getPublicUrl(banner);
 | 
							updates.bannerUrl = DriveFiles.getPublicUrl(banner);
 | 
				
			||||||
		updates.bannerColor = banner.properties.avgColor ? banner.properties.avgColor : null;
 | 
							updates.bannerBlurhash = banner.blurhash;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Update user
 | 
						// Update user
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -210,8 +210,8 @@ export default define(meta, async (ps, user, token) => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		updates.avatarUrl = DriveFiles.getPublicUrl(avatar, true);
 | 
							updates.avatarUrl = DriveFiles.getPublicUrl(avatar, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (avatar.properties.avgColor) {
 | 
							if (avatar.blurhash) {
 | 
				
			||||||
			updates.avatarColor = avatar.properties.avgColor;
 | 
								updates.avatarBlurhash = avatar.blurhash;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -223,8 +223,8 @@ export default define(meta, async (ps, user, token) => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		updates.bannerUrl = DriveFiles.getPublicUrl(banner, false);
 | 
							updates.bannerUrl = DriveFiles.getPublicUrl(banner, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (banner.properties.avgColor) {
 | 
							if (banner.blurhash) {
 | 
				
			||||||
			updates.bannerColor = banner.properties.avgColor;
 | 
								updates.bannerBlurhash = banner.blurhash;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -327,7 +327,6 @@ export default async function(
 | 
				
			||||||
	const properties: {
 | 
						const properties: {
 | 
				
			||||||
		width?: number;
 | 
							width?: number;
 | 
				
			||||||
		height?: number;
 | 
							height?: number;
 | 
				
			||||||
		avgColor?: string;
 | 
					 | 
				
			||||||
	} = {};
 | 
						} = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (info.width) {
 | 
						if (info.width) {
 | 
				
			||||||
| 
						 | 
					@ -335,10 +334,6 @@ export default async function(
 | 
				
			||||||
		properties['height'] = info.height;
 | 
							properties['height'] = info.height;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (info.avgColor) {
 | 
					 | 
				
			||||||
		properties['avgColor'] = `rgb(${info.avgColor.join(',')})`;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const profile = user ? await UserProfiles.findOne(user.id) : null;
 | 
						const profile = user ? await UserProfiles.findOne(user.id) : null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const folder = await fetchFolder();
 | 
						const folder = await fetchFolder();
 | 
				
			||||||
| 
						 | 
					@ -351,6 +346,7 @@ export default async function(
 | 
				
			||||||
	file.folderId = folder !== null ? folder.id : null;
 | 
						file.folderId = folder !== null ? folder.id : null;
 | 
				
			||||||
	file.comment = comment;
 | 
						file.comment = comment;
 | 
				
			||||||
	file.properties = properties;
 | 
						file.properties = properties;
 | 
				
			||||||
 | 
						file.blurhash = info.blurhash || null;
 | 
				
			||||||
	file.isLink = isLink;
 | 
						file.isLink = isLink;
 | 
				
			||||||
	file.isSensitive = user
 | 
						file.isSensitive = user
 | 
				
			||||||
		? Users.isLocalUser(user) && profile!.alwaysMarkNsfw ? true :
 | 
							? Users.isLocalUser(user) && profile!.alwaysMarkNsfw ? true :
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -26,7 +26,7 @@ describe('Get file info', () => {
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			width: undefined,
 | 
								width: undefined,
 | 
				
			||||||
			height: undefined,
 | 
								height: undefined,
 | 
				
			||||||
			avgColor: undefined
 | 
								blurhash: null
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	}));
 | 
						}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -43,7 +43,7 @@ describe('Get file info', () => {
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			width: 512,
 | 
								width: 512,
 | 
				
			||||||
			height: 512,
 | 
								height: 512,
 | 
				
			||||||
			avgColor: [ 181, 99, 106 ]
 | 
								blurhash: '' // TODO
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	}));
 | 
						}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -60,7 +60,7 @@ describe('Get file info', () => {
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			width: 256,
 | 
								width: 256,
 | 
				
			||||||
			height: 256,
 | 
								height: 256,
 | 
				
			||||||
			avgColor: [ 249, 253, 250 ]
 | 
								blurhash: '' // TODO
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	}));
 | 
						}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -77,7 +77,7 @@ describe('Get file info', () => {
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			width: 256,
 | 
								width: 256,
 | 
				
			||||||
			height: 256,
 | 
								height: 256,
 | 
				
			||||||
			avgColor: [ 249, 253, 250 ]
 | 
								blurhash: '' // TODO
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	}));
 | 
						}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -94,7 +94,7 @@ describe('Get file info', () => {
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			width: 256,
 | 
								width: 256,
 | 
				
			||||||
			height: 256,
 | 
								height: 256,
 | 
				
			||||||
			avgColor: [ 255, 255, 255 ]
 | 
								blurhash: '' // TODO
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	}));
 | 
						}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -111,7 +111,7 @@ describe('Get file info', () => {
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			width: 256,
 | 
								width: 256,
 | 
				
			||||||
			height: 256,
 | 
								height: 256,
 | 
				
			||||||
			avgColor: [ 255, 255, 255 ]
 | 
								blurhash: '' // TODO
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	}));
 | 
						}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -129,7 +129,7 @@ describe('Get file info', () => {
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			width: 256,
 | 
								width: 256,
 | 
				
			||||||
			height: 256,
 | 
								height: 256,
 | 
				
			||||||
			avgColor: [ 255, 255, 255 ]
 | 
								blurhash: '' // TODO
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	}));
 | 
						}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -146,7 +146,7 @@ describe('Get file info', () => {
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			width: 25000,
 | 
								width: 25000,
 | 
				
			||||||
			height: 25000,
 | 
								height: 25000,
 | 
				
			||||||
			avgColor: undefined
 | 
								blurhash: '' // TODO
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	}));
 | 
						}));
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1669,6 +1669,11 @@ bluebird@^3.1.1, bluebird@^3.4.1:
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
 | 
					  resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
 | 
				
			||||||
  integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
 | 
					  integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					blurhash@1.1.3:
 | 
				
			||||||
 | 
					  version "1.1.3"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/blurhash/-/blurhash-1.1.3.tgz#dc325af7da836d07a0861d830bdd63694382483e"
 | 
				
			||||||
 | 
					  integrity sha512-yUhPJvXexbqbyijCIE/T2NCXcj9iNPhWmOKbPTuR/cm7Q5snXYIfnVnz6m7MWOXxODMz/Cr3UcVkRdHiuDVRDw==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bn.js@^4.0.0:
 | 
					bn.js@^4.0.0:
 | 
				
			||||||
  version "4.11.8"
 | 
					  version "4.11.8"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f"
 | 
					  resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue