Merge branch 'notification-read-api' into swn

This commit is contained in:
tamaina 2021-12-28 18:46:44 +09:00
commit 9a78690222
20 changed files with 204 additions and 116 deletions

View File

@ -1,2 +1,47 @@
export const USER_ONLINE_THRESHOLD = 1000 * 60 * 10; // 10min export const USER_ONLINE_THRESHOLD = 1000 * 60 * 10; // 10min
export const USER_ACTIVE_THRESHOLD = 1000 * 60 * 60 * 24 * 3; // 3days export const USER_ACTIVE_THRESHOLD = 1000 * 60 * 60 * 24 * 3; // 3days
// ブラウザで直接表示することを許可するファイルの種類のリスト
// ここに含まれないものは application/octet-stream としてレスポンスされる
// SVGはXSSを生むので許可しない
export const FILE_TYPE_BROWSERSAFE = [
// Images
'image/png',
'image/gif',
'image/jpeg',
'image/webp',
'image/apng',
'image/bmp',
'image/tiff',
'image/x-icon',
// OggS
'audio/opus',
'video/ogg',
'audio/ogg',
'application/ogg',
// ISO/IEC base media file format
'video/quicktime',
'video/mp4',
'audio/mp4',
'video/x-m4v',
'audio/x-m4a',
'video/3gpp',
'video/3gpp2',
'video/mpeg',
'audio/mpeg',
'video/webm',
'audio/webm',
'audio/aac',
'audio/x-flac',
'audio/vnd.wave',
];
/*
https://github.com/sindresorhus/file-type/blob/main/supported.js
https://github.com/sindresorhus/file-type/blob/main/core.js
https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers
*/

View File

@ -99,7 +99,10 @@ export async function getFileInfo(path: string): Promise<FileInfo> {
/** /**
* Detect MIME Type and extension * Detect MIME Type and extension
*/ */
export async function detectType(path: string) { export async function detectType(path: string): Promise<{
mime: string;
ext: string | null;
}> {
// Check 0 byte // Check 0 byte
const fileSize = await getFileSize(path); const fileSize = await getFileSize(path);
if (fileSize === 0) { if (fileSize === 0) {

View File

@ -14,6 +14,7 @@ import { detectType } from '@/misc/get-file-info';
import { convertToJpeg, convertToPngOrJpeg } from '@/services/drive/image-processor'; import { convertToJpeg, convertToPngOrJpeg } from '@/services/drive/image-processor';
import { GenerateVideoThumbnail } from '@/services/drive/generate-video-thumbnail'; import { GenerateVideoThumbnail } from '@/services/drive/generate-video-thumbnail';
import { StatusError } from '@/misc/fetch'; import { StatusError } from '@/misc/fetch';
import { FILE_TYPE_BROWSERSAFE } from '@/const';
//const _filename = fileURLToPath(import.meta.url); //const _filename = fileURLToPath(import.meta.url);
const _filename = __filename; const _filename = __filename;
@ -27,6 +28,7 @@ const commonReadableHandlerGenerator = (ctx: Koa.Context) => (e: Error): void =>
ctx.set('Cache-Control', 'max-age=300'); ctx.set('Cache-Control', 'max-age=300');
}; };
// eslint-disable-next-line import/no-default-export
export default async function(ctx: Koa.Context) { export default async function(ctx: Koa.Context) {
const key = ctx.params.key; const key = ctx.params.key;
@ -81,7 +83,7 @@ export default async function(ctx: Koa.Context) {
const image = await convertFile(); const image = await convertFile();
ctx.body = image.data; ctx.body = image.data;
ctx.set('Content-Type', image.type); ctx.set('Content-Type', FILE_TYPE_BROWSERSAFE.includes(image.type) ? image.type : 'application/octet-stream');
ctx.set('Cache-Control', 'max-age=31536000, immutable'); ctx.set('Cache-Control', 'max-age=31536000, immutable');
} catch (e) { } catch (e) {
serverLogger.error(`${e}`); serverLogger.error(`${e}`);
@ -112,14 +114,14 @@ export default async function(ctx: Koa.Context) {
}).toString(); }).toString();
ctx.body = InternalStorage.read(key); ctx.body = InternalStorage.read(key);
ctx.set('Content-Type', mime); ctx.set('Content-Type', FILE_TYPE_BROWSERSAFE.includes(mime) ? mime : 'application/octet-stream');
ctx.set('Cache-Control', 'max-age=31536000, immutable'); ctx.set('Cache-Control', 'max-age=31536000, immutable');
ctx.set('Content-Disposition', contentDisposition('inline', filename)); ctx.set('Content-Disposition', contentDisposition('inline', filename));
} else { } else {
const readable = InternalStorage.read(file.accessKey!); const readable = InternalStorage.read(file.accessKey!);
readable.on('error', commonReadableHandlerGenerator(ctx)); readable.on('error', commonReadableHandlerGenerator(ctx));
ctx.body = readable; ctx.body = readable;
ctx.set('Content-Type', file.type); ctx.set('Content-Type', FILE_TYPE_BROWSERSAFE.includes(file.type) ? file.type : 'application/octet-stream');
ctx.set('Cache-Control', 'max-age=31536000, immutable'); ctx.set('Cache-Control', 'max-age=31536000, immutable');
ctx.set('Content-Disposition', contentDisposition('inline', file.name)); ctx.set('Content-Disposition', contentDisposition('inline', file.name));
} }

View File

@ -6,6 +6,7 @@ import { createTemp } from '@/misc/create-temp';
import { downloadUrl } from '@/misc/download-url'; import { downloadUrl } from '@/misc/download-url';
import { detectType } from '@/misc/get-file-info'; import { detectType } from '@/misc/get-file-info';
import { StatusError } from '@/misc/fetch'; import { StatusError } from '@/misc/fetch';
import { FILE_TYPE_BROWSERSAFE } from '@/const';
export async function proxyMedia(ctx: Koa.Context) { export async function proxyMedia(ctx: Koa.Context) {
const url = 'url' in ctx.query ? ctx.query.url : 'https://' + ctx.params.url; const url = 'url' in ctx.query ? ctx.query.url : 'https://' + ctx.params.url;
@ -18,7 +19,7 @@ export async function proxyMedia(ctx: Koa.Context) {
const { mime, ext } = await detectType(path); const { mime, ext } = await detectType(path);
if (!mime.startsWith('image/')) throw 403; if (!FILE_TYPE_BROWSERSAFE.includes(mime)) throw 403;
let image: IImage; let image: IImage;

View File

@ -20,6 +20,7 @@ import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error';
import * as S3 from 'aws-sdk/clients/s3'; import * as S3 from 'aws-sdk/clients/s3';
import { getS3 } from './s3'; import { getS3 } from './s3';
import * as sharp from 'sharp'; import * as sharp from 'sharp';
import { FILE_TYPE_BROWSERSAFE } from '@/const';
const logger = driveLogger.createSubLogger('register', 'yellow'); const logger = driveLogger.createSubLogger('register', 'yellow');
@ -241,6 +242,7 @@ export async function generateAlts(path: string, type: string, generateWeb: bool
*/ */
async function upload(key: string, stream: fs.ReadStream | Buffer, type: string, filename?: string) { async function upload(key: string, stream: fs.ReadStream | Buffer, type: string, filename?: string) {
if (type === 'image/apng') type = 'image/png'; if (type === 'image/apng') type = 'image/png';
if (!FILE_TYPE_BROWSERSAFE.includes(type)) type = 'application/octet-stream';
const meta = await fetchMeta(); const meta = await fetchMeta();

View File

@ -77,7 +77,7 @@
import { defineComponent, markRaw } from 'vue'; import { defineComponent, markRaw } from 'vue';
import { emojilist } from '@/scripts/emojilist'; import { emojilist } from '@/scripts/emojilist';
import { getStaticImageUrl } from '@/scripts/get-static-image-url'; import { getStaticImageUrl } from '@/scripts/get-static-image-url';
import Particle from '@/components/particle.vue'; import Ripple from '@/components/ripple.vue';
import * as os from '@/os'; import * as os from '@/os';
import { isTouchUsing } from '@/scripts/touch'; import { isTouchUsing } from '@/scripts/touch';
import { isMobile } from '@/scripts/is-mobile'; import { isMobile } from '@/scripts/is-mobile';
@ -296,9 +296,9 @@ export default defineComponent({
if (ev) { if (ev) {
const el = ev.currentTarget || ev.target; const el = ev.currentTarget || ev.target;
const rect = el.getBoundingClientRect(); const rect = el.getBoundingClientRect();
const x = rect.left + (el.clientWidth / 2); const x = rect.left + (el.offsetWidth / 2);
const y = rect.top + (el.clientHeight / 2); const y = rect.top + (el.offsetHeight / 2);
os.popup(Particle, { x, y }, {}, 'end'); os.popup(Ripple, { x, y }, {}, 'end');
} }
const key = this.getKey(emoji); const key = this.getKey(emoji);

View File

@ -5,7 +5,7 @@
<div ref="prefixEl" class="prefix"><slot name="prefix"></slot></div> <div ref="prefixEl" class="prefix"><slot name="prefix"></slot></div>
<input ref="inputEl" <input ref="inputEl"
v-model="v" v-model="v"
v-panel v-adaptive-border
:type="type" :type="type"
:disabled="disabled" :disabled="disabled"
:required="required" :required="required"
@ -243,7 +243,8 @@ export default defineComponent({
font-weight: normal; font-weight: normal;
font-size: 1em; font-size: 1em;
color: var(--fg); color: var(--fg);
border: solid 0.5px var(--panel); background: var(--panel);
border: solid 1px var(--panel);
border-radius: 6px; border-radius: 6px;
outline: none; outline: none;
box-shadow: none; box-shadow: none;
@ -251,7 +252,7 @@ export default defineComponent({
transition: border-color 0.1s ease-out; transition: border-color 0.1s ease-out;
&:hover { &:hover {
border-color: var(--inputBorderHover); border-color: var(--inputBorderHover) !important;
} }
} }
@ -298,7 +299,7 @@ export default defineComponent({
&.focused { &.focused {
> input { > input {
border-color: var(--accent); border-color: var(--accent) !important;
//box-shadow: 0 0 0 4px var(--focus); //box-shadow: 0 0 0 4px var(--focus);
} }
} }

View File

@ -3,7 +3,9 @@
<div class="label" @click="focus"><slot name="label"></slot></div> <div class="label" @click="focus"><slot name="label"></slot></div>
<div ref="container" class="input" :class="{ inline, disabled, focused }" @click.prevent="onClick"> <div ref="container" class="input" :class="{ inline, disabled, focused }" @click.prevent="onClick">
<div ref="prefixEl" class="prefix"><slot name="prefix"></slot></div> <div ref="prefixEl" class="prefix"><slot name="prefix"></slot></div>
<select ref="inputEl" v-model="v" v-panel <select ref="inputEl"
v-model="v"
v-adaptive-border
class="select" class="select"
:disabled="disabled" :disabled="disabled"
:required="required" :required="required"
@ -226,7 +228,7 @@ export default defineComponent({
&:hover { &:hover {
> .select { > .select {
border-color: var(--inputBorderHover); border-color: var(--inputBorderHover) !important;
} }
} }
@ -242,6 +244,7 @@ export default defineComponent({
font-weight: normal; font-weight: normal;
font-size: 1em; font-size: 1em;
color: var(--fg); color: var(--fg);
background: var(--panel);
border: solid 1px var(--panel); border: solid 1px var(--panel);
border-radius: 6px; border-radius: 6px;
outline: none; outline: none;
@ -295,7 +298,7 @@ export default defineComponent({
&.focused { &.focused {
> select { > select {
border-color: var(--accent); border-color: var(--accent) !important;
} }
} }

View File

@ -2,10 +2,6 @@
<div <div
class="ziffeoms" class="ziffeoms"
:class="{ disabled, checked }" :class="{ disabled, checked }"
role="switch"
:aria-checked="checked"
:aria-disabled="disabled"
@click.prevent="toggle"
> >
<input <input
ref="input" ref="input"
@ -13,18 +9,20 @@
:disabled="disabled" :disabled="disabled"
@keydown.enter="toggle" @keydown.enter="toggle"
> >
<span v-tooltip="checked ? $ts.itsOn : $ts.itsOff" class="button"> <span ref="button" v-adaptive-border v-tooltip="checked ? $ts.itsOn : $ts.itsOff" class="button" @click.prevent="toggle">
<span class="handle"></span> <i class="check fas fa-check"></i>
</span> </span>
<span class="label"> <span class="label">
<span><slot></slot></span> <span @click="toggle"><slot></slot></span>
<p class="caption"><slot name="caption"></slot></p> <p class="caption"><slot name="caption"></slot></p>
</span> </span>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent, ref, toRefs } from 'vue';
import * as os from '@/os';
import Ripple from '@/components/ripple.vue';
export default defineComponent({ export default defineComponent({
props: { props: {
@ -37,17 +35,28 @@ export default defineComponent({
default: false default: false
} }
}, },
computed: {
checked(): boolean { setup(props, context) {
return this.modelValue; const button = ref<HTMLElement>();
} const checked = toRefs(props).modelValue;
const toggle = () => {
if (props.disabled) return;
context.emit('update:modelValue', !checked.value);
if (!checked.value) {
const rect = button.value.getBoundingClientRect();
const x = rect.left + (button.value.offsetWidth / 2);
const y = rect.top + (button.value.offsetHeight / 2);
os.popup(Ripple, { x, y, particle: false }, {}, 'end');
}
};
return {
button,
checked,
toggle,
};
}, },
methods: {
toggle() {
if (this.disabled) return;
this.$emit('update:modelValue', !this.checked);
}
}
}); });
</script> </script>
@ -55,16 +64,7 @@ export default defineComponent({
.ziffeoms { .ziffeoms {
position: relative; position: relative;
display: flex; display: flex;
cursor: pointer; transition: all 0.2s ease;
transition: all 0.3s;
&:first-child {
margin-top: 0;
}
&:last-child {
margin-bottom: 0;
}
> * { > * {
user-select: none; user-select: none;
@ -80,27 +80,32 @@ export default defineComponent({
> .button { > .button {
position: relative; position: relative;
display: inline-block; display: inline-flex;
flex-shrink: 0; flex-shrink: 0;
margin: 0; margin: 0;
width: 36px; box-sizing: border-box;
height: 26px; width: 23px;
background: var(--switchBg); height: 23px;
outline: none; outline: none;
border-radius: 999px; background: var(--panel);
border: solid 1px var(--panel);
border-radius: 4px;
cursor: pointer;
transition: inherit; transition: inherit;
> .handle { > .check {
position: absolute; margin: auto;
top: 0; opacity: 0;
bottom: 0; color: var(--fgOnAccent);
left: 5px; font-size: 13px;
margin: auto 0; transform: scale(0.5);
border-radius: 100%; transition: all 0.2s ease;
transition: background-color 0.3s, transform 0.3s; }
width: 16px; }
height: 16px;
background-color: #fff; &:hover {
> .button {
border-color: var(--inputBorderHover) !important;
} }
} }
@ -108,13 +113,13 @@ export default defineComponent({
margin-left: 16px; margin-left: 16px;
margin-top: 2px; margin-top: 2px;
display: block; display: block;
cursor: pointer;
transition: inherit; transition: inherit;
color: var(--fg); color: var(--fg);
> span { > span {
display: block; display: block;
line-height: 20px; line-height: 20px;
cursor: pointer;
transition: inherit; transition: inherit;
} }
@ -129,12 +134,6 @@ export default defineComponent({
} }
} }
&:hover {
> .button {
background-color: var(--accentedBg);
}
}
&.disabled { &.disabled {
opacity: 0.6; opacity: 0.6;
cursor: not-allowed; cursor: not-allowed;
@ -142,11 +141,12 @@ export default defineComponent({
&.checked { &.checked {
> .button { > .button {
background-color: var(--accent); background-color: var(--accent) !important;
border-color: var(--accent); border-color: var(--accent) !important;
> .handle { > .check {
transform: translateX(10px); opacity: 1;
transform: scale(1);
} }
} }
} }

View File

@ -4,7 +4,7 @@
<div class="input" :class="{ disabled, focused, tall, pre }"> <div class="input" :class="{ disabled, focused, tall, pre }">
<textarea ref="inputEl" <textarea ref="inputEl"
v-model="v" v-model="v"
v-panel v-adaptive-border
:class="{ code, _monospace: code }" :class="{ code, _monospace: code }"
:disabled="disabled" :disabled="disabled"
:required="required" :required="required"
@ -210,7 +210,8 @@ export default defineComponent({
font-weight: normal; font-weight: normal;
font-size: 1em; font-size: 1em;
color: var(--fg); color: var(--fg);
border: solid 0.5px var(--panel); background: var(--panel);
border: solid 1px var(--panel);
border-radius: 6px; border-radius: 6px;
outline: none; outline: none;
box-shadow: none; box-shadow: none;
@ -218,13 +219,13 @@ export default defineComponent({
transition: border-color 0.1s ease-out; transition: border-color 0.1s ease-out;
&:hover { &:hover {
border-color: var(--inputBorderHover); border-color: var(--inputBorderHover) !important;
} }
} }
&.focused { &.focused {
> textarea { > textarea {
border-color: var(--accent); border-color: var(--accent) !important;
} }
} }

View File

@ -6,7 +6,7 @@
<i v-else-if="info.icon" class="icon" :class="info.icon"></i> <i v-else-if="info.icon" class="icon" :class="info.icon"></i>
<div class="title"> <div class="title">
<MkUserName v-if="info.userName" :user="info.userName" :nowrap="false" class="title"/> <MkUserName v-if="info.userName" :user="info.userName" :nowrap="true" class="title"/>
<div v-else-if="info.title" class="title">{{ info.title }}</div> <div v-else-if="info.title" class="title">{{ info.title }}</div>
<div v-if="!narrow && info.subtitle" class="subtitle"> <div v-if="!narrow && info.subtitle" class="subtitle">
{{ info.subtitle }} {{ info.subtitle }}
@ -268,6 +268,7 @@ export default defineComponent({
> .titleContainer { > .titleContainer {
display: flex; display: flex;
align-items: center; align-items: center;
max-width: 400px;
overflow: auto; overflow: auto;
white-space: nowrap; white-space: nowrap;
text-align: left; text-align: left;

View File

@ -2,7 +2,7 @@
<button <button
v-if="count > 0" v-if="count > 0"
ref="buttonRef" ref="buttonRef"
v-particle="canToggle" v-ripple="canToggle"
class="hkzvhatu _button" class="hkzvhatu _button"
:class="{ reacted: note.myReaction == reaction, canToggle }" :class="{ reacted: note.myReaction == reaction, canToggle }"
@click="toggleReaction()" @click="toggleReaction()"

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="vswabwbm" :style="{ top: `${y - 64}px`, left: `${x - 64}px` }" :class="{ active }"> <div class="vswabwbm" :style="{ zIndex, top: `${y - 64}px`, left: `${x - 64}px` }" :class="{ active }">
<svg width="128" height="128" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg"> <svg width="128" height="128" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg">
<circle fill="none" cx="64" cy="64"> <circle fill="none" cx="64" cy="64">
<animate attributeName="r" <animate attributeName="r"
@ -52,7 +52,8 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent, onMounted } from 'vue';
import * as os from '@/os';
export default defineComponent({ export default defineComponent({
props: { props: {
@ -63,37 +64,46 @@ export default defineComponent({
y: { y: {
type: Number, type: Number,
required: true required: true
},
particle: {
type: Boolean,
required: false,
default: true,
} }
}, },
emits: ['end'], emits: ['end'],
data() { setup(props, context) {
const particles = []; const particles = [];
const origin = 64; const origin = 64;
const colors = ['#FF1493', '#00FFFF', '#FFE202']; const colors = ['#FF1493', '#00FFFF', '#FFE202'];
for (let i = 0; i < 12; i++) { if (props.particle) {
const angle = Math.random() * (Math.PI * 2); for (let i = 0; i < 12; i++) {
const pos = Math.random() * 16; const angle = Math.random() * (Math.PI * 2);
const velocity = 16 + (Math.random() * 48); const pos = Math.random() * 16;
particles.push({ const velocity = 16 + (Math.random() * 48);
size: 4 + (Math.random() * 8), particles.push({
xA: origin + (Math.sin(angle) * pos), size: 4 + (Math.random() * 8),
yA: origin + (Math.cos(angle) * pos), xA: origin + (Math.sin(angle) * pos),
xB: origin + (Math.sin(angle) * (pos + velocity)), yA: origin + (Math.cos(angle) * pos),
yB: origin + (Math.cos(angle) * (pos + velocity)), xB: origin + (Math.sin(angle) * (pos + velocity)),
color: colors[Math.floor(Math.random() * colors.length)] yB: origin + (Math.cos(angle) * (pos + velocity)),
}); color: colors[Math.floor(Math.random() * colors.length)]
});
}
} }
onMounted(() => {
setTimeout(() => {
context.emit('end');
}, 1100);
});
return { return {
particles particles,
zIndex: os.claimZIndex('high'),
}; };
}, },
mounted() {
setTimeout(() => {
this.$emit('end');
}, 1100);
}
}); });
</script> </script>
@ -101,7 +111,6 @@ export default defineComponent({
.vswabwbm { .vswabwbm {
pointer-events: none; pointer-events: none;
position: fixed; position: fixed;
z-index: 1000000;
width: 128px; width: 128px;
height: 128px; height: 128px;

View File

@ -51,14 +51,13 @@
<span v-if="passwordRetypeState == 'not-match'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.passwordNotMatched }}</span> <span v-if="passwordRetypeState == 'not-match'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.passwordNotMatched }}</span>
</template> </template>
</MkInput> </MkInput>
<label v-if="meta.tosUrl" class="_formBlock tou"> <MkSwitch v-if="meta.tosUrl" v-model="ToSAgreement" class="_formBlock tou">
<input v-model="ToSAgreement" type="checkbox">
<I18n :src="$ts.agreeTo"> <I18n :src="$ts.agreeTo">
<template #0> <template #0>
<a :href="meta.tosUrl" class="_link" target="_blank">{{ $ts.tos }}</a> <a :href="meta.tosUrl" class="_link" target="_blank">{{ $ts.tos }}</a>
</template> </template>
</I18n> </I18n>
</label> </MkSwitch>
<captcha v-if="meta.enableHcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" class="_formBlock captcha" provider="hcaptcha" :sitekey="meta.hcaptchaSiteKey"/> <captcha v-if="meta.enableHcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" class="_formBlock captcha" provider="hcaptcha" :sitekey="meta.hcaptchaSiteKey"/>
<captcha v-if="meta.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" class="_formBlock captcha" provider="recaptcha" :sitekey="meta.recaptchaSiteKey"/> <captcha v-if="meta.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" class="_formBlock captcha" provider="recaptcha" :sitekey="meta.recaptchaSiteKey"/>
<MkButton class="_formBlock" type="submit" :disabled="shouldDisableSubmitting" gradate data-cy-signup-submit>{{ $ts.start }}</MkButton> <MkButton class="_formBlock" type="submit" :disabled="shouldDisableSubmitting" gradate data-cy-signup-submit>{{ $ts.start }}</MkButton>
@ -258,11 +257,5 @@ export default defineComponent({
.captcha { .captcha {
margin: 16px 0; margin: 16px 0;
} }
> .tou {
display: block;
margin: 16px 0;
cursor: pointer;
}
} }
</style> </style>

View File

@ -0,0 +1,24 @@
import { Directive } from 'vue';
export default {
mounted(src, binding, vn) {
const getBgColor = (el: HTMLElement) => {
const style = window.getComputedStyle(el);
if (style.backgroundColor && !['rgba(0, 0, 0, 0)', 'rgba(0,0,0,0)', 'transparent'].includes(style.backgroundColor)) {
return style.backgroundColor;
} else {
return el.parentElement ? getBgColor(el.parentElement) : 'transparent';
}
}
const parentBg = getBgColor(src.parentElement);
const myBg = window.getComputedStyle(src).backgroundColor;
if (parentBg === myBg) {
src.style.borderColor = 'var(--divider)';
} else {
src.style.borderColor = myBg;
}
},
} as Directive;

View File

@ -3,7 +3,7 @@ import { App } from 'vue';
import userPreview from './user-preview'; import userPreview from './user-preview';
import size from './size'; import size from './size';
import getSize from './get-size'; import getSize from './get-size';
import particle from './particle'; import ripple from './ripple';
import tooltip from './tooltip'; import tooltip from './tooltip';
import hotkey from './hotkey'; import hotkey from './hotkey';
import appear from './appear'; import appear from './appear';
@ -11,13 +11,14 @@ import anim from './anim';
import stickyContainer from './sticky-container'; import stickyContainer from './sticky-container';
import clickAnime from './click-anime'; import clickAnime from './click-anime';
import panel from './panel'; import panel from './panel';
import adaptiveBorder from './adaptive-border';
export default function(app: App) { export default function(app: App) {
app.directive('userPreview', userPreview); app.directive('userPreview', userPreview);
app.directive('user-preview', userPreview); app.directive('user-preview', userPreview);
app.directive('size', size); app.directive('size', size);
app.directive('get-size', getSize); app.directive('get-size', getSize);
app.directive('particle', particle); app.directive('ripple', ripple);
app.directive('tooltip', tooltip); app.directive('tooltip', tooltip);
app.directive('hotkey', hotkey); app.directive('hotkey', hotkey);
app.directive('appear', appear); app.directive('appear', appear);
@ -25,4 +26,5 @@ export default function(app: App) {
app.directive('click-anime', clickAnime); app.directive('click-anime', clickAnime);
app.directive('sticky-container', stickyContainer); app.directive('sticky-container', stickyContainer);
app.directive('panel', panel); app.directive('panel', panel);
app.directive('adaptive-border', adaptiveBorder);
} }

View File

@ -7,7 +7,7 @@ export default {
if (style.backgroundColor && !['rgba(0, 0, 0, 0)', 'rgba(0,0,0,0)', 'transparent'].includes(style.backgroundColor)) { if (style.backgroundColor && !['rgba(0, 0, 0, 0)', 'rgba(0,0,0,0)', 'transparent'].includes(style.backgroundColor)) {
return style.backgroundColor; return style.backgroundColor;
} else { } else {
return getBgColor(el.parentElement); return el.parentElement ? getBgColor(el.parentElement) : 'transparent';
} }
} }

View File

@ -1,4 +1,4 @@
import Particle from '@/components/particle.vue'; import Ripple from '@/components/ripple.vue';
import { popup } from '@/os'; import { popup } from '@/os';
export default { export default {
@ -9,10 +9,10 @@ export default {
el.addEventListener('click', () => { el.addEventListener('click', () => {
const rect = el.getBoundingClientRect(); const rect = el.getBoundingClientRect();
const x = rect.left + (el.clientWidth / 2); const x = rect.left + (el.offsetWidth / 2);
const y = rect.top + (el.clientHeight / 2); const y = rect.top + (el.offsetHeight / 2);
popup(Particle, { x, y }, {}, 'end'); popup(Ripple, { x, y }, {}, 'end');
}); });
} }
}; };

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="_formRoot"> <div class="_formRoot">
<div v-panel class="rfqxtzch _formBlock"> <div v-adaptive-border class="rfqxtzch _panel _formBlock">
<div class="toggle"> <div class="toggle">
<div class="toggleWrapper"> <div class="toggleWrapper">
<input id="dn" v-model="darkMode" type="checkbox" class="dn"/> <input id="dn" v-model="darkMode" type="checkbox" class="dn"/>

View File

@ -56,6 +56,7 @@ module.exports = {
'object-curly-spacing': ['error', 'always'], 'object-curly-spacing': ['error', 'always'],
'space-infix-ops': ['error'], 'space-infix-ops': ['error'],
'space-before-blocks': ['error', 'always'], 'space-before-blocks': ['error', 'always'],
'@typescript-eslint/no-unnecessary-condition': ['error'],
'@typescript-eslint/no-var-requires': ['warn'], '@typescript-eslint/no-var-requires': ['warn'],
'@typescript-eslint/no-inferrable-types': ['warn'], '@typescript-eslint/no-inferrable-types': ['warn'],
'@typescript-eslint/no-empty-function': ['off'], '@typescript-eslint/no-empty-function': ['off'],