Merge branch 'notification-read-api' into swn
This commit is contained in:
commit
9a78690222
20 changed files with 204 additions and 116 deletions
|
@ -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
|
||||||
|
*/
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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()"
|
||||||
|
|
|
@ -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,14 +64,20 @@ 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'];
|
||||||
|
|
||||||
|
if (props.particle) {
|
||||||
for (let i = 0; i < 12; i++) {
|
for (let i = 0; i < 12; i++) {
|
||||||
const angle = Math.random() * (Math.PI * 2);
|
const angle = Math.random() * (Math.PI * 2);
|
||||||
const pos = Math.random() * 16;
|
const pos = Math.random() * 16;
|
||||||
|
@ -84,16 +91,19 @@ export default defineComponent({
|
||||||
color: colors[Math.floor(Math.random() * colors.length)]
|
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;
|
||||||
|
|
|
@ -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>
|
||||||
|
|
24
packages/client/src/directives/adaptive-border.ts
Normal file
24
packages/client/src/directives/adaptive-border.ts
Normal 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;
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
|
@ -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"/>
|
||||||
|
|
|
@ -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'],
|
||||||
|
|
Loading…
Reference in a new issue