Merge branch 'develop' into swn
This commit is contained in:
@@ -73,6 +73,22 @@ export default defineComponent({
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@keyframes earwiggleleft {
|
||||
from { transform: rotate(37.6deg) skew(30deg); }
|
||||
25% { transform: rotate(10deg) skew(30deg); }
|
||||
50% { transform: rotate(20deg) skew(30deg); }
|
||||
75% { transform: rotate(0deg) skew(30deg); }
|
||||
to { transform: rotate(37.6deg) skew(30deg); }
|
||||
}
|
||||
|
||||
@keyframes earwiggleright {
|
||||
from { transform: rotate(-37.6deg) skew(-30deg); }
|
||||
30% { transform: rotate(-10deg) skew(-30deg); }
|
||||
55% { transform: rotate(-20deg) skew(-30deg); }
|
||||
75% { transform: rotate(0deg) skew(-30deg); }
|
||||
to { transform: rotate(-37.6deg) skew(-30deg); }
|
||||
}
|
||||
|
||||
.eiwwqkts {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
@@ -132,6 +148,16 @@ export default defineComponent({
|
||||
border-radius: 75% 0 75% 75%;
|
||||
transform: rotate(-37.5deg) skew(-30deg);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&:before {
|
||||
animation: earwiggleleft 1s infinite;
|
||||
}
|
||||
|
||||
&:after {
|
||||
animation: earwiggleright 1s infinite;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -99,7 +99,8 @@ export default defineComponent({
|
||||
z-index: 10;
|
||||
position: sticky;
|
||||
top: var(--stickyTop, 0px);
|
||||
background: var(--panel);
|
||||
padding: var(--x-padding);
|
||||
background: var(--x-header, var(--panel));
|
||||
/* TODO panelの半透明バージョンをプログラマティックに作りたい
|
||||
background: var(--X17);
|
||||
-webkit-backdrop-filter: var(--blur, blur(8px));
|
||||
|
@@ -245,7 +245,7 @@ export default defineComponent({
|
||||
font-size: 1em;
|
||||
color: var(--fg);
|
||||
background: var(--panel);
|
||||
border: solid 1px var(--inputBorder);
|
||||
border: solid 0.5px var(--inputBorder);
|
||||
border-radius: 6px;
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
|
@@ -212,7 +212,7 @@ export default defineComponent({
|
||||
font-size: 1em;
|
||||
color: var(--fg);
|
||||
background: var(--panel);
|
||||
border: solid 1px var(--inputBorder);
|
||||
border: solid 0.5px var(--inputBorder);
|
||||
border-radius: 6px;
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
|
@@ -15,7 +15,7 @@ if (localStorage.getItem('accounts') != null) {
|
||||
|
||||
import * as Sentry from '@sentry/browser';
|
||||
import { Integrations } from '@sentry/tracing';
|
||||
import { computed, createApp, watch, markRaw } from 'vue';
|
||||
import { computed, createApp, watch, markRaw, version as vueVersion } from 'vue';
|
||||
import compareVersions from 'compare-versions';
|
||||
|
||||
import widgets from '@client/widgets';
|
||||
@@ -49,6 +49,8 @@ window.onunhandledrejection = null;
|
||||
if (_DEV_) {
|
||||
console.warn('Development mode!!!');
|
||||
|
||||
console.info(`vue ${vueVersion}`);
|
||||
|
||||
(window as any).$i = $i;
|
||||
(window as any).$store = defaultStore;
|
||||
|
||||
|
134
src/client/pages/emojis.category.vue
Normal file
134
src/client/pages/emojis.category.vue
Normal file
@@ -0,0 +1,134 @@
|
||||
<template>
|
||||
<div class="driuhtrh">
|
||||
<div class="query">
|
||||
<MkInput v-model="q" class="_inputNoTopMargin _inputNoBottomMargin" :placeholder="$ts.search">
|
||||
<template #prefix><i class="fas fa-search"></i></template>
|
||||
</MkInput>
|
||||
|
||||
<div class="tags">
|
||||
<span class="tag _button" v-for="tag in tags" :class="{ active: selectedTags.has(tag) }" @click="toggleTag(tag)">{{ tag }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<MkFolder class="emojis" v-if="searchEmojis">
|
||||
<template #header>{{ $ts.searchResult }}</template>
|
||||
<div class="zuvgdzyt">
|
||||
<XEmoji v-for="emoji in searchEmojis" :key="emoji.name" class="emoji" :emoji="emoji"/>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder class="emojis" v-for="category in customEmojiCategories" :key="category">
|
||||
<template #header>{{ category || $ts.other }}</template>
|
||||
<div class="zuvgdzyt">
|
||||
<XEmoji v-for="emoji in customEmojis.filter(e => e.category === category)" :key="emoji.name" class="emoji" :emoji="emoji"/>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed } from 'vue';
|
||||
import MkButton from '@client/components/ui/button.vue';
|
||||
import MkInput from '@client/components/ui/input.vue';
|
||||
import MkSelect from '@client/components/ui/select.vue';
|
||||
import MkFolder from '@client/components/ui/folder.vue';
|
||||
import MkTab from '@client/components/tab.vue';
|
||||
import * as os from '@client/os';
|
||||
import * as symbols from '@client/symbols';
|
||||
import { emojiCategories, emojiTags } from '@client/instance';
|
||||
import XEmoji from './emojis.emoji.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MkButton,
|
||||
MkInput,
|
||||
MkSelect,
|
||||
MkFolder,
|
||||
MkTab,
|
||||
XEmoji,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
q: '',
|
||||
customEmojiCategories: emojiCategories,
|
||||
customEmojis: this.$instance.emojis,
|
||||
tags: emojiTags,
|
||||
selectedTags: new Set(),
|
||||
searchEmojis: null,
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
q() { this.search(); },
|
||||
selectedTags: {
|
||||
handler() {
|
||||
this.search();
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
search() {
|
||||
if ((this.q === '' || this.q == null) && this.selectedTags.size === 0) {
|
||||
this.searchEmojis = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.selectedTags.size === 0) {
|
||||
this.searchEmojis = this.customEmojis.filter(e => e.name.includes(this.q) || e.aliases.includes(this.q));
|
||||
} else {
|
||||
this.searchEmojis = this.customEmojis.filter(e => (e.name.includes(this.q) || e.aliases.includes(this.q)) && [...this.selectedTags].every(t => e.aliases.includes(t)));
|
||||
}
|
||||
},
|
||||
|
||||
toggleTag(tag) {
|
||||
if (this.selectedTags.has(tag)) {
|
||||
this.selectedTags.delete(tag);
|
||||
} else {
|
||||
this.selectedTags.add(tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.driuhtrh {
|
||||
background: var(--bg);
|
||||
|
||||
> .query {
|
||||
background: var(--bg);
|
||||
padding: 16px;
|
||||
|
||||
> .tags {
|
||||
> .tag {
|
||||
display: inline-block;
|
||||
margin: 8px 8px 0 0;
|
||||
padding: 4px 8px;
|
||||
font-size: 0.9em;
|
||||
background: var(--panel);
|
||||
border: solid 0.5px var(--divider);
|
||||
border-radius: 5px;
|
||||
|
||||
&.active {
|
||||
border-color: var(--accent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .emojis {
|
||||
--x-header: var(--bg);
|
||||
--x-padding: 0 16px;
|
||||
|
||||
.zuvgdzyt {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
|
||||
grid-gap: 12px;
|
||||
margin: 0 var(--margin) var(--margin) var(--margin);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
92
src/client/pages/emojis.emoji.vue
Normal file
92
src/client/pages/emojis.emoji.vue
Normal file
@@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<button class="zuvgdzyu _button" @click="menu">
|
||||
<img :src="emoji.url" class="img" :alt="emoji.name"/>
|
||||
<div class="body">
|
||||
<div class="name _monospace">{{ emoji.name }}</div>
|
||||
<div class="info">{{ emoji.aliases.join(' ') }}</div>
|
||||
</div>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import * as os from '@client/os';
|
||||
import copyToClipboard from '@client/scripts/copy-to-clipboard';
|
||||
import VanillaTilt from 'vanilla-tilt';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
emoji: {
|
||||
type: Object,
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
VanillaTilt.init(this.$el, {
|
||||
reverse: true,
|
||||
gyroscope: false,
|
||||
scale: 1.1,
|
||||
speed: 500,
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
menu(ev) {
|
||||
os.popupMenu([{
|
||||
type: 'label',
|
||||
text: ':' + this.emoji.name + ':',
|
||||
}, {
|
||||
text: this.$ts.copy,
|
||||
icon: 'fas fa-copy',
|
||||
action: () => {
|
||||
copyToClipboard(`:${this.emoji.name}:`);
|
||||
os.success();
|
||||
}
|
||||
}], ev.currentTarget || ev.target);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.zuvgdzyu {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
background: var(--panel);
|
||||
border-radius: 8px;
|
||||
transform-style: preserve-3d;
|
||||
transform: perspective(1000px);
|
||||
|
||||
&:hover {
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
> .img {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
transform: translateZ(20px);
|
||||
}
|
||||
|
||||
> .body {
|
||||
padding: 0 0 0 8px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
transform: translateZ(10px);
|
||||
|
||||
> .name {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
> .info {
|
||||
opacity: 0.5;
|
||||
font-size: 0.9em;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -1,151 +1,30 @@
|
||||
<template>
|
||||
<div class="driuhtrh">
|
||||
<div class="query">
|
||||
<MkInput v-model="q" class="_inputNoTopMargin _inputNoBottomMargin" :placeholder="$ts.search">
|
||||
<template #prefix><i class="fas fa-search"></i></template>
|
||||
</MkInput>
|
||||
</div>
|
||||
|
||||
<div class="emojis">
|
||||
<MkFolder v-if="searchEmojis">
|
||||
<template #header>{{ $ts.searchResult }}</template>
|
||||
<div class="zuvgdzyt">
|
||||
<button v-for="emoji in searchEmojis" :key="emoji.name" class="emoji _button" @click="menu(emoji, $event)">
|
||||
<img :src="emoji.url" class="img" :alt="emoji.name"/>
|
||||
<div class="body">
|
||||
<div class="name _monospace">{{ emoji.name }}</div>
|
||||
<div class="info">{{ emoji.aliases.join(' ') }}</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</MkFolder>
|
||||
<MkFolder v-for="category in customEmojiCategories" :key="category">
|
||||
<template #header>{{ category || $ts.other }}</template>
|
||||
<div class="zuvgdzyt">
|
||||
<button v-for="emoji in customEmojis.filter(e => e.category === category)" :key="emoji.name" class="emoji _button" @click="menu(emoji, $event)">
|
||||
<img :src="emoji.url" class="img" :alt="emoji.name"/>
|
||||
<div class="body">
|
||||
<div class="name _monospace">{{ emoji.name }}</div>
|
||||
<div class="info">{{ emoji.aliases.join(' ') }}</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</div>
|
||||
</div>
|
||||
<XCategory v-if="tab === 'category'"/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import MkButton from '@client/components/ui/button.vue';
|
||||
import MkInput from '@client/components/ui/input.vue';
|
||||
import MkSelect from '@client/components/ui/select.vue';
|
||||
import MkFolder from '@client/components/ui/folder.vue';
|
||||
import { defineComponent, computed } from 'vue';
|
||||
import * as os from '@client/os';
|
||||
import * as symbols from '@client/symbols';
|
||||
import { emojiCategories } from '@client/instance';
|
||||
import copyToClipboard from '@client/scripts/copy-to-clipboard';
|
||||
import XCategory from './emojis.category.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MkButton,
|
||||
MkInput,
|
||||
MkSelect,
|
||||
MkFolder,
|
||||
XCategory,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
[symbols.PAGE_INFO]: {
|
||||
[symbols.PAGE_INFO]: computed(() => ({
|
||||
title: this.$ts.customEmojis,
|
||||
icon: 'fas fa-laugh'
|
||||
},
|
||||
q: '',
|
||||
customEmojiCategories: emojiCategories,
|
||||
customEmojis: this.$instance.emojis,
|
||||
searchEmojis: null,
|
||||
icon: 'fas fa-laugh',
|
||||
bg: 'var(--bg)',
|
||||
})),
|
||||
tab: 'category',
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
q() {
|
||||
if (this.q === '' || this.q == null) {
|
||||
this.searchEmojis = null;
|
||||
return;
|
||||
}
|
||||
|
||||
this.searchEmojis = this.customEmojis.filter(e => e.name.includes(this.q) || e.aliases.includes(this.q));
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
menu(emoji, ev) {
|
||||
os.popupMenu([{
|
||||
type: 'label',
|
||||
text: ':' + emoji.name + ':',
|
||||
}, {
|
||||
text: this.$ts.copy,
|
||||
icon: 'fas fa-copy',
|
||||
action: () => {
|
||||
copyToClipboard(`:${emoji.name}:`);
|
||||
os.success();
|
||||
}
|
||||
}], ev.currentTarget || ev.target);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.driuhtrh {
|
||||
> .query {
|
||||
background: var(--bg);
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
> .emojis {
|
||||
.zuvgdzyt {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
|
||||
grid-gap: 12px;
|
||||
margin: 0 var(--margin) var(--margin) var(--margin);
|
||||
|
||||
> .emoji {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
border: solid 1px var(--divider);
|
||||
border-radius: 8px;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
> .img {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
}
|
||||
|
||||
> .body {
|
||||
padding: 0 0 0 8px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
|
||||
> .name {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
> .info {
|
||||
opacity: 0.5;
|
||||
font-size: 0.9em;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -22,7 +22,8 @@ export default defineComponent({
|
||||
return {
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: this.$ts.favorites,
|
||||
icon: 'fas fa-star'
|
||||
icon: 'fas fa-star',
|
||||
bg: 'var(--bg)',
|
||||
},
|
||||
pagination: {
|
||||
endpoint: 'i/favorites',
|
||||
|
@@ -1,37 +1,39 @@
|
||||
<template>
|
||||
<div class="fcuexfpr _root">
|
||||
<transition name="fade" mode="out-in">
|
||||
<div v-if="note" class="note">
|
||||
<div class="_gap" v-if="showNext">
|
||||
<XNotes class="_content" :pagination="next" :no-gap="true"/>
|
||||
</div>
|
||||
|
||||
<div class="main _gap">
|
||||
<MkButton v-if="!showNext && hasNext" class="load next" @click="showNext = true"><i class="fas fa-chevron-up"></i></MkButton>
|
||||
<div class="note _gap">
|
||||
<MkRemoteCaution v-if="note.user.host != null" :href="note.url || note.uri" class="_isolated"/>
|
||||
<XNoteDetailed v-model:note="note" :key="note.id" class="_isolated note"/>
|
||||
<div class="fcuexfpr">
|
||||
<div class="_root">
|
||||
<transition name="fade" mode="out-in">
|
||||
<div v-if="note" class="note">
|
||||
<div class="_gap" v-if="showNext">
|
||||
<XNotes class="_content" :pagination="next" :no-gap="true"/>
|
||||
</div>
|
||||
<div class="_content clips _gap" v-if="clips && clips.length > 0">
|
||||
<div class="title">{{ $ts.clip }}</div>
|
||||
<MkA v-for="item in clips" :key="item.id" :to="`/clips/${item.id}`" class="item _panel _gap">
|
||||
<b>{{ item.name }}</b>
|
||||
<div v-if="item.description" class="description">{{ item.description }}</div>
|
||||
<div class="user">
|
||||
<MkAvatar :user="item.user" class="avatar" :show-indicator="true"/> <MkUserName :user="item.user" :nowrap="false"/>
|
||||
</div>
|
||||
</MkA>
|
||||
</div>
|
||||
<MkButton v-if="!showPrev && hasPrev" class="load prev" @click="showPrev = true"><i class="fas fa-chevron-down"></i></MkButton>
|
||||
</div>
|
||||
|
||||
<div class="_gap" v-if="showPrev">
|
||||
<XNotes class="_content" :pagination="prev" :no-gap="true"/>
|
||||
<div class="main _gap">
|
||||
<MkButton v-if="!showNext && hasNext" class="load next" @click="showNext = true"><i class="fas fa-chevron-up"></i></MkButton>
|
||||
<div class="note _gap">
|
||||
<MkRemoteCaution v-if="note.user.host != null" :href="note.url || note.uri" class="_isolated"/>
|
||||
<XNoteDetailed v-model:note="note" :key="note.id" class="_isolated note"/>
|
||||
</div>
|
||||
<div class="_content clips _gap" v-if="clips && clips.length > 0">
|
||||
<div class="title">{{ $ts.clip }}</div>
|
||||
<MkA v-for="item in clips" :key="item.id" :to="`/clips/${item.id}`" class="item _panel _gap">
|
||||
<b>{{ item.name }}</b>
|
||||
<div v-if="item.description" class="description">{{ item.description }}</div>
|
||||
<div class="user">
|
||||
<MkAvatar :user="item.user" class="avatar" :show-indicator="true"/> <MkUserName :user="item.user" :nowrap="false"/>
|
||||
</div>
|
||||
</MkA>
|
||||
</div>
|
||||
<MkButton v-if="!showPrev && hasPrev" class="load prev" @click="showPrev = true"><i class="fas fa-chevron-down"></i></MkButton>
|
||||
</div>
|
||||
|
||||
<div class="_gap" v-if="showPrev">
|
||||
<XNotes class="_content" :pagination="prev" :no-gap="true"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<MkError v-else-if="error" @retry="fetch()"/>
|
||||
<MkLoading v-else/>
|
||||
</transition>
|
||||
<MkError v-else-if="error" @retry="fetch()"/>
|
||||
<MkLoading v-else/>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -63,12 +65,14 @@ export default defineComponent({
|
||||
return {
|
||||
[symbols.PAGE_INFO]: computed(() => this.note ? {
|
||||
title: this.$ts.note,
|
||||
subtitle: new Date(this.note.createdAt).toLocaleString(),
|
||||
avatar: this.note.user,
|
||||
path: `/notes/${this.note.id}`,
|
||||
share: {
|
||||
title: this.$t('noteOf', { user: this.note.user.name }),
|
||||
text: this.note.text,
|
||||
},
|
||||
bg: 'var(--bg)',
|
||||
} : null),
|
||||
note: null,
|
||||
clips: null,
|
||||
@@ -149,52 +153,54 @@ export default defineComponent({
|
||||
.fcuexfpr {
|
||||
background: var(--bg);
|
||||
|
||||
> .note {
|
||||
> .main {
|
||||
> .load {
|
||||
min-width: 0;
|
||||
margin: 0 auto;
|
||||
border-radius: 999px;
|
||||
> ._root {
|
||||
> .note {
|
||||
> .main {
|
||||
> .load {
|
||||
min-width: 0;
|
||||
margin: 0 auto;
|
||||
border-radius: 999px;
|
||||
|
||||
&.next {
|
||||
margin-bottom: var(--margin);
|
||||
}
|
||||
|
||||
&.prev {
|
||||
margin-top: var(--margin);
|
||||
}
|
||||
}
|
||||
|
||||
> .note {
|
||||
> .note {
|
||||
border-radius: var(--radius);
|
||||
background: var(--panel);
|
||||
}
|
||||
}
|
||||
|
||||
> .clips {
|
||||
> .title {
|
||||
font-weight: bold;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
> .item {
|
||||
display: block;
|
||||
padding: 16px;
|
||||
|
||||
> .description {
|
||||
padding: 8px 0;
|
||||
&.next {
|
||||
margin-bottom: var(--margin);
|
||||
}
|
||||
|
||||
> .user {
|
||||
$height: 32px;
|
||||
padding-top: 16px;
|
||||
border-top: solid 0.5px var(--divider);
|
||||
line-height: $height;
|
||||
&.prev {
|
||||
margin-top: var(--margin);
|
||||
}
|
||||
}
|
||||
|
||||
> .avatar {
|
||||
width: $height;
|
||||
height: $height;
|
||||
> .note {
|
||||
> .note {
|
||||
border-radius: var(--radius);
|
||||
background: var(--panel);
|
||||
}
|
||||
}
|
||||
|
||||
> .clips {
|
||||
> .title {
|
||||
font-weight: bold;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
> .item {
|
||||
display: block;
|
||||
padding: 16px;
|
||||
|
||||
> .description {
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
> .user {
|
||||
$height: 32px;
|
||||
padding-top: 16px;
|
||||
border-top: solid 0.5px var(--divider);
|
||||
line-height: $height;
|
||||
|
||||
> .avatar {
|
||||
width: $height;
|
||||
height: $height;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -21,6 +21,7 @@ export default defineComponent({
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: this.$ts.notifications,
|
||||
icon: 'fas fa-bell',
|
||||
bg: 'var(--bg)',
|
||||
actions: [{
|
||||
text: this.$ts.markAllAsRead,
|
||||
icon: 'fas fa-check',
|
||||
|
@@ -86,7 +86,8 @@ export default defineComponent({
|
||||
setup(props, context) {
|
||||
const indexInfo = {
|
||||
title: i18n.locale.settings,
|
||||
icon: 'fas fa-cog'
|
||||
icon: 'fas fa-cog',
|
||||
bg: 'var(--bg)',
|
||||
};
|
||||
const INFO = ref(indexInfo);
|
||||
const page = ref(props.initialPage);
|
||||
|
@@ -26,7 +26,7 @@
|
||||
<FormLink to="/bios" behavior="browser"><template #icon><i class="fas fa-door-open"></i></template>BIOS</FormLink>
|
||||
<FormLink to="/cli" behavior="browser"><template #icon><i class="fas fa-door-open"></i></template>CLI</FormLink>
|
||||
|
||||
<FormLink to="./delete-account"><template #icon><i class="fas fa-exclamation-triangle"></i></template>{{ $ts.closeAccount }}</FormLink>
|
||||
<FormLink to="/settings/delete-account"><template #icon><i class="fas fa-exclamation-triangle"></i></template>{{ $ts.closeAccount }}</FormLink>
|
||||
</FormBase>
|
||||
</template>
|
||||
|
||||
|
@@ -1,25 +1,10 @@
|
||||
<template>
|
||||
<div class="cmuxhskf" v-hotkey.global="keymap" v-size="{ min: [800] }">
|
||||
<XTutorial v-if="$store.reactiveState.tutorial.value != -1" class="tutorial _block _isolated"/>
|
||||
<XPostForm v-if="$store.reactiveState.showFixedPostForm.value" class="post-form _block _isolated" fixed/>
|
||||
<div class="tabs">
|
||||
<div class="left">
|
||||
<button class="_button tab" @click="() => { src = 'home'; saveSrc(); }" :class="{ active: src === 'home' }" v-tooltip="$ts._timelines.home"><i class="fas fa-home"></i></button>
|
||||
<button class="_button tab" @click="() => { src = 'local'; saveSrc(); }" :class="{ active: src === 'local' }" v-tooltip="$ts._timelines.local" v-if="isLocalTimelineAvailable"><i class="fas fa-comments"></i></button>
|
||||
<button class="_button tab" @click="() => { src = 'social'; saveSrc(); }" :class="{ active: src === 'social' }" v-tooltip="$ts._timelines.social" v-if="isLocalTimelineAvailable"><i class="fas fa-share-alt"></i></button>
|
||||
<button class="_button tab" @click="() => { src = 'global'; saveSrc(); }" :class="{ active: src === 'global' }" v-tooltip="$ts._timelines.global" v-if="isGlobalTimelineAvailable"><i class="fas fa-globe"></i></button>
|
||||
<span class="divider"></span>
|
||||
<button class="_button tab" @click="() => { src = 'mentions'; saveSrc(); }" :class="{ active: src === 'mentions' }" v-tooltip="$ts.mentions"><i class="fas fa-at"></i><i v-if="$i.hasUnreadMentions" class="fas fa-circle i"></i></button>
|
||||
<button class="_button tab" @click="() => { src = 'directs'; saveSrc(); }" :class="{ active: src === 'directs' }" v-tooltip="$ts.directNotes"><i class="fas fa-envelope"></i><i v-if="$i.hasUnreadSpecifiedNotes" class="fas fa-circle i"></i></button>
|
||||
</div>
|
||||
<div class="right">
|
||||
<button class="_button tab" @click="chooseChannel" :class="{ active: src === 'channel' }" v-tooltip="$ts.channel"><i class="fas fa-satellite-dish"></i><i v-if="$i.hasUnreadChannel" class="fas fa-circle i"></i></button>
|
||||
<button class="_button tab" @click="chooseAntenna" :class="{ active: src === 'antenna' }" v-tooltip="$ts.antennas"><i class="fas fa-satellite"></i><i v-if="$i.hasUnreadAntenna" class="fas fa-circle i"></i></button>
|
||||
<button class="_button tab" @click="chooseList" :class="{ active: src === 'list' }" v-tooltip="$ts.lists"><i class="fas fa-list-ul"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<XTutorial v-if="$store.reactiveState.tutorial.value != -1" class="tutorial _block"/>
|
||||
<XPostForm v-if="$store.reactiveState.showFixedPostForm.value" class="post-form _block" fixed/>
|
||||
|
||||
<div class="new" v-if="queue > 0"><button class="_buttonPrimary" @click="top()">{{ $ts.newNoteRecived }}</button></div>
|
||||
<div class="tl">
|
||||
<div class="tl _block">
|
||||
<XTimeline ref="tl" class="tl"
|
||||
:key="src === 'list' ? `list:${list.id}` : src === 'antenna' ? `antenna:${antenna.id}` : src === 'channel' ? `channel:${channel.id}` : src"
|
||||
:src="src"
|
||||
@@ -63,12 +48,37 @@ export default defineComponent({
|
||||
queue: 0,
|
||||
[symbols.PAGE_INFO]: computed(() => ({
|
||||
title: this.$ts.timeline,
|
||||
subtitle: this.src === 'local' ? this.$ts._timelines.local : this.src === 'social' ? this.$ts._timelines.social : this.src === 'global' ? this.$ts._timelines.global : this.$ts._timelines.home,
|
||||
icon: this.src === 'local' ? 'fas fa-comments' : this.src === 'social' ? 'fas fa-share-alt' : this.src === 'global' ? 'fas fa-globe' : 'fas fa-home',
|
||||
bg: 'var(--bg)',
|
||||
actions: [{
|
||||
icon: 'fas fa-calendar-alt',
|
||||
text: this.$ts.jumpToSpecifiedDate,
|
||||
handler: this.timetravel
|
||||
}],
|
||||
tabs: [{
|
||||
active: this.src === 'home',
|
||||
title: this.$ts._timelines.home,
|
||||
icon: 'fas fa-home',
|
||||
iconOnly: true,
|
||||
onClick: () => { this.src = 'home'; this.saveSrc(); },
|
||||
}, {
|
||||
active: this.src === 'local',
|
||||
title: this.$ts._timelines.local,
|
||||
icon: 'fas fa-comments',
|
||||
iconOnly: true,
|
||||
onClick: () => { this.src = 'local'; this.saveSrc(); },
|
||||
}, {
|
||||
active: this.src === 'social',
|
||||
title: this.$ts._timelines.social,
|
||||
icon: 'fas fa-share-alt',
|
||||
iconOnly: true,
|
||||
onClick: () => { this.src = 'social'; this.saveSrc(); },
|
||||
}, {
|
||||
active: this.src === 'global',
|
||||
title: this.$ts._timelines.global,
|
||||
icon: 'fas fa-globe',
|
||||
iconOnly: true,
|
||||
onClick: () => { this.src = 'global'; this.saveSrc(); },
|
||||
}]
|
||||
})),
|
||||
};
|
||||
@@ -213,6 +223,8 @@ export default defineComponent({
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cmuxhskf {
|
||||
padding: var(--margin);
|
||||
|
||||
> .new {
|
||||
position: sticky;
|
||||
top: calc(var(--stickyTop, 0px) + 16px);
|
||||
@@ -227,79 +239,15 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
> .tabs {
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
padding: 0 8px;
|
||||
white-space: nowrap;
|
||||
overflow: auto;
|
||||
border-bottom: solid 0.5px var(--divider);
|
||||
|
||||
// 影の都合上
|
||||
position: relative;
|
||||
|
||||
> .right {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
> .left, > .right {
|
||||
> .tab {
|
||||
position: relative;
|
||||
height: 50px;
|
||||
padding: 0 12px;
|
||||
|
||||
&:hover {
|
||||
color: var(--fgHighlighted);
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: var(--fgHighlighted);
|
||||
|
||||
&:after {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background: var(--accent);
|
||||
}
|
||||
}
|
||||
|
||||
> .i {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
right: 8px;
|
||||
color: var(--indicator);
|
||||
font-size: 8px;
|
||||
animation: blink 1s infinite;
|
||||
}
|
||||
}
|
||||
|
||||
> .divider {
|
||||
display: inline-block;
|
||||
width: 1px;
|
||||
height: 28px;
|
||||
vertical-align: middle;
|
||||
margin: 0 8px;
|
||||
background: var(--divider);
|
||||
}
|
||||
}
|
||||
> .tl {
|
||||
background: var(--bg);
|
||||
border-radius: var(--radius);
|
||||
overflow: clip;
|
||||
}
|
||||
|
||||
&.min-width_800px {
|
||||
> .tl {
|
||||
background: var(--bg);
|
||||
padding: 32px 0;
|
||||
|
||||
> .tl {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -60,23 +60,9 @@
|
||||
<XPhotos :user="user" :key="user.id" class="_gap"/>
|
||||
</div>
|
||||
<div class="main">
|
||||
<div class="nav _gap">
|
||||
<MkA :to="userPage(user)" :class="{ active: page === 'index' }" class="link">
|
||||
<i class="fas fa-comment-alt icon"></i>
|
||||
<span>{{ $ts.notes }}</span>
|
||||
</MkA>
|
||||
<MkA :to="userPage(user, 'clips')" :class="{ active: page === 'clips' }" class="link">
|
||||
<i class="fas fa-paperclip icon"></i>
|
||||
<span>{{ $ts.clips }}</span>
|
||||
</MkA>
|
||||
<MkA :to="userPage(user, 'pages')" :class="{ active: page === 'pages' }" class="link">
|
||||
<i class="fas fa-file-alt icon"></i>
|
||||
<span>{{ $ts.pages }}</span>
|
||||
</MkA>
|
||||
<div class="actions">
|
||||
<button @click="menu" class="menu _button"><i class="fas fa-ellipsis-h"></i></button>
|
||||
<MkFollowButton v-if="!$i || $i.id != user.id" :user="user" :inline="true" :transparent="false" :full="true" large class="koudoku"/>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button @click="menu" class="menu _button"><i class="fas fa-ellipsis-h"></i></button>
|
||||
<MkFollowButton v-if="!$i || $i.id != user.id" :user="user" :inline="true" :transparent="false" :full="true" large class="koudoku"/>
|
||||
</div>
|
||||
<template v-if="page === 'index'">
|
||||
<div v-if="user.pinnedNotes.length > 0" class="_gap">
|
||||
@@ -178,25 +164,6 @@
|
||||
</div>
|
||||
|
||||
<div class="contents">
|
||||
<div class="nav _gap">
|
||||
<MkA :to="userPage(user)" :class="{ active: page === 'index' }" class="link" v-click-anime>
|
||||
<i class="fas fa-comment-alt icon"></i>
|
||||
<span>{{ $ts.notes }}</span>
|
||||
</MkA>
|
||||
<MkA :to="userPage(user, 'clips')" :class="{ active: page === 'clips' }" class="link" v-click-anime>
|
||||
<i class="fas fa-paperclip icon"></i>
|
||||
<span>{{ $ts.clips }}</span>
|
||||
</MkA>
|
||||
<MkA :to="userPage(user, 'pages')" :class="{ active: page === 'pages' }" class="link" v-click-anime>
|
||||
<i class="fas fa-file-alt icon"></i>
|
||||
<span>{{ $ts.pages }}</span>
|
||||
</MkA>
|
||||
<MkA :to="userPage(user, 'gallery')" :class="{ active: page === 'gallery' }" class="link" v-click-anime>
|
||||
<i class="fas fa-icons icon"></i>
|
||||
<span>{{ $ts.gallery }}</span>
|
||||
</MkA>
|
||||
</div>
|
||||
|
||||
<template v-if="page === 'index'">
|
||||
<div>
|
||||
<div v-if="user.pinnedNotes.length > 0" class="_gap">
|
||||
@@ -283,6 +250,27 @@ export default defineComponent({
|
||||
share: {
|
||||
title: this.user.name,
|
||||
},
|
||||
bg: 'var(--bg)',
|
||||
tabs: [{
|
||||
active: this.page === 'index',
|
||||
title: this.$ts.overview,
|
||||
icon: 'fas fa-home',
|
||||
}, {
|
||||
active: this.page === 'clips',
|
||||
title: this.$ts.clips,
|
||||
icon: 'fas fa-paperclip',
|
||||
onClick: () => { this.page = 'clips'; },
|
||||
}, {
|
||||
active: this.page === 'pages',
|
||||
title: this.$ts.pages,
|
||||
icon: 'fas fa-file-alt',
|
||||
onClick: () => { this.page = 'pages'; },
|
||||
}, {
|
||||
active: this.page === 'gallery',
|
||||
title: this.$ts.gallery,
|
||||
icon: 'fas fa-icons',
|
||||
onClick: () => { this.page = 'gallery'; },
|
||||
}]
|
||||
} : null),
|
||||
user: null,
|
||||
error: null,
|
||||
@@ -314,7 +302,7 @@ export default defineComponent({
|
||||
|
||||
mounted() {
|
||||
window.requestAnimationFrame(this.parallaxLoop);
|
||||
this.narrow = this.$el.clientWidth < 1000;
|
||||
this.narrow = true//this.$el.clientWidth < 1000;
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
@@ -772,37 +760,6 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
> .contents {
|
||||
> .nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 90%;
|
||||
|
||||
> .link {
|
||||
flex: 1;
|
||||
display: inline-block;
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
border-bottom: solid 3px transparent;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: var(--accent);
|
||||
border-bottom-color: var(--accent);
|
||||
}
|
||||
|
||||
&:not(.active):hover {
|
||||
color: var(--fgHighlighted);
|
||||
}
|
||||
|
||||
> .icon {
|
||||
margin-right: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .content {
|
||||
margin-bottom: var(--margin);
|
||||
}
|
||||
|
@@ -245,7 +245,6 @@ hr {
|
||||
._panel {
|
||||
background: var(--panel);
|
||||
border-radius: var(--radius);
|
||||
border: var(--panelBorder);
|
||||
overflow: clip;
|
||||
}
|
||||
|
||||
|
@@ -36,7 +36,7 @@
|
||||
navFg: '@fg',
|
||||
navHoverFg: ':lighten<17<@fg',
|
||||
navActive: '@accent',
|
||||
navIndicator: '@accent',
|
||||
navIndicator: '@indicator',
|
||||
link: '#44a4c1',
|
||||
hashtag: '#ff9156',
|
||||
mention: '@accent',
|
||||
|
@@ -36,7 +36,7 @@
|
||||
navFg: '@fg',
|
||||
navHoverFg: ':darken<17<@fg',
|
||||
navActive: '@accent',
|
||||
navIndicator: '@accent',
|
||||
navIndicator: '@indicator',
|
||||
link: '#44a4c1',
|
||||
hashtag: '#ff9156',
|
||||
mention: '@accent',
|
||||
|
@@ -1,25 +1,35 @@
|
||||
<template>
|
||||
<div class="fdidabkb" :class="{ center }" :style="`--height:${height};`" :key="key">
|
||||
<div class="fdidabkb" :class="{ slim: titleOnly || narrow }" :style="`--height:${height};`" :key="key">
|
||||
<transition :name="$store.state.animation ? 'header' : ''" mode="out-in" appear>
|
||||
<div class="buttons left" v-if="backButton">
|
||||
<button class="_button button back" @click.stop="$emit('back')" @touchstart="preventDrag" v-tooltip="$ts.goBack"><i class="fas fa-chevron-left"></i></button>
|
||||
</div>
|
||||
</transition>
|
||||
<template v-if="info">
|
||||
<div class="titleContainer">
|
||||
<div class="titleContainer" @click="showTabsPopup">
|
||||
<i v-if="info.icon" class="icon" :class="info.icon"></i>
|
||||
<MkAvatar v-else-if="info.avatar" class="avatar" :user="info.avatar" :disable-preview="true" :show-indicator="true"/>
|
||||
|
||||
<div class="title">
|
||||
<MkUserName v-if="info.userName" :user="info.userName" :nowrap="false" class="title"/>
|
||||
<div v-else-if="info.title" class="title">{{ info.title }}</div>
|
||||
<div class="subtitle" v-if="info.subtitle">
|
||||
<div class="subtitle" v-if="!narrow && info.subtitle">
|
||||
{{ info.subtitle }}
|
||||
</div>
|
||||
<div class="subtitle activeTab" v-if="narrow && hasTabs">
|
||||
{{ info.tabs.find(tab => tab.active)?.title }}
|
||||
<i class="chevron fas fa-chevron-down"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tabs" v-if="!narrow">
|
||||
<button class="tab _button" v-for="tab in info.tabs" :class="{ active: tab.active }" @click="tab.onClick" v-tooltip="tab.title">
|
||||
<i v-if="tab.icon" class="icon" :class="tab.icon"></i>
|
||||
<span v-if="!tab.iconOnly" class="title">{{ tab.title }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="buttons right">
|
||||
<template v-if="info.actions && showActions">
|
||||
<template v-if="info.actions && !narrow">
|
||||
<button v-for="action in info.actions" class="_button button" :class="{ highlighted: action.highlighted }" @click.stop="action.handler" @touchstart="preventDrag" v-tooltip="action.text"><i :class="action.icon"></i></button>
|
||||
</template>
|
||||
<button v-if="shouldShowMenu" class="_button button" @click.stop="showMenu" @touchstart="preventDrag" v-tooltip="$ts.menu"><i class="fas fa-ellipsis-h"></i></button>
|
||||
@@ -52,24 +62,28 @@ export default defineComponent({
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
center: {
|
||||
titleOnly: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
showActions: false,
|
||||
narrow: false,
|
||||
height: 0,
|
||||
key: 0,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
hasTabs(): boolean {
|
||||
return this.info.tabs && this.info.tabs.length > 0;
|
||||
},
|
||||
|
||||
shouldShowMenu() {
|
||||
if (this.info.actions != null && !this.showActions) return true;
|
||||
if (this.info.actions != null && this.narrow) return true;
|
||||
if (this.info.menu != null) return true;
|
||||
if (this.info.share != null) return true;
|
||||
if (this.menu != null) return true;
|
||||
@@ -85,10 +99,10 @@ export default defineComponent({
|
||||
|
||||
mounted() {
|
||||
this.height = this.$el.parentElement.offsetHeight + 'px';
|
||||
this.showActions = this.$el.parentElement.offsetWidth >= 500;
|
||||
this.narrow = this.titleOnly || this.$el.parentElement.offsetWidth < 500;
|
||||
new ResizeObserver((entries, observer) => {
|
||||
this.height = this.$el.parentElement.offsetHeight + 'px';
|
||||
this.showActions = this.$el.parentElement.offsetWidth >= 500;
|
||||
this.narrow = this.titleOnly || this.$el.parentElement.offsetWidth < 500;
|
||||
}).observe(this.$el);
|
||||
},
|
||||
|
||||
@@ -102,7 +116,7 @@ export default defineComponent({
|
||||
|
||||
showMenu(ev) {
|
||||
let menu = this.info.menu ? this.info.menu() : [];
|
||||
if (!this.showActions && this.info.actions) {
|
||||
if (this.narrow && this.info.actions) {
|
||||
menu = [...this.info.actions.map(x => ({
|
||||
text: x.text,
|
||||
icon: x.icon,
|
||||
@@ -124,6 +138,18 @@ export default defineComponent({
|
||||
popupMenu(menu, ev.currentTarget || ev.target);
|
||||
},
|
||||
|
||||
showTabsPopup(ev) {
|
||||
if (!this.hasTabs) return;
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
const menu = this.info.tabs.map(tab => ({
|
||||
text: tab.title,
|
||||
icon: tab.icon,
|
||||
action: tab.onClick,
|
||||
}));
|
||||
popupMenu(menu, ev.currentTarget || ev.target);
|
||||
},
|
||||
|
||||
preventDrag(ev) {
|
||||
ev.stopPropagation();
|
||||
}
|
||||
@@ -135,7 +161,7 @@ export default defineComponent({
|
||||
.fdidabkb {
|
||||
display: flex;
|
||||
|
||||
&.center {
|
||||
&.slim {
|
||||
text-align: center;
|
||||
|
||||
> .titleContainer {
|
||||
@@ -190,6 +216,7 @@ export default defineComponent({
|
||||
overflow: auto;
|
||||
white-space: nowrap;
|
||||
text-align: left;
|
||||
font-weight: bold;
|
||||
|
||||
> .avatar {
|
||||
$size: 32px;
|
||||
@@ -219,6 +246,54 @@ export default defineComponent({
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&.activeTab {
|
||||
text-align: center;
|
||||
|
||||
> .chevron {
|
||||
display: inline-block;
|
||||
margin-left: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .tabs {
|
||||
margin-left: 16px;
|
||||
font-size: 0.8em;
|
||||
|
||||
> .tab {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
padding: 0 10px;
|
||||
height: 100%;
|
||||
font-weight: normal;
|
||||
opacity: 0.7;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.active {
|
||||
opacity: 1;
|
||||
|
||||
&:after {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
background: var(--accent);
|
||||
}
|
||||
}
|
||||
|
||||
> .icon + .title {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -12,7 +12,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<main class="main" @contextmenu.stop="onContextmenu">
|
||||
<main class="main" @contextmenu.stop="onContextmenu" :style="{ background: pageInfo?.bg }">
|
||||
<header class="header" @click="onHeaderClick">
|
||||
<XHeader :info="pageInfo" :back-button="true" @back="back()"/>
|
||||
</header>
|
||||
@@ -145,6 +145,15 @@ export default defineComponent({
|
||||
}
|
||||
}, '*');
|
||||
}, { passive: true });
|
||||
window.addEventListener('touchmove', ev => {
|
||||
this.$refs.live2d.contentWindow.postMessage({
|
||||
type: 'moveCursor',
|
||||
body: {
|
||||
x: ev.touches[0].clientX - iframeRect.left,
|
||||
y: ev.touches[0].clientY - iframeRect.top,
|
||||
}
|
||||
}, '*');
|
||||
}, { passive: true });
|
||||
}
|
||||
},
|
||||
|
||||
|
@@ -2,8 +2,8 @@
|
||||
<div class="mk-app" :class="{ wallpaper }">
|
||||
<XSidebar ref="nav" class="sidebar"/>
|
||||
|
||||
<div class="contents" ref="contents" @contextmenu.stop="onContextmenu">
|
||||
<header class="header" ref="header" @click="onHeaderClick">
|
||||
<div class="contents" ref="contents" @contextmenu.stop="onContextmenu" :style="{ background: pageInfo?.bg }">
|
||||
<header class="header" ref="header" @click="onHeaderClick" :style="{ background: pageInfo?.bg }">
|
||||
<XHeader :info="pageInfo" :back-button="true" @back="back()"/>
|
||||
</header>
|
||||
<main ref="main">
|
||||
@@ -258,7 +258,6 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
> .sidebar {
|
||||
border-right: solid 0.5px var(--divider);
|
||||
}
|
||||
|
||||
> .contents {
|
||||
@@ -313,7 +312,8 @@ export default defineComponent({
|
||||
|
||||
> .widgets {
|
||||
padding: 0 var(--margin);
|
||||
border-left: solid 0.5px var(--divider);
|
||||
//border-left: solid 0.5px var(--divider);
|
||||
background: var(--navBg);
|
||||
|
||||
@media (max-width: $widgets-hide-threshold) {
|
||||
display: none;
|
||||
|
@@ -1,15 +1,57 @@
|
||||
export type Schema = {
|
||||
type: 'boolean' | 'number' | 'string' | 'array' | 'object' | 'any';
|
||||
nullable: boolean;
|
||||
optional: boolean;
|
||||
import { SimpleObj, SimpleSchema } from './simple-schema';
|
||||
import { packedUserSchema } from '@/models/repositories/user';
|
||||
import { packedNoteSchema } from '@/models/repositories/note';
|
||||
import { packedUserListSchema } from '@/models/repositories/user-list';
|
||||
import { packedAppSchema } from '@/models/repositories/app';
|
||||
import { packedMessagingMessageSchema } from '@/models/repositories/messaging-message';
|
||||
import { packedNotificationSchema } from '@/models/repositories/notification';
|
||||
import { packedDriveFileSchema } from '@/models/repositories/drive-file';
|
||||
import { packedDriveFolderSchema } from '@/models/repositories/drive-folder';
|
||||
import { packedFollowingSchema } from '@/models/repositories/following';
|
||||
import { packedMutingSchema } from '@/models/repositories/muting';
|
||||
import { packedBlockingSchema } from '@/models/repositories/blocking';
|
||||
import { packedNoteReactionSchema } from '@/models/repositories/note-reaction';
|
||||
import { packedHashtagSchema } from '@/models/repositories/hashtag';
|
||||
import { packedPageSchema } from '@/models/repositories/page';
|
||||
import { packedUserGroupSchema } from '@/models/repositories/user-group';
|
||||
import { packedNoteFavoriteSchema } from '@/models/repositories/note-favorite';
|
||||
import { packedChannelSchema } from '@/models/repositories/channel';
|
||||
import { packedAntennaSchema } from '@/models/repositories/antenna';
|
||||
import { packedClipSchema } from '@/models/repositories/clip';
|
||||
import { packedFederationInstanceSchema } from '@/models/repositories/federation-instance';
|
||||
import { packedQueueCountSchema } from '@/models/repositories/queue';
|
||||
import { packedGalleryPostSchema } from '@/models/repositories/gallery-post';
|
||||
|
||||
export const refs = {
|
||||
User: packedUserSchema,
|
||||
UserList: packedUserListSchema,
|
||||
UserGroup: packedUserGroupSchema,
|
||||
App: packedAppSchema,
|
||||
MessagingMessage: packedMessagingMessageSchema,
|
||||
Note: packedNoteSchema,
|
||||
NoteReaction: packedNoteReactionSchema,
|
||||
NoteFavorite: packedNoteFavoriteSchema,
|
||||
Notification: packedNotificationSchema,
|
||||
DriveFile: packedDriveFileSchema,
|
||||
DriveFolder: packedDriveFolderSchema,
|
||||
Following: packedFollowingSchema,
|
||||
Muting: packedMutingSchema,
|
||||
Blocking: packedBlockingSchema,
|
||||
Hashtag: packedHashtagSchema,
|
||||
Page: packedPageSchema,
|
||||
Channel: packedChannelSchema,
|
||||
QueueCount: packedQueueCountSchema,
|
||||
Antenna: packedAntennaSchema,
|
||||
Clip: packedClipSchema,
|
||||
FederationInstance: packedFederationInstanceSchema,
|
||||
GalleryPost: packedGalleryPostSchema,
|
||||
};
|
||||
|
||||
export interface Schema extends SimpleSchema {
|
||||
items?: Schema;
|
||||
properties?: Obj;
|
||||
description?: string;
|
||||
example?: any;
|
||||
format?: string;
|
||||
ref?: string;
|
||||
enum?: string[];
|
||||
};
|
||||
ref?: keyof typeof refs;
|
||||
}
|
||||
|
||||
type NonUndefinedPropertyNames<T extends Obj> = {
|
||||
[K in keyof T]: T[K]['optional'] extends true ? never : K
|
||||
@@ -22,7 +64,7 @@ type UndefinedPropertyNames<T extends Obj> = {
|
||||
type OnlyRequired<T extends Obj> = Pick<T, NonUndefinedPropertyNames<T>>;
|
||||
type OnlyOptional<T extends Obj> = Pick<T, UndefinedPropertyNames<T>>;
|
||||
|
||||
export type Obj = { [key: string]: Schema };
|
||||
export interface Obj extends SimpleObj { [key: string]: Schema; }
|
||||
|
||||
export type ObjType<s extends Obj> =
|
||||
{ [P in keyof OnlyOptional<s>]?: SchemaType<s[P]> } &
|
||||
@@ -48,6 +90,10 @@ export type SchemaType<p extends Schema> =
|
||||
p['type'] extends 'string' ? NullOrUndefined<p, string> :
|
||||
p['type'] extends 'boolean' ? NullOrUndefined<p, boolean> :
|
||||
p['type'] extends 'array' ? NullOrUndefined<p, MyType<NonNullable<p['items']>>[]> :
|
||||
p['type'] extends 'object' ? NullOrUndefined<p, ObjType<NonNullable<p['properties']>>> :
|
||||
p['type'] extends 'object' ? (
|
||||
p['ref'] extends keyof typeof refs
|
||||
? NullOrUndefined<p, SchemaType<typeof refs[p['ref']]>>
|
||||
: NullOrUndefined<p, ObjType<NonNullable<p['properties']>>>
|
||||
) :
|
||||
p['type'] extends 'any' ? NullOrUndefined<p, any> :
|
||||
any;
|
||||
|
15
src/misc/simple-schema.ts
Normal file
15
src/misc/simple-schema.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export interface SimpleSchema {
|
||||
type: 'boolean' | 'number' | 'string' | 'array' | 'object' | 'any';
|
||||
nullable: boolean;
|
||||
optional: boolean;
|
||||
items?: SimpleSchema;
|
||||
properties?: SimpleObj;
|
||||
description?: string;
|
||||
example?: any;
|
||||
format?: string;
|
||||
ref?: string;
|
||||
enum?: string[];
|
||||
default?: boolean | null;
|
||||
}
|
||||
|
||||
export interface SimpleObj { [key: string]: SimpleSchema; }
|
@@ -56,7 +56,7 @@ export const packedBlockingSchema = {
|
||||
blockee: {
|
||||
type: 'object' as const,
|
||||
optional: false as const, nullable: false as const,
|
||||
ref: 'User',
|
||||
ref: 'User' as const,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
@@ -53,7 +53,7 @@ export const packedClipSchema = {
|
||||
},
|
||||
user: {
|
||||
type: 'object' as const,
|
||||
ref: 'User',
|
||||
ref: 'User' as const,
|
||||
optional: false as const, nullable: false as const,
|
||||
},
|
||||
name: {
|
||||
|
@@ -234,7 +234,7 @@ export const packedDriveFileSchema = {
|
||||
folder: {
|
||||
type: 'object' as const,
|
||||
optional: true as const, nullable: true as const,
|
||||
ref: 'DriveFolder'
|
||||
ref: 'DriveFolder' as const,
|
||||
},
|
||||
userId: {
|
||||
type: 'string' as const,
|
||||
@@ -245,7 +245,7 @@ export const packedDriveFileSchema = {
|
||||
user: {
|
||||
type: 'object' as const,
|
||||
optional: true as const, nullable: true as const,
|
||||
ref: 'User'
|
||||
ref: 'User' as const,
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@@ -87,7 +87,7 @@ export const packedDriveFolderSchema = {
|
||||
parent: {
|
||||
type: 'object' as const,
|
||||
optional: true as const, nullable: true as const,
|
||||
ref: 'DriveFolder'
|
||||
ref: 'DriveFolder' as const,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@@ -110,7 +110,7 @@ export const packedFollowingSchema = {
|
||||
followee: {
|
||||
type: 'object' as const,
|
||||
optional: true as const, nullable: false as const,
|
||||
ref: 'User',
|
||||
ref: 'User' as const,
|
||||
},
|
||||
followerId: {
|
||||
type: 'string' as const,
|
||||
@@ -120,7 +120,7 @@ export const packedFollowingSchema = {
|
||||
follower: {
|
||||
type: 'object' as const,
|
||||
optional: true as const, nullable: false as const,
|
||||
ref: 'User',
|
||||
ref: 'User' as const,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { EntityRepository, Repository } from 'typeorm';
|
||||
import { GalleryPost } from '@/models/entities/gallery-post';
|
||||
import { SchemaType } from '../../misc/schema';
|
||||
import { SchemaType } from '@/misc/schema';
|
||||
import { Users, DriveFiles, GalleryLikes } from '../index';
|
||||
import { awaitAll } from '@/prelude/await-all';
|
||||
import { User } from '@/models/entities/user';
|
||||
@@ -76,7 +76,7 @@ export const packedGalleryPostSchema = {
|
||||
},
|
||||
user: {
|
||||
type: 'object' as const,
|
||||
ref: 'User',
|
||||
ref: 'User' as const,
|
||||
optional: false as const, nullable: false as const,
|
||||
},
|
||||
fileIds: {
|
||||
@@ -94,7 +94,7 @@ export const packedGalleryPostSchema = {
|
||||
items: {
|
||||
type: 'object' as const,
|
||||
optional: false as const, nullable: false as const,
|
||||
ref: 'DriveFile'
|
||||
ref: 'DriveFile' as const,
|
||||
}
|
||||
},
|
||||
tags: {
|
||||
|
@@ -67,7 +67,7 @@ export const packedMessagingMessageSchema = {
|
||||
},
|
||||
user: {
|
||||
type: 'object' as const,
|
||||
ref: 'User',
|
||||
ref: 'User' as const,
|
||||
optional: true as const, nullable: false as const,
|
||||
},
|
||||
text: {
|
||||
@@ -82,7 +82,7 @@ export const packedMessagingMessageSchema = {
|
||||
file: {
|
||||
type: 'object' as const,
|
||||
optional: true as const, nullable: true as const,
|
||||
ref: 'DriveFile',
|
||||
ref: 'DriveFile' as const,
|
||||
},
|
||||
recipientId: {
|
||||
type: 'string' as const,
|
||||
@@ -92,7 +92,7 @@ export const packedMessagingMessageSchema = {
|
||||
recipient: {
|
||||
type: 'object' as const,
|
||||
optional: true as const, nullable: true as const,
|
||||
ref: 'User'
|
||||
ref: 'User' as const,
|
||||
},
|
||||
groupId: {
|
||||
type: 'string' as const,
|
||||
@@ -102,7 +102,7 @@ export const packedMessagingMessageSchema = {
|
||||
group: {
|
||||
type: 'object' as const,
|
||||
optional: true as const, nullable: true as const,
|
||||
ref: 'UserGroup'
|
||||
ref: 'UserGroup' as const,
|
||||
},
|
||||
isRead: {
|
||||
type: 'boolean' as const,
|
||||
|
@@ -56,7 +56,7 @@ export const packedMutingSchema = {
|
||||
mutee: {
|
||||
type: 'object' as const,
|
||||
optional: false as const, nullable: false as const,
|
||||
ref: 'User',
|
||||
ref: 'User' as const,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
@@ -45,7 +45,7 @@ export const packedNoteFavoriteSchema = {
|
||||
note: {
|
||||
type: 'object' as const,
|
||||
optional: false as const, nullable: false as const,
|
||||
ref: 'Note',
|
||||
ref: 'Note' as const,
|
||||
},
|
||||
noteId: {
|
||||
type: 'string' as const,
|
||||
|
@@ -42,7 +42,7 @@ export const packedNoteReactionSchema = {
|
||||
user: {
|
||||
type: 'object' as const,
|
||||
optional: false as const, nullable: false as const,
|
||||
ref: 'User',
|
||||
ref: 'User' as const,
|
||||
},
|
||||
type: {
|
||||
type: 'string' as const,
|
||||
|
@@ -95,7 +95,7 @@ export class NoteRepository extends Repository<Note> {
|
||||
hide = true;
|
||||
} else if (meId === packedNote.userId) {
|
||||
hide = false;
|
||||
} else if (packedNote.reply && (meId === (packedNote.reply as PackedNote).userId)) {
|
||||
} else if (packedNote.reply && (meId === packedNote.reply.userId)) {
|
||||
// 自分の投稿に対するリプライ
|
||||
hide = false;
|
||||
} else if (packedNote.mentions && packedNote.mentions.some(id => meId === id)) {
|
||||
@@ -353,7 +353,7 @@ export const packedNoteSchema = {
|
||||
},
|
||||
user: {
|
||||
type: 'object' as const,
|
||||
ref: 'User',
|
||||
ref: 'User' as const,
|
||||
optional: false as const, nullable: false as const,
|
||||
},
|
||||
replyId: {
|
||||
@@ -371,12 +371,12 @@ export const packedNoteSchema = {
|
||||
reply: {
|
||||
type: 'object' as const,
|
||||
optional: true as const, nullable: true as const,
|
||||
ref: 'Note'
|
||||
ref: 'Note' as const,
|
||||
},
|
||||
renote: {
|
||||
type: 'object' as const,
|
||||
optional: true as const, nullable: true as const,
|
||||
ref: 'Note'
|
||||
ref: 'Note' as const,
|
||||
},
|
||||
viaMobile: {
|
||||
type: 'boolean' as const,
|
||||
@@ -423,7 +423,7 @@ export const packedNoteSchema = {
|
||||
items: {
|
||||
type: 'object' as const,
|
||||
optional: false as const, nullable: false as const,
|
||||
ref: 'DriveFile'
|
||||
ref: 'DriveFile' as const,
|
||||
}
|
||||
},
|
||||
tags: {
|
||||
@@ -447,11 +447,24 @@ export const packedNoteSchema = {
|
||||
channel: {
|
||||
type: 'object' as const,
|
||||
optional: true as const, nullable: true as const,
|
||||
ref: 'Channel'
|
||||
items: {
|
||||
type: 'object' as const,
|
||||
optional: false as const, nullable: false as const,
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string' as const,
|
||||
optional: false as const, nullable: false as const,
|
||||
},
|
||||
name: {
|
||||
type: 'string' as const,
|
||||
optional: false as const, nullable: true as const,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
localOnly: {
|
||||
type: 'boolean' as const,
|
||||
optional: false as const, nullable: true as const,
|
||||
optional: true as const, nullable: false as const,
|
||||
},
|
||||
emojis: {
|
||||
type: 'array' as const,
|
||||
@@ -466,7 +479,7 @@ export const packedNoteSchema = {
|
||||
},
|
||||
url: {
|
||||
type: 'string' as const,
|
||||
optional: false as const, nullable: false as const,
|
||||
optional: false as const, nullable: true as const,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -485,11 +498,11 @@ export const packedNoteSchema = {
|
||||
},
|
||||
uri: {
|
||||
type: 'string' as const,
|
||||
optional: false as const, nullable: true as const,
|
||||
optional: true as const, nullable: false as const,
|
||||
},
|
||||
url: {
|
||||
type: 'string' as const,
|
||||
optional: false as const, nullable: true as const,
|
||||
optional: true as const, nullable: false as const,
|
||||
},
|
||||
|
||||
myReaction: {
|
||||
|
@@ -136,7 +136,7 @@ export const packedNotificationSchema = {
|
||||
},
|
||||
user: {
|
||||
type: 'object' as const,
|
||||
ref: 'User',
|
||||
ref: 'User' as const,
|
||||
optional: true as const, nullable: true as const,
|
||||
},
|
||||
userId: {
|
||||
@@ -146,7 +146,7 @@ export const packedNotificationSchema = {
|
||||
},
|
||||
note: {
|
||||
type: 'object' as const,
|
||||
ref: 'Note',
|
||||
ref: 'Note' as const,
|
||||
optional: true as const, nullable: true as const,
|
||||
},
|
||||
reaction: {
|
||||
|
@@ -137,7 +137,7 @@ export const packedPageSchema = {
|
||||
},
|
||||
user: {
|
||||
type: 'object' as const,
|
||||
ref: 'User',
|
||||
ref: 'User' as const,
|
||||
optional: false as const, nullable: false as const,
|
||||
},
|
||||
}
|
||||
|
@@ -375,12 +375,12 @@ export const packedUserSchema = {
|
||||
},
|
||||
isAdmin: {
|
||||
type: 'boolean' as const,
|
||||
nullable: false as const, optional: false as const,
|
||||
nullable: false as const, optional: true as const,
|
||||
default: false
|
||||
},
|
||||
isModerator: {
|
||||
type: 'boolean' as const,
|
||||
nullable: false as const, optional: false as const,
|
||||
nullable: false as const, optional: true as const,
|
||||
default: false
|
||||
},
|
||||
isBot: {
|
||||
@@ -402,23 +402,11 @@ export const packedUserSchema = {
|
||||
type: 'string' as const,
|
||||
nullable: false as const, optional: false as const
|
||||
},
|
||||
host: {
|
||||
type: 'string' as const,
|
||||
nullable: true as const, optional: false as const
|
||||
},
|
||||
url: {
|
||||
type: 'string' as const,
|
||||
nullable: false as const, optional: false as const,
|
||||
format: 'url'
|
||||
},
|
||||
aliases: {
|
||||
type: 'array' as const,
|
||||
nullable: false as const, optional: false as const,
|
||||
items: {
|
||||
type: 'string' as const,
|
||||
nullable: false as const, optional: false as const
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -457,7 +445,7 @@ export const packedUserSchema = {
|
||||
},
|
||||
isSuspended: {
|
||||
type: 'boolean' as const,
|
||||
nullable: false as const, optional: false as const,
|
||||
nullable: false as const, optional: true as const,
|
||||
example: false
|
||||
},
|
||||
description: {
|
||||
@@ -476,7 +464,7 @@ export const packedUserSchema = {
|
||||
},
|
||||
fields: {
|
||||
type: 'array' as const,
|
||||
nullable: false as const, optional: false as const,
|
||||
nullable: false as const, optional: true as const,
|
||||
items: {
|
||||
type: 'object' as const,
|
||||
nullable: false as const, optional: false as const,
|
||||
@@ -520,31 +508,31 @@ export const packedUserSchema = {
|
||||
items: {
|
||||
type: 'object' as const,
|
||||
nullable: false as const, optional: false as const,
|
||||
ref: 'Note'
|
||||
ref: 'Note' as const,
|
||||
}
|
||||
},
|
||||
pinnedPageId: {
|
||||
type: 'string' as const,
|
||||
nullable: true as const, optional: false as const
|
||||
nullable: true as const, optional: true as const
|
||||
},
|
||||
pinnedPage: {
|
||||
type: 'object' as const,
|
||||
nullable: true as const, optional: false as const,
|
||||
ref: 'Page'
|
||||
nullable: true as const, optional: true as const,
|
||||
ref: 'Page' as const,
|
||||
},
|
||||
twoFactorEnabled: {
|
||||
type: 'boolean' as const,
|
||||
nullable: false as const, optional: false as const,
|
||||
nullable: false as const, optional: true as const,
|
||||
default: false
|
||||
},
|
||||
usePasswordLessLogin: {
|
||||
type: 'boolean' as const,
|
||||
nullable: false as const, optional: false as const,
|
||||
nullable: false as const, optional: true as const,
|
||||
default: false
|
||||
},
|
||||
securityKeys: {
|
||||
type: 'boolean' as const,
|
||||
nullable: false as const, optional: false as const,
|
||||
nullable: false as const, optional: true as const,
|
||||
default: false
|
||||
},
|
||||
avatarId: {
|
||||
|
@@ -3,7 +3,7 @@ import { dirname } from 'path';
|
||||
import { Context } from 'cafy';
|
||||
import * as path from 'path';
|
||||
import * as glob from 'glob';
|
||||
import { Schema } from '@/misc/schema';
|
||||
import { SimpleSchema } from '@/misc/simple-schema';
|
||||
|
||||
//const _filename = fileURLToPath(import.meta.url);
|
||||
const _filename = __filename;
|
||||
@@ -34,7 +34,7 @@ export interface IEndpointMeta {
|
||||
};
|
||||
};
|
||||
|
||||
res?: Schema;
|
||||
res?: SimpleSchema;
|
||||
|
||||
/**
|
||||
* このエンドポイントにリクエストするのにユーザー情報が必須か否か
|
||||
|
@@ -1,26 +1,4 @@
|
||||
import { packedUserSchema } from '@/models/repositories/user';
|
||||
import { Schema } from '@/misc/schema';
|
||||
import { packedNoteSchema } from '@/models/repositories/note';
|
||||
import { packedUserListSchema } from '@/models/repositories/user-list';
|
||||
import { packedAppSchema } from '@/models/repositories/app';
|
||||
import { packedMessagingMessageSchema } from '@/models/repositories/messaging-message';
|
||||
import { packedNotificationSchema } from '@/models/repositories/notification';
|
||||
import { packedDriveFileSchema } from '@/models/repositories/drive-file';
|
||||
import { packedDriveFolderSchema } from '@/models/repositories/drive-folder';
|
||||
import { packedFollowingSchema } from '@/models/repositories/following';
|
||||
import { packedMutingSchema } from '@/models/repositories/muting';
|
||||
import { packedBlockingSchema } from '@/models/repositories/blocking';
|
||||
import { packedNoteReactionSchema } from '@/models/repositories/note-reaction';
|
||||
import { packedHashtagSchema } from '@/models/repositories/hashtag';
|
||||
import { packedPageSchema } from '@/models/repositories/page';
|
||||
import { packedUserGroupSchema } from '@/models/repositories/user-group';
|
||||
import { packedNoteFavoriteSchema } from '@/models/repositories/note-favorite';
|
||||
import { packedChannelSchema } from '@/models/repositories/channel';
|
||||
import { packedAntennaSchema } from '@/models/repositories/antenna';
|
||||
import { packedClipSchema } from '@/models/repositories/clip';
|
||||
import { packedFederationInstanceSchema } from '@/models/repositories/federation-instance';
|
||||
import { packedQueueCountSchema } from '@/models/repositories/queue';
|
||||
import { packedGalleryPostSchema } from '@/models/repositories/gallery-post';
|
||||
import { refs, Schema } from '@/misc/schema';
|
||||
|
||||
export function convertSchemaToOpenApiSchema(schema: Schema) {
|
||||
const res: any = schema;
|
||||
@@ -72,26 +50,7 @@ export const schemas = {
|
||||
required: ['error']
|
||||
},
|
||||
|
||||
User: convertSchemaToOpenApiSchema(packedUserSchema),
|
||||
UserList: convertSchemaToOpenApiSchema(packedUserListSchema),
|
||||
UserGroup: convertSchemaToOpenApiSchema(packedUserGroupSchema),
|
||||
App: convertSchemaToOpenApiSchema(packedAppSchema),
|
||||
MessagingMessage: convertSchemaToOpenApiSchema(packedMessagingMessageSchema),
|
||||
Note: convertSchemaToOpenApiSchema(packedNoteSchema),
|
||||
NoteReaction: convertSchemaToOpenApiSchema(packedNoteReactionSchema),
|
||||
NoteFavorite: convertSchemaToOpenApiSchema(packedNoteFavoriteSchema),
|
||||
Notification: convertSchemaToOpenApiSchema(packedNotificationSchema),
|
||||
DriveFile: convertSchemaToOpenApiSchema(packedDriveFileSchema),
|
||||
DriveFolder: convertSchemaToOpenApiSchema(packedDriveFolderSchema),
|
||||
Following: convertSchemaToOpenApiSchema(packedFollowingSchema),
|
||||
Muting: convertSchemaToOpenApiSchema(packedMutingSchema),
|
||||
Blocking: convertSchemaToOpenApiSchema(packedBlockingSchema),
|
||||
Hashtag: convertSchemaToOpenApiSchema(packedHashtagSchema),
|
||||
Page: convertSchemaToOpenApiSchema(packedPageSchema),
|
||||
Channel: convertSchemaToOpenApiSchema(packedChannelSchema),
|
||||
QueueCount: convertSchemaToOpenApiSchema(packedQueueCountSchema),
|
||||
Antenna: convertSchemaToOpenApiSchema(packedAntennaSchema),
|
||||
Clip: convertSchemaToOpenApiSchema(packedClipSchema),
|
||||
FederationInstance: convertSchemaToOpenApiSchema(packedFederationInstanceSchema),
|
||||
GalleryPost: convertSchemaToOpenApiSchema(packedGalleryPostSchema),
|
||||
...Object.fromEntries(
|
||||
Object.entries(refs).map(([key, schema]) => [key, convertSchemaToOpenApiSchema(schema)])
|
||||
),
|
||||
};
|
||||
|
@@ -43,7 +43,7 @@ export default class extends Channel {
|
||||
|
||||
// 関係ない返信は除外
|
||||
if (note.reply) {
|
||||
const reply = note.reply as PackedNote;
|
||||
const reply = note.reply;
|
||||
// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
|
||||
if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return;
|
||||
}
|
||||
|
@@ -51,7 +51,7 @@ export default class extends Channel {
|
||||
|
||||
// 関係ない返信は除外
|
||||
if (note.reply) {
|
||||
const reply = note.reply as PackedNote;
|
||||
const reply = note.reply;
|
||||
// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
|
||||
if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return;
|
||||
}
|
||||
|
@@ -4,7 +4,6 @@ import Channel from '../channel';
|
||||
import { fetchMeta } from '@/misc/fetch-meta';
|
||||
import { Notes } from '@/models/index';
|
||||
import { PackedNote } from '@/models/repositories/note';
|
||||
import { PackedUser } from '@/models/repositories/user';
|
||||
import { checkWordMute } from '@/misc/check-word-mute';
|
||||
import { isBlockerUserRelated } from '@/misc/is-blocker-user-related';
|
||||
|
||||
@@ -31,7 +30,7 @@ export default class extends Channel {
|
||||
if (!(
|
||||
(note.channelId == null && this.user!.id === note.userId) ||
|
||||
(note.channelId == null && this.following.has(note.userId)) ||
|
||||
(note.channelId == null && ((note.user as PackedUser).host == null && note.visibility === 'public')) ||
|
||||
(note.channelId == null && (note.user.host == null && note.visibility === 'public')) ||
|
||||
(note.channelId != null && this.followingChannels.has(note.channelId))
|
||||
)) return;
|
||||
|
||||
@@ -60,7 +59,7 @@ export default class extends Channel {
|
||||
|
||||
// 関係ない返信は除外
|
||||
if (note.reply) {
|
||||
const reply = note.reply as PackedNote;
|
||||
const reply = note.reply;
|
||||
// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
|
||||
if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return;
|
||||
}
|
||||
|
@@ -4,7 +4,6 @@ import Channel from '../channel';
|
||||
import { fetchMeta } from '@/misc/fetch-meta';
|
||||
import { Notes } from '@/models/index';
|
||||
import { PackedNote } from '@/models/repositories/note';
|
||||
import { PackedUser } from '@/models/repositories/user';
|
||||
import { checkWordMute } from '@/misc/check-word-mute';
|
||||
import { isBlockerUserRelated } from '@/misc/is-blocker-user-related';
|
||||
|
||||
@@ -26,7 +25,7 @@ export default class extends Channel {
|
||||
|
||||
@autobind
|
||||
private async onNote(note: PackedNote) {
|
||||
if ((note.user as PackedUser).host !== null) return;
|
||||
if (note.user.host !== null) return;
|
||||
if (note.visibility !== 'public') return;
|
||||
if (note.channelId != null && !this.followingChannels.has(note.channelId)) return;
|
||||
|
||||
@@ -45,7 +44,7 @@ export default class extends Channel {
|
||||
|
||||
// 関係ない返信は除外
|
||||
if (note.reply) {
|
||||
const reply = note.reply as PackedNote;
|
||||
const reply = note.reply;
|
||||
// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
|
||||
if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return;
|
||||
}
|
||||
|
@@ -165,8 +165,8 @@ export default class Connection {
|
||||
};
|
||||
|
||||
add(note);
|
||||
if (note.reply) add(note.reply as PackedNote);
|
||||
if (note.renote) add(note.renote as PackedNote);
|
||||
if (note.reply) add(note.reply);
|
||||
if (note.renote) add(note.renote);
|
||||
}
|
||||
|
||||
@autobind
|
||||
|
@@ -7,7 +7,7 @@
|
||||
import * as nestedProperty from 'nested-property';
|
||||
import autobind from 'autobind-decorator';
|
||||
import Logger from '../logger';
|
||||
import { Schema } from '@/misc/schema';
|
||||
import { SimpleSchema } from '@/misc/simple-schema';
|
||||
import { EntitySchema, getRepository, Repository, LessThan, Between } from 'typeorm';
|
||||
import { dateUTC, isTimeSame, isTimeBefore, subtractTime, addTime } from '@/prelude/time';
|
||||
import { getChartInsertLock } from '@/misc/app-lock';
|
||||
@@ -56,7 +56,7 @@ export default abstract class Chart<T extends Record<string, any>> {
|
||||
diff: DeepPartial<T>;
|
||||
group: string | null;
|
||||
}[] = [];
|
||||
public schema: Schema;
|
||||
public schema: SimpleSchema;
|
||||
protected repository: Repository<Log>;
|
||||
|
||||
protected abstract genNewLog(latest: T): DeepPartial<T>;
|
||||
@@ -69,7 +69,7 @@ export default abstract class Chart<T extends Record<string, any>> {
|
||||
protected abstract fetchActual(group: string | null): Promise<DeepPartial<T>>;
|
||||
|
||||
@autobind
|
||||
private static convertSchemaToFlatColumnDefinitions(schema: Schema) {
|
||||
private static convertSchemaToFlatColumnDefinitions(schema: SimpleSchema) {
|
||||
const columns = {} as any;
|
||||
const flatColumns = (x: Obj, path?: string) => {
|
||||
for (const [k, v] of Object.entries(x)) {
|
||||
@@ -181,7 +181,7 @@ export default abstract class Chart<T extends Record<string, any>> {
|
||||
}
|
||||
|
||||
@autobind
|
||||
public static schemaToEntity(name: string, schema: Schema): EntitySchema {
|
||||
public static schemaToEntity(name: string, schema: SimpleSchema): EntitySchema {
|
||||
return new EntitySchema({
|
||||
name: `__chart__${camelToSnake(name)}`,
|
||||
columns: {
|
||||
@@ -211,7 +211,7 @@ export default abstract class Chart<T extends Record<string, any>> {
|
||||
});
|
||||
}
|
||||
|
||||
constructor(name: string, schema: Schema, grouped = false) {
|
||||
constructor(name: string, schema: SimpleSchema, grouped = false) {
|
||||
this.name = name;
|
||||
this.schema = schema;
|
||||
const entity = Chart.schemaToEntity(name, schema);
|
||||
@@ -546,8 +546,8 @@ export default abstract class Chart<T extends Record<string, any>> {
|
||||
}
|
||||
}
|
||||
|
||||
export function convertLog(logSchema: Schema): Schema {
|
||||
const v: Schema = JSON.parse(JSON.stringify(logSchema)); // copy
|
||||
export function convertLog(logSchema: SimpleSchema): SimpleSchema {
|
||||
const v: SimpleSchema = JSON.parse(JSON.stringify(logSchema)); // copy
|
||||
if (v.type === 'number') {
|
||||
v.type = 'array';
|
||||
v.items = {
|
||||
|
Reference in New Issue
Block a user