Compare commits
No commits in common. "ac63accbed76ca8bc0efb37f30d93b81dfaf6208" and "8d4614a48df392baa45599e59ba5c6eb4b1d6e2d" have entirely different histories.
ac63accbed
...
8d4614a48d
|
@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<MkButton rounded :full="!props.small" small @click="toggle"><b>{{ modelValue ? i18n.ts._cw.hide : i18n.ts._cw.show }}</b><span v-if="!modelValue" :class="$style.label">{{ label }}</span></MkButton>
|
<MkButton rounded full small @click="toggle"><b>{{ modelValue ? i18n.ts._cw.hide : i18n.ts._cw.show }}</b><span v-if="!modelValue" :class="$style.label">{{ label }}</span></MkButton>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
@ -21,7 +21,6 @@ const props = defineProps<{
|
||||||
renote?: Misskey.entities.Note | null;
|
renote?: Misskey.entities.Note | null;
|
||||||
files?: Misskey.entities.DriveFile[];
|
files?: Misskey.entities.DriveFile[];
|
||||||
poll?: Misskey.entities.Note['poll'] | PollEditorModelValue | null;
|
poll?: Misskey.entities.Note['poll'] | PollEditorModelValue | null;
|
||||||
small?: boolean | null;
|
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
|
|
@ -36,7 +36,6 @@ import { deviceKind } from '@/scripts/device-kind.js';
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
src?: HTMLElement;
|
src?: HTMLElement;
|
||||||
anchor?: { x: string; y: string; };
|
anchor?: { x: string; y: string; };
|
||||||
all?: boolean | null;
|
|
||||||
}>(), {
|
}>(), {
|
||||||
anchor: () => ({ x: 'right', y: 'center' }),
|
anchor: () => ({ x: 'right', y: 'center' }),
|
||||||
});
|
});
|
||||||
|
@ -53,7 +52,7 @@ const modal = shallowRef<InstanceType<typeof MkModal>>();
|
||||||
|
|
||||||
const menu = defaultStore.state.menu;
|
const menu = defaultStore.state.menu;
|
||||||
|
|
||||||
const items = Object.keys(navbarItemDef).filter(k => props.all || !menu.includes(k)).map(k => navbarItemDef[k]).filter(def => def.show == null ? true : def.show).map(def => ({
|
const items = Object.keys(navbarItemDef).filter(k => !menu.includes(k)).map(k => navbarItemDef[k]).filter(def => def.show == null ? true : def.show).map(def => ({
|
||||||
type: def.to ? 'link' : 'button',
|
type: def.to ? 'link' : 'button',
|
||||||
text: def.title,
|
text: def.title,
|
||||||
icon: def.icon,
|
icon: def.icon,
|
||||||
|
|
|
@ -24,10 +24,16 @@
|
||||||
<MkA to="/#global" v-if="isGlobalTimelineAvailable" class="item" active-class="active" exact><i class="ti ti-whirl icon"></i>{{ i18n.ts._timelines.global }}</MkA>
|
<MkA to="/#global" v-if="isGlobalTimelineAvailable" class="item" active-class="active" exact><i class="ti ti-whirl icon"></i>{{ i18n.ts._timelines.global }}</MkA>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="favoriteChannels" class="container">
|
<div v-if="followedChannels" class="container">
|
||||||
<div class="header">{{ i18n.ts.channel }} ({{ i18n.ts.favorites }})<MkA to="/channels" class="_button add"><i class="ti ti-dots"></i></MkA></div>
|
<div class="header">{{ i18n.ts.channel }} ({{ i18n.ts.following }})<button class="_button add" @click="addChannel"><i class="ti ti-plus"></i></button></div>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<MkA v-for="channel in favoriteChannels" :key="channel.id" :to="`/channels/${ channel.id }`" class="item" :class="{ read: !channel.hasUnreadNote }" active-class="active" exact><i class="ti ti-device-tv icon"></i>{{ channel.name }}</MkA>
|
<MkA v-for="channel in followedChannels" :key="channel.id" :to="`/channels/${ channel.id }`" class="item" :class="{ read: !channel.hasUnreadNote }" active-class="active" exact><i class="ti ti-device-tv icon"></i>{{ channel.name }}</MkA>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="featuredChannels" class="container">
|
||||||
|
<div class="header">{{ i18n.ts.channel }}<button class="_button add" @click="addChannel"><i class="ti ti-plus"></i></button></div>
|
||||||
|
<div class="body">
|
||||||
|
<MkA v-for="channel in featuredChannels" :key="channel.id" :to="`/channels/${ channel.id }`" class="item" active-class="active" exact><i class="ti ti-device-tv icon"></i>{{ channel.name }}</MkA>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="lists" class="container">
|
<div v-if="lists" class="container">
|
||||||
|
@ -37,7 +43,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="antennas" class="container">
|
<div v-if="antennas" class="container">
|
||||||
<div class="header">{{ i18n.ts.antennas }}<MkA to="/my/antennas/create" class="_button add"><i class="ti ti-plus"></i></MkA></div>
|
<div class="header">{{ i18n.ts.antennas }}<button class="_button add" @click="addAntenna"><i class="ti ti-plus"></i></button></div>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<MkA v-for="antenna in antennas" :key="antenna.id" :to="`/timeline/antenna/${ antenna.id }`" class="item" active-class="active" exact><i class="ti ti-antenna icon"></i>{{ antenna.name }}</MkA>
|
<MkA v-for="antenna in antennas" :key="antenna.id" :to="`/timeline/antenna/${ antenna.id }`" class="item" active-class="active" exact><i class="ti ti-antenna icon"></i>{{ antenna.name }}</MkA>
|
||||||
</div>
|
</div>
|
||||||
|
@ -49,14 +55,10 @@
|
||||||
<button class="_button menu" @click="showMenu">
|
<button class="_button menu" @click="showMenu">
|
||||||
<i class="ti ti-menu-2 icon"></i>
|
<i class="ti ti-menu-2 icon"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<MkA v-if="$i.isAdmin || $i.isModerator" v-tooltip="i18n.ts.controlPanel" class="item" to="/admin">
|
<button v-tooltip="i18n.ts.search" class="_button item search" @click="search">
|
||||||
<i class="ti ti-dashboard icon"></i>
|
<i class="ti ti-search icon"></i>
|
||||||
</MkA>
|
|
||||||
<button v-tooltip="i18n.ts.more" class="item _button" @click="more">
|
|
||||||
<i class="ti ti-grid-dots icon"></i>
|
|
||||||
</button>
|
</button>
|
||||||
<MkA v-tooltip="i18n.ts.settings" class="item" to="/settings"><i class="ti ti-settings icon"></i></MkA>
|
<MkA v-tooltip="i18n.ts.settings" class="item" to="/settings"><i class="ti ti-settings icon"></i></MkA>
|
||||||
</div>
|
</div>
|
||||||
|
@ -99,9 +101,11 @@ import { provide, ref, shallowRef, toRefs, computed, onMounted, defineAsyncCompo
|
||||||
import { instanceName, url } from '@/config.js';
|
import { instanceName, url } from '@/config.js';
|
||||||
import XWidgets from './chat/widgets.vue';
|
import XWidgets from './chat/widgets.vue';
|
||||||
import XCommon from './_common_/common.vue';
|
import XCommon from './_common_/common.vue';
|
||||||
|
import XHeaderClock from './chat/header-clock.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
import { search } from '@/scripts/search.js';
|
||||||
import makeList from '@/scripts/new-list.js';
|
import makeList from '@/scripts/new-list.js';
|
||||||
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
|
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
|
||||||
import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
|
import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
|
||||||
|
@ -112,11 +116,9 @@ import { mainRouter } from '@/router/main.js';
|
||||||
import { defaultRoutes, page } from '@/router/definition.js';
|
import { defaultRoutes, page } from '@/router/definition.js';
|
||||||
import { useRouterFactory } from '@/router/supplier.js';
|
import { useRouterFactory } from '@/router/supplier.js';
|
||||||
|
|
||||||
|
|
||||||
const XDrawerMenu = defineAsyncComponent(() => import('@/ui/_common_/navbar-for-mobile.vue'));
|
const XDrawerMenu = defineAsyncComponent(() => import('@/ui/_common_/navbar-for-mobile.vue'));
|
||||||
const XStatusBars = defineAsyncComponent(() => import('@/ui/_common_/statusbars.vue'));
|
const XStatusBars = defineAsyncComponent(() => import('@/ui/_common_/statusbars.vue'));
|
||||||
const XAnnouncements = defineAsyncComponent(() => import('@/ui/_common_/announcements.vue'));
|
const XAnnouncements = defineAsyncComponent(() => import('@/ui/_common_/announcements.vue'));
|
||||||
const XLaunchPad = defineAsyncComponent(() => import('@/components/MkLaunchPad.vue'));
|
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
|
@ -127,9 +129,6 @@ const routes = [
|
||||||
globalCacheKey: 'index',
|
globalCacheKey: 'index',
|
||||||
}, {
|
}, {
|
||||||
...defaultRoutes.find((route) => route.path == '/channels/new'),
|
...defaultRoutes.find((route) => route.path == '/channels/new'),
|
||||||
}, {
|
|
||||||
path: '/channels/:channelId/about',
|
|
||||||
component: page(() => import('./chat/pages/channels.about.vue')),
|
|
||||||
}, {
|
}, {
|
||||||
path: '/channels/:channelId',
|
path: '/channels/:channelId',
|
||||||
props: { tlSrc: 'channel' },
|
props: { tlSrc: 'channel' },
|
||||||
|
@ -193,7 +192,8 @@ const menu = computed(() => [{
|
||||||
|
|
||||||
const lists = ref([]);
|
const lists = ref([]);
|
||||||
const antennas = ref([]);
|
const antennas = ref([]);
|
||||||
const favoriteChannels = ref([]);
|
const followedChannels = ref([]);
|
||||||
|
const featuredChannels = ref([]);
|
||||||
|
|
||||||
function getSidebarContent() {
|
function getSidebarContent() {
|
||||||
misskeyApi('users/lists/list').then(newLists => {
|
misskeyApi('users/lists/list').then(newLists => {
|
||||||
|
@ -204,11 +204,22 @@ function getSidebarContent() {
|
||||||
antennas.value = newAntennas;
|
antennas.value = newAntennas;
|
||||||
});
|
});
|
||||||
|
|
||||||
misskeyApi('channels/my-favorites').then(channels => {
|
misskeyApi('channels/followed', { limit: 20 }).then(channels => {
|
||||||
favoriteChannels.value = channels;
|
followedChannels.value = channels;
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: pagination
|
||||||
|
misskeyApi('channels/featured', { limit: 20 }).then(channels => {
|
||||||
|
featuredChannels.value = channels;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addChannel() {
|
||||||
|
chatRouter.push('/channels/new');
|
||||||
|
}
|
||||||
|
function addAntenna() {
|
||||||
|
chatRouter.push('/my/antennas/create');
|
||||||
|
}
|
||||||
async function addList() {
|
async function addList() {
|
||||||
const success = await makeList();
|
const success = await makeList();
|
||||||
if (success) {
|
if (success) {
|
||||||
|
@ -224,13 +235,6 @@ function hideMenu() {
|
||||||
drawerMenuShowing.value = false;
|
drawerMenuShowing.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function more(ev: MouseEvent) {
|
|
||||||
os.popup(XLaunchPad, {
|
|
||||||
src: ev.currentTarget ?? ev.target,
|
|
||||||
all: true,
|
|
||||||
}, {}, 'closed');
|
|
||||||
}
|
|
||||||
|
|
||||||
function post() {
|
function post() {
|
||||||
os.post();
|
os.post();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
<template>
|
||||||
|
<div class="acemodlh _monospace">
|
||||||
|
<div>
|
||||||
|
<span v-text="y"></span>/<span v-text="m"></span>/<span v-text="d"></span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span v-text="hh"></span>
|
||||||
|
<span :style="{ visibility: showColon ? 'visible' : 'hidden' }">:</span>
|
||||||
|
<span v-text="mm"></span>
|
||||||
|
<span :style="{ visibility: showColon ? 'visible' : 'hidden' }">:</span>
|
||||||
|
<span v-text="ss"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
clock: null,
|
||||||
|
y: null,
|
||||||
|
m: null,
|
||||||
|
d: null,
|
||||||
|
hh: null,
|
||||||
|
mm: null,
|
||||||
|
ss: null,
|
||||||
|
showColon: true,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.tick();
|
||||||
|
this.clock = setInterval(this.tick, 1000);
|
||||||
|
},
|
||||||
|
beforeUnmount() {
|
||||||
|
clearInterval(this.clock);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
tick() {
|
||||||
|
const now = new Date();
|
||||||
|
this.y = now.getFullYear().toString();
|
||||||
|
this.m = (now.getMonth() + 1).toString().padStart(2, '0');
|
||||||
|
this.d = now.getDate().toString().padStart(2, '0');
|
||||||
|
this.hh = now.getHours().toString().padStart(2, '0');
|
||||||
|
this.mm = now.getMinutes().toString().padStart(2, '0');
|
||||||
|
this.ss = now.getSeconds().toString().padStart(2, '0');
|
||||||
|
this.showColon = now.getSeconds() % 2 === 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.acemodlh {
|
||||||
|
opacity: 0.7;
|
||||||
|
font-size: 0.85em;
|
||||||
|
line-height: 1em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,28 +1,21 @@
|
||||||
<template>
|
<template>
|
||||||
<header :class="$style.root">
|
<header class="dehvdgxo">
|
||||||
<div :class="$style.left">
|
<MkA v-user-preview="note.user.id" class="name" :to="userPage(note.user)">
|
||||||
<MkA v-user-preview="note.user.id" :class="$style.name" :to="userPage(note.user)">
|
<MkUserName :user="note.user"/>
|
||||||
<MkUserName :user="note.user"/>
|
</MkA>
|
||||||
|
<span v-if="note.user.isBot" class="is-bot">bot</span>
|
||||||
|
<span class="username"><MkAcct :user="note.user"/></span>
|
||||||
|
<div class="info">
|
||||||
|
<MkA class="created-at" :to="notePage(note)">
|
||||||
|
<MkTime :time="note.createdAt"/>
|
||||||
</MkA>
|
</MkA>
|
||||||
<span v-if="note.user.isBot" :class="$style.is-bot">bot</span>
|
<span v-if="note.visibility !== 'public'" class="visibility">
|
||||||
<span :class="$style.username"><MkAcct :user="note.user"/></span>
|
<i v-if="note.visibility === 'home'" class="ti ti-home"></i>
|
||||||
<div v-if="note.user.badgeRoles" :class="$style.badgeRoles">
|
<i v-else-if="note.visibility === 'followers'" class="ti ti-lock"></i>
|
||||||
<img v-for="(role, i) in note.user.badgeRoles" :key="i" v-tooltip="role.name" :class="$style.badgeRole" :src="role.iconUrl!"/>
|
<i v-else-if="note.visibility === 'specified'" class="ti ti-mail"></i>
|
||||||
</div>
|
</span>
|
||||||
</div>
|
<span v-if="note.localOnly" class="localOnly"><i class="ti ti-rocket-off"></i></span>
|
||||||
<div :class="$style.right">
|
<span v-if="note.channel" :title="note.channel.name"><i class="ti ti-device-tv"></i></span>
|
||||||
<div :class="$style.info">
|
|
||||||
<span v-if="note.visibility !== 'public'">
|
|
||||||
<i v-if="note.visibility === 'home'" class="ti ti-home"></i>
|
|
||||||
<i v-else-if="note.visibility === 'followers'" class="ti ti-lock"></i>
|
|
||||||
<i v-else-if="note.visibility === 'specified'" class="ti ti-mail"></i>
|
|
||||||
</span>
|
|
||||||
<span v-if="note.localOnly" :class="$style.localOnly"><i class="ti ti-rocket-off"></i></span>
|
|
||||||
<span v-if="note.channel" :title="note.channel.name"><i class="ti ti-device-tv"></i></span>
|
|
||||||
<MkA :class="$style.createdAt" :to="notePage(note)">
|
|
||||||
<MkTime :time="note.createdAt" colored/>
|
|
||||||
</MkA>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
</template>
|
</template>
|
||||||
|
@ -52,69 +45,51 @@ export default defineComponent({
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" scoped>
|
||||||
.root {
|
.dehvdgxo {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
|
|
||||||
.left {
|
> .name {
|
||||||
display: flex;
|
display: block;
|
||||||
margin-right: auto;
|
margin: 0 .5em 0 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: none;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.name {
|
> .is-bot {
|
||||||
display: block;
|
flex-shrink: 0;
|
||||||
margin: 0 .5em 0 0;
|
align-self: center;
|
||||||
padding: 0;
|
margin: 0 .5em 0 0;
|
||||||
overflow: hidden;
|
padding: 1px 6px;
|
||||||
font-size: 1em;
|
font-size: 80%;
|
||||||
font-weight: bold;
|
border: solid 0.5px var(--divider);
|
||||||
text-decoration: none;
|
border-radius: 3px;
|
||||||
text-overflow: ellipsis;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.isBot {
|
> .username {
|
||||||
flex-shrink: 0;
|
margin: 0 .5em 0 0;
|
||||||
align-self: center;
|
overflow: hidden;
|
||||||
margin: 0 .5em 0 0;
|
text-overflow: ellipsis;
|
||||||
padding: 1px 6px;
|
|
||||||
font-size: 80%;
|
|
||||||
border: solid 0.5px var(--divider);
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.username {
|
|
||||||
margin: 0 .5em 0 0;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.badgeRoles {
|
|
||||||
margin: 0 .5em 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.badgeRole {
|
|
||||||
height: 1.3em;
|
|
||||||
vertical-align: -20%;
|
|
||||||
|
|
||||||
& + .badgeRole {
|
|
||||||
margin-left: 0.2em;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.info {
|
> .info {
|
||||||
opacity: 0.7;
|
font-size: 0.9em;
|
||||||
|
opacity: 0.7;
|
||||||
|
|
||||||
> *:not(:last-child) {
|
> span {
|
||||||
margin-right: 8px;
|
margin-left: 8px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,56 +1,113 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="wrpstxzv">
|
<div class="wrpstxzv" :class="{ children }">
|
||||||
<MkAvatar class="avatar" :user="note.user" link preview/>
|
<div class="main">
|
||||||
<Mfm :text="summary" :plain="true" :nowrap="true" :author="note.user" :nyaize="'respect'" class="text"/>
|
<MkAvatar class="avatar" :user="note.user"/>
|
||||||
|
<div class="body">
|
||||||
|
<XNoteHeader class="header" :note="note" :mini="true"/>
|
||||||
|
<div class="body">
|
||||||
|
<p v-if="note.cw != null" class="cw">
|
||||||
|
<Mfm v-if="note.cw != ''" class="text" :text="note.cw" :author="note.user" :i="$i" :custom-emojis="note.emojis"/>
|
||||||
|
<MkCwButton v-model="showContent" :note="note"/>
|
||||||
|
</p>
|
||||||
|
<div v-show="note.cw == null || showContent" class="content">
|
||||||
|
<XSubNoteContent class="text" :note="note"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, shallowRef, watchEffect } from 'vue';
|
import { ref, shallowRef } from 'vue';
|
||||||
import * as misskey from 'misskey-js';
|
import * as misskey from 'misskey-js';
|
||||||
import { getNoteSummary } from '@/scripts/get-note-summary.js';
|
import XNoteHeader from './note-header.vue';
|
||||||
|
import XSubNoteContent from './sub-note-content.vue';
|
||||||
|
import MkCwButton from '@/components/MkCwButton.vue';
|
||||||
|
import { notePage } from '@/filters/note.js';
|
||||||
|
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { $i } from '@/account.js';
|
import { $i } from '@/account.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
note: misskey.entities.Note;
|
note: misskey.entities.Note;
|
||||||
showContent?: boolean | null;
|
detail?: boolean;
|
||||||
}>();
|
children?: boolean;
|
||||||
|
// how many notes are in between this one and the note being viewed in detail
|
||||||
const summary = ref(null);
|
depth?: number;
|
||||||
|
}>(), {
|
||||||
watchEffect(() => {
|
depth: 1,
|
||||||
if (props.showContent) {
|
children: false,
|
||||||
summary.value = getNoteSummary({
|
|
||||||
...props.note,
|
|
||||||
cw: null,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
summary.value = getNoteSummary(props.note);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let showContent = ref(false);
|
||||||
|
let replies: misskey.entities.Note[] = shallowRef([]);
|
||||||
|
|
||||||
|
if (props.detail) {
|
||||||
|
misskeyApi('notes/children', {
|
||||||
|
noteId: props.note.id,
|
||||||
|
limit: 5,
|
||||||
|
}).then(res => {
|
||||||
|
replies = res;
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.wrpstxzv {
|
.wrpstxzv {
|
||||||
display: flex;
|
padding: 16px 16px;
|
||||||
align-items: center;
|
font-size: 0.8em;
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
> .avatar {
|
&.children {
|
||||||
flex-shrink: 0;
|
padding: 10px 0 0 16px;
|
||||||
display: block;
|
font-size: 1em;
|
||||||
margin: 0 8px 0 0;
|
|
||||||
width: 28px;
|
|
||||||
height: 28px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
> .text {
|
> .main {
|
||||||
overflow: hidden;
|
display: flex;
|
||||||
flex-shrink: 1;
|
|
||||||
text-overflow: ellipsis;
|
> .avatar {
|
||||||
white-space: nowrap;
|
flex-shrink: 0;
|
||||||
|
display: block;
|
||||||
|
margin: 0 8px 0 0;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .body {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
|
> .header {
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .body {
|
||||||
|
> .cw {
|
||||||
|
cursor: default;
|
||||||
|
display: block;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
|
||||||
|
> .text {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .content {
|
||||||
|
> .text {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .reply {
|
||||||
|
border-left: solid 0.5px var(--divider);
|
||||||
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -5,35 +5,14 @@
|
||||||
ref="rootEl"
|
ref="rootEl"
|
||||||
v-hotkey="keymap"
|
v-hotkey="keymap"
|
||||||
class="vfzoeqcg"
|
class="vfzoeqcg"
|
||||||
:class="{ collapsed: renoteCollapsed, operating: operating }"
|
:class="{ renote: isRenote, operating: operating }"
|
||||||
:tabindex="!isDeleted ? '-1' : undefined"
|
:tabindex="!isDeleted ? '-1' : undefined"
|
||||||
>
|
>
|
||||||
<div v-if="appearNote.reply && !renoteCollapsed" class="header reply-to">
|
<XSub v-if="appearNote.reply && !renoteCollapsed" :note="appearNote.reply" class="reply-to"/>
|
||||||
<MkA class="icon" :to="`/notes/${appearNote.replyId}`"><i class="ti ti-corner-up-right"></i></MkA>
|
|
||||||
<XSub class="note" :note="appearNote.reply" :showContent="showContent"/>
|
|
||||||
</div>
|
|
||||||
<div v-if="pinned" class="info"><i class="ti ti-pin"></i> {{ i18n.ts.pinnedNote }}</div>
|
<div v-if="pinned" class="info"><i class="ti ti-pin"></i> {{ i18n.ts.pinnedNote }}</div>
|
||||||
<!--<div v-if="appearNote._prId_" class="tip"><i class="fas fa-bullhorn"></i> {{ i18n.ts.promotion }}<button class="_textButton hide" @click="readPromo()">{{ i18n.ts.hideThisNote }} <i class="ti ti-x"></i></button></div>-->
|
<!--<div v-if="appearNote._prId_" class="tip"><i class="fas fa-bullhorn"></i> {{ i18n.ts.promotion }}<button class="_textButton hide" @click="readPromo()">{{ i18n.ts.hideThisNote }} <i class="ti ti-x"></i></button></div>-->
|
||||||
<!--<div v-if="appearNote._featuredId_" class="tip"><i class="ti ti-bolt"></i> {{ i18n.ts.featured }}</div>-->
|
<!--<div v-if="appearNote._featuredId_" class="tip"><i class="ti ti-bolt"></i> {{ i18n.ts.featured }}</div>-->
|
||||||
<div v-if="isRenote && renoteCollapsed" class="collapsed-renote" @click="renoteCollapsed = false">
|
<div v-if="isRenote" class="renote">
|
||||||
<MkAvatar class="avatar" :user="note.user"/>
|
|
||||||
<i class="icon ti ti-repeat"></i>
|
|
||||||
<XSub class="note" :note="appearNote"/>
|
|
||||||
<div class="info">
|
|
||||||
<span v-if="note.visibility !== 'public'" class="visibility" :title="i18n.ts._visibility[note.visibility]">
|
|
||||||
<i v-if="note.visibility === 'home'" class="ti ti-home"></i>
|
|
||||||
<i v-else-if="note.visibility === 'followers'" class="ti ti-lock"></i>
|
|
||||||
<i v-else-if="note.visibility === 'specified'" ref="specified" class="ti ti-mail"></i>
|
|
||||||
</span>
|
|
||||||
<span v-if="note.localOnly" :title="i18n.ts._visibility['disableFederation']"><i class="ti ti-rocket-off"></i></span>
|
|
||||||
<span v-if="note.channel" :title="note.channel.name"><i class="ti ti-device-tv"></i></span>
|
|
||||||
<button ref="renoteTime" class="_button time" @click.stop="showRenoteMenu()">
|
|
||||||
<i class="ti ti-dots dropdownIcon"></i>
|
|
||||||
<MkTime :time="note.createdAt"/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="isRenote" class="header renote-header">
|
|
||||||
<MkAvatar class="avatar" :user="note.user"/>
|
<MkAvatar class="avatar" :user="note.user"/>
|
||||||
<i class="ti ti-repeat"></i>
|
<i class="ti ti-repeat"></i>
|
||||||
<I18n :src="i18n.ts.renotedBy" tag="span">
|
<I18n :src="i18n.ts.renotedBy" tag="span">
|
||||||
|
@ -44,6 +23,10 @@
|
||||||
</template>
|
</template>
|
||||||
</I18n>
|
</I18n>
|
||||||
<div class="info">
|
<div class="info">
|
||||||
|
<button ref="renoteTime" class="_button time" @click="showRenoteMenu()">
|
||||||
|
<i class="ti ti-dots dropdownIcon"></i>
|
||||||
|
<MkTime :time="note.createdAt"/>
|
||||||
|
</button>
|
||||||
<span v-if="note.visibility !== 'public'" class="visibility" :title="i18n.ts._visibility[note.visibility]">
|
<span v-if="note.visibility !== 'public'" class="visibility" :title="i18n.ts._visibility[note.visibility]">
|
||||||
<i v-if="note.visibility === 'home'" class="ti ti-home"></i>
|
<i v-if="note.visibility === 'home'" class="ti ti-home"></i>
|
||||||
<i v-else-if="note.visibility === 'followers'" class="ti ti-lock"></i>
|
<i v-else-if="note.visibility === 'followers'" class="ti ti-lock"></i>
|
||||||
|
@ -51,13 +34,14 @@
|
||||||
</span>
|
</span>
|
||||||
<span v-if="note.localOnly" :title="i18n.ts._visibility['disableFederation']"><i class="ti ti-rocket-off"></i></span>
|
<span v-if="note.localOnly" :title="i18n.ts._visibility['disableFederation']"><i class="ti ti-rocket-off"></i></span>
|
||||||
<span v-if="note.channel" :title="note.channel.name"><i class="ti ti-device-tv"></i></span>
|
<span v-if="note.channel" :title="note.channel.name"><i class="ti ti-device-tv"></i></span>
|
||||||
<button ref="renoteTime" class="_button time" @click="showRenoteMenu()">
|
|
||||||
<i class="ti ti-dots dropdownIcon"></i>
|
|
||||||
<MkTime :time="note.createdAt"/>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<article v-if="!renoteCollapsed" class="article" @contextmenu.stop="onContextmenu">
|
<div v-if="renoteCollapsed" class="collapsed-renote">
|
||||||
|
<MkAvatar class="avatar" :user="appearNote.user" link preview/>
|
||||||
|
<Mfm :text="getNoteSummary(appearNote)" :plain="true" :nowrap="true" :author="appearNote.user" :nyaize="'respect'" class="text" @click="renoteCollapsed = false"/>
|
||||||
|
</div>
|
||||||
|
<article v-else class="article" @contextmenu.stop="onContextmenu">
|
||||||
|
<!-- <div v-if="appearNote.channel" :class="$style.colorBar" :style="{ background: appearNote.channel.color }"></div> -->
|
||||||
<MkAvatar class="avatar" :user="appearNote.user"/>
|
<MkAvatar class="avatar" :user="appearNote.user"/>
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<XNoteHeader class="header" :note="appearNote" :mini="true"/>
|
<XNoteHeader class="header" :note="appearNote" :mini="true"/>
|
||||||
|
@ -65,11 +49,12 @@
|
||||||
<div class="body" style="container-type: inline-size;">
|
<div class="body" style="container-type: inline-size;">
|
||||||
<p v-if="appearNote.cw != null" class="cw">
|
<p v-if="appearNote.cw != null" class="cw">
|
||||||
<Mfm v-if="appearNote.cw != ''" class="text" :text="appearNote.cw" :author="appearNote.user" :nyaize="'respect'"/>
|
<Mfm v-if="appearNote.cw != ''" class="text" :text="appearNote.cw" :author="appearNote.user" :nyaize="'respect'"/>
|
||||||
<MkCwButton v-model="showContent" :text="appearNote.text" :renote="appearNote.renote" :files="appearNote.files" :poll="appearNote.poll" :small="true" />
|
<MkCwButton v-model="showContent" :text="appearNote.text" :renote="appearNote.renote" :files="appearNote.files" :poll="appearNote.poll"/>
|
||||||
</p>
|
</p>
|
||||||
<div v-show="appearNote.cw == null || showContent" class="content" :class="{ collapsed }">
|
<div v-show="appearNote.cw == null || showContent" class="content" :class="{ collapsed }">
|
||||||
<div class="text">
|
<div class="text">
|
||||||
<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
|
<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
|
||||||
|
<MkA v-if="appearNote.replyId" class="reply" :to="`/notes/${appearNote.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
|
||||||
<Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis"/>
|
<Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis"/>
|
||||||
<div v-if="translating || translation" class="translation">
|
<div v-if="translating || translation" class="translation">
|
||||||
<MkLoading v-if="translating" mini/>
|
<MkLoading v-if="translating" mini/>
|
||||||
|
@ -95,7 +80,7 @@
|
||||||
<MkA v-if="appearNote.channel && !inChannel" class="channel" :to="`/channels/${appearNote.channel.id}`"><i class="ti ti-device-tv"></i> {{ appearNote.channel.name }}</MkA>
|
<MkA v-if="appearNote.channel && !inChannel" class="channel" :to="`/channels/${appearNote.channel.id}`"><i class="ti ti-device-tv"></i> {{ appearNote.channel.name }}</MkA>
|
||||||
</div>
|
</div>
|
||||||
<MkReactionsViewer :note="appearNote"/>
|
<MkReactionsViewer :note="appearNote"/>
|
||||||
<footer class="footer">
|
<footer class="footer _panel">
|
||||||
<button v-tooltip="i18n.ts.reply" class="button _button" @click="reply()">
|
<button v-tooltip="i18n.ts.reply" class="button _button" @click="reply()">
|
||||||
<i class="ti ti-arrow-back-up"></i>
|
<i class="ti ti-arrow-back-up"></i>
|
||||||
<p v-if="appearNote.repliesCount > 0" class="count">{{ appearNote.repliesCount }}</p>
|
<p v-if="appearNote.repliesCount > 0" class="count">{{ appearNote.repliesCount }}</p>
|
||||||
|
@ -261,12 +246,6 @@ const renoteCollapsed = ref(
|
||||||
);
|
);
|
||||||
const operating = ref(false);
|
const operating = ref(false);
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
if (appearNote.value.channel && rootEl.value && !inChannel.value) {
|
|
||||||
rootEl.value.style.setProperty('--channelColor', appearNote.value.channel.color);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/* Overload FunctionにLintが対応していないのでコメントアウト
|
/* Overload FunctionにLintが対応していないのでコメントアウト
|
||||||
function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly: true): boolean;
|
function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly: true): boolean;
|
||||||
function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly: false): boolean | 'sensitiveMute';
|
function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly: false): boolean | 'sensitiveMute';
|
||||||
|
@ -582,32 +561,15 @@ function emitUpdReaction(emoji: string, delta: number) {
|
||||||
&:hover, &.operating {
|
&:hover, &.operating {
|
||||||
> .article > .main > .footer {
|
> .article > .main > .footer {
|
||||||
display: block;
|
display: block;
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
&::before {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&::before {
|
&.renote {
|
||||||
content: "";
|
background: rgba(128, 255, 0, 0.05);
|
||||||
position: absolute;
|
|
||||||
top: 0; left: 0;
|
|
||||||
width: 100%; height: 100%;
|
|
||||||
z-index: -1;
|
|
||||||
background: var(--channelColor);
|
|
||||||
opacity: 0.15;
|
|
||||||
filter: saturate(75%);
|
|
||||||
transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&::after {
|
&.highlighted {
|
||||||
content: "";
|
background: rgba(255, 128, 0, 0.05);
|
||||||
position: absolute;
|
|
||||||
top: 0; left: 0;
|
|
||||||
width: 5px; height: 100%;
|
|
||||||
z-index: -1;
|
|
||||||
background: var(--channelColor);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
> .info {
|
> .info {
|
||||||
|
@ -634,58 +596,19 @@ function emitUpdReaction(emoji: string, delta: number) {
|
||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .collapsed-renote {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding-bottom: 8px;
|
|
||||||
overflow: hidden;
|
|
||||||
gap: 0.5em;
|
|
||||||
padding: 12px 16px 12px 16px;
|
|
||||||
|
|
||||||
> .avatar {
|
|
||||||
flex-shrink: 0;
|
|
||||||
display: inline-block;
|
|
||||||
width: 28px;
|
|
||||||
height: 28px;
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .icon {
|
|
||||||
color: var(--renote);
|
|
||||||
}
|
|
||||||
|
|
||||||
> .note {
|
|
||||||
opacity: 0.7;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
> .header {
|
|
||||||
padding: 12px 16px 0px 16px;
|
|
||||||
line-height: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .reply-to {
|
> .reply-to {
|
||||||
display: flex;
|
opacity: 0.7;
|
||||||
align-items: center;
|
padding-bottom: 0;
|
||||||
|
|
||||||
> .icon {
|
|
||||||
color: var(--mention);
|
|
||||||
margin-right: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .note {
|
|
||||||
opacity: 0.7;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
> .renote-header {
|
> .renote, > .collapsed-renote {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
padding: 12px 16px 4px 16px;
|
||||||
line-height: 28px;
|
line-height: 28px;
|
||||||
white-space: pre;
|
white-space: pre;
|
||||||
|
color: var(--renote);
|
||||||
|
font-size: 0.9em;
|
||||||
|
|
||||||
> .avatar {
|
> .avatar {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
@ -697,7 +620,7 @@ function emitUpdReaction(emoji: string, delta: number) {
|
||||||
}
|
}
|
||||||
|
|
||||||
> i {
|
> i {
|
||||||
margin-right: 0.5em;
|
margin-right: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
> span {
|
> span {
|
||||||
|
@ -710,16 +633,11 @@ function emitUpdReaction(emoji: string, delta: number) {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
> .renote-header, > .collapsed-renote {
|
|
||||||
color: var(--renote);
|
|
||||||
|
|
||||||
> .info {
|
> .info {
|
||||||
margin-left: auto;
|
margin-left: 8px;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
> .time {
|
> .time {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
@ -730,12 +648,16 @@ function emitUpdReaction(emoji: string, delta: number) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> *:not(:last-child) {
|
> span {
|
||||||
margin-right: 8px;
|
margin-left: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> .renote + .article {
|
||||||
|
padding-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
> .article {
|
> .article {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 12px 16px;
|
padding: 12px 16px;
|
||||||
|
@ -802,6 +724,11 @@ function emitUpdReaction(emoji: string, delta: number) {
|
||||||
> .text {
|
> .text {
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
|
|
||||||
|
> .reply {
|
||||||
|
color: var(--accent);
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
> .rp {
|
> .rp {
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
font-style: oblique;
|
font-style: oblique;
|
||||||
|
@ -858,14 +785,11 @@ function emitUpdReaction(emoji: string, delta: number) {
|
||||||
|
|
||||||
> .footer {
|
> .footer {
|
||||||
display: none;
|
display: none;
|
||||||
overflow: clip;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0px;
|
top: 8px;
|
||||||
right: 0px;
|
right: 8px;
|
||||||
padding: 2px 8px;
|
padding: 0 6px;
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
background: var(--panel);
|
|
||||||
border-top-left-radius: var(--radius);
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
|
|
@ -1,286 +0,0 @@
|
||||||
<!--
|
|
||||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
-->
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<MkStickyContainer>
|
|
||||||
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
|
||||||
<MkSpacer :contentMax="700" :class="$style.main">
|
|
||||||
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
|
|
||||||
<div v-if="channel && tab === 'overview'" key="overview" class="_gaps">
|
|
||||||
<div class="_panel" :class="$style.bannerContainer">
|
|
||||||
<div :style="{ backgroundImage: channel.bannerUrl ? `url(${channel.bannerUrl})` : undefined }" :class="$style.banner">
|
|
||||||
<div :class="$style.bannerStatus">
|
|
||||||
<div><i class="ti ti-users ti-fw"></i><I18n :src="i18n.ts._channel.usersCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.usersCount }}</b></template></I18n></div>
|
|
||||||
<div><i class="ti ti-pencil ti-fw"></i><I18n :src="i18n.ts._channel.notesCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.notesCount }}</b></template></I18n></div>
|
|
||||||
</div>
|
|
||||||
<div v-if="channel.isSensitive" :class="$style.sensitiveIndicator">{{ i18n.ts.sensitive }}</div>
|
|
||||||
<div :class="$style.bannerFade"></div>
|
|
||||||
</div>
|
|
||||||
<div v-if="channel.description" :class="$style.description">
|
|
||||||
<Mfm :text="channel.description" :isNote="false"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<MkFoldableSection>
|
|
||||||
<template #header><i class="ti ti-pin ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.pinnedNotes }}</template>
|
|
||||||
<div v-if="channel.pinnedNotes && channel.pinnedNotes.length > 0" class="_gaps">
|
|
||||||
<MkNote v-for="note in channel.pinnedNotes" :key="note.id" class="_panel" :note="note"/>
|
|
||||||
</div>
|
|
||||||
</MkFoldableSection>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="tab === 'featured'" key="featured">
|
|
||||||
<MkNotes :pagination="featuredPagination"/>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="tab === 'search'" key="search">
|
|
||||||
<div class="_gaps">
|
|
||||||
<div>
|
|
||||||
<MkInput v-model="searchQuery" @enter="search()">
|
|
||||||
<template #prefix><i class="ti ti-search"></i></template>
|
|
||||||
</MkInput>
|
|
||||||
<MkButton primary rounded style="margin-top: 8px;" @click="search()">{{ i18n.ts.search }}</MkButton>
|
|
||||||
</div>
|
|
||||||
<MkNotes v-if="searchPagination" :key="searchKey" :pagination="searchPagination"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</MkHorizontalSwipe>
|
|
||||||
</MkSpacer>
|
|
||||||
<template #footer>
|
|
||||||
<div :class="$style.footer">
|
|
||||||
<MkSpacer :contentMax="700" :marginMin="16" :marginMax="16">
|
|
||||||
<div class="_buttonsCenter">
|
|
||||||
<MkButton inline rounded primary gradate @click="goToTl()"><i class="ti ti-pencil"></i> {{ i18n.ts.postToTheChannel }}</MkButton>
|
|
||||||
</div>
|
|
||||||
</MkSpacer>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</MkStickyContainer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { computed, watch, ref } from 'vue';
|
|
||||||
import * as Misskey from 'misskey-js';
|
|
||||||
import MkPostForm from '@/components/MkPostForm.vue';
|
|
||||||
import MkTimeline from '@/components/MkTimeline.vue';
|
|
||||||
import * as os from '@/os.js';
|
|
||||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
|
||||||
import { $i, iAmModerator } from '@/account.js';
|
|
||||||
import { i18n } from '@/i18n.js';
|
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
|
||||||
import { deviceKind } from '@/scripts/device-kind.js';
|
|
||||||
import MkNotes from '@/components/MkNotes.vue';
|
|
||||||
import { url } from '@/config.js';
|
|
||||||
import MkButton from '@/components/MkButton.vue';
|
|
||||||
import MkInput from '@/components/MkInput.vue';
|
|
||||||
import { defaultStore } from '@/store.js';
|
|
||||||
import MkNote from '@/components/MkNote.vue';
|
|
||||||
import MkInfo from '@/components/MkInfo.vue';
|
|
||||||
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
|
||||||
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
|
||||||
import { PageHeaderItem } from '@/types/page-header.js';
|
|
||||||
import { isSupportShare } from '@/scripts/navigator.js';
|
|
||||||
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
|
|
||||||
import { miLocalStorage } from '@/local-storage.js';
|
|
||||||
import { useRouter } from '@/router/supplier.js';
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
channelId: string;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const tab = ref('overview');
|
|
||||||
|
|
||||||
const channel = ref<Misskey.entities.Channel | null>(null);
|
|
||||||
const favorited = ref(false);
|
|
||||||
const searchQuery = ref('');
|
|
||||||
const searchPagination = ref();
|
|
||||||
const searchKey = ref('');
|
|
||||||
const featuredPagination = computed(() => ({
|
|
||||||
endpoint: 'notes/featured' as const,
|
|
||||||
limit: 10,
|
|
||||||
params: {
|
|
||||||
channelId: props.channelId,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
watch(() => props.channelId, async () => {
|
|
||||||
channel.value = await misskeyApi('channels/show', {
|
|
||||||
channelId: props.channelId,
|
|
||||||
});
|
|
||||||
favorited.value = channel.value.isFavorited ?? false;
|
|
||||||
}, { immediate: true });
|
|
||||||
|
|
||||||
function edit() {
|
|
||||||
router.push(`/channels/${channel.value?.id}/edit`);
|
|
||||||
}
|
|
||||||
|
|
||||||
function goToTl() {
|
|
||||||
router.push(`/channels/${channel.value?.id}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function search() {
|
|
||||||
if (!channel.value) return;
|
|
||||||
|
|
||||||
const query = searchQuery.value.toString().trim();
|
|
||||||
|
|
||||||
if (query == null) return;
|
|
||||||
|
|
||||||
searchPagination.value = {
|
|
||||||
endpoint: 'notes/search',
|
|
||||||
limit: 10,
|
|
||||||
params: {
|
|
||||||
query: query,
|
|
||||||
channelId: channel.value.id,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
searchKey.value = query;
|
|
||||||
}
|
|
||||||
|
|
||||||
const headerActions = computed(() => {
|
|
||||||
if (channel.value && channel.value.userId) {
|
|
||||||
const headerItems: PageHeaderItem[] = [];
|
|
||||||
|
|
||||||
headerItems.push({
|
|
||||||
icon: 'ti ti-link',
|
|
||||||
text: i18n.ts.copyUrl,
|
|
||||||
handler: async (): Promise<void> => {
|
|
||||||
if (!channel.value) {
|
|
||||||
console.warn('failed to copy channel URL. channel.value is null.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
copyToClipboard(`${url}/channels/${channel.value.id}`);
|
|
||||||
os.success();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isSupportShare()) {
|
|
||||||
headerItems.push({
|
|
||||||
icon: 'ti ti-share',
|
|
||||||
text: i18n.ts.share,
|
|
||||||
handler: async (): Promise<void> => {
|
|
||||||
if (!channel.value) {
|
|
||||||
console.warn('failed to share channel. channel.value is null.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
navigator.share({
|
|
||||||
title: channel.value.name,
|
|
||||||
text: channel.value.description ?? undefined,
|
|
||||||
url: `${url}/channels/${channel.value.id}`,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (($i && $i.id === channel.value.userId) || iAmModerator) {
|
|
||||||
headerItems.push({
|
|
||||||
icon: 'ti ti-settings',
|
|
||||||
text: i18n.ts.edit,
|
|
||||||
handler: edit,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return headerItems.length > 0 ? headerItems : null;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const headerTabs = computed(() => [{
|
|
||||||
key: 'overview',
|
|
||||||
title: i18n.ts.overview,
|
|
||||||
icon: 'ti ti-info-circle',
|
|
||||||
}, {
|
|
||||||
key: 'featured',
|
|
||||||
title: i18n.ts.featured,
|
|
||||||
icon: 'ti ti-bolt',
|
|
||||||
}, {
|
|
||||||
key: 'search',
|
|
||||||
title: i18n.ts.search,
|
|
||||||
icon: 'ti ti-search',
|
|
||||||
}]);
|
|
||||||
|
|
||||||
definePageMetadata(() => ({
|
|
||||||
title: channel.value ? channel.value.name : i18n.ts.channel,
|
|
||||||
icon: 'ti ti-device-tv',
|
|
||||||
}));
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" module>
|
|
||||||
.main {
|
|
||||||
min-height: calc(100cqh - (var(--stickyTop, 0px) + var(--stickyBottom, 0px)));
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
-webkit-backdrop-filter: var(--blur, blur(15px));
|
|
||||||
backdrop-filter: var(--blur, blur(15px));
|
|
||||||
background: var(--acrylicBg);
|
|
||||||
border-top: solid 0.5px var(--divider);
|
|
||||||
}
|
|
||||||
|
|
||||||
.bannerContainer {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.subscribe {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 1;
|
|
||||||
top: 16px;
|
|
||||||
left: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.favorite {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 1;
|
|
||||||
top: 16px;
|
|
||||||
right: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.banner {
|
|
||||||
position: relative;
|
|
||||||
height: 200px;
|
|
||||||
background-position: center;
|
|
||||||
background-size: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bannerFade {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 64px;
|
|
||||||
background: linear-gradient(0deg, var(--panel), var(--X15));
|
|
||||||
}
|
|
||||||
|
|
||||||
.bannerStatus {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 1;
|
|
||||||
bottom: 16px;
|
|
||||||
right: 16px;
|
|
||||||
padding: 8px 12px;
|
|
||||||
font-size: 80%;
|
|
||||||
background: rgba(0, 0, 0, 0.7);
|
|
||||||
border-radius: 6px;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description {
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sensitiveIndicator {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 1;
|
|
||||||
bottom: 16px;
|
|
||||||
left: 16px;
|
|
||||||
background: rgba(0, 0, 0, 0.7);
|
|
||||||
color: var(--warn);
|
|
||||||
border-radius: 6px;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 1em;
|
|
||||||
padding: 4px 7px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,26 +1,26 @@
|
||||||
<template>
|
<template>
|
||||||
<div :class="$style.root">
|
<div class="dbiokgaf">
|
||||||
<MkPageHeader @click="scrollToNew()" :actions="headerActions"/>
|
<MkPageHeader @click="goTop()"/>
|
||||||
<div v-if="timetravelTarget" :class="$style.info">
|
<div v-if="timetravelTarget" class="info">
|
||||||
<MkInfo>{{ i18n.ts.showingPastTimeline }} <button class="_textButton" @click="timetravel()">{{ i18n.ts.clear }}</button></MkInfo>
|
<MkInfo>{{ i18n.ts.showingPastTimeline }} <button class="_textButton clear" @click="timetravel()">{{ i18n.ts.clear }}</button></MkInfo>
|
||||||
</div>
|
</div>
|
||||||
<div :class="[$style.body, { [$style.reverse]: inChannel }]">
|
<div class="body" :class="{ reverse: inChannel }">
|
||||||
<div :class="$style.postform">
|
<div class="postform">
|
||||||
<div v-if="typers.length > 0" class="typers">
|
<div v-if="typers.length > 0" class="typers">
|
||||||
<I18n :src="i18n.ts.typingUsers" text-tag="span">
|
<I18n :src="i18n.ts.typingUsers" text-tag="span" class="users">
|
||||||
<template #users>
|
<template #users>
|
||||||
<b v-for="user in typers" :key="user.id" :class="$style.user">{{ user.username }}</b>
|
<b v-for="user in typers" :key="user.id" class="user">{{ user.username }}</b>
|
||||||
</template>
|
</template>
|
||||||
</I18n>
|
</I18n>
|
||||||
<MkEllipsis/>
|
<MkEllipsis/>
|
||||||
</div>
|
</div>
|
||||||
<XPostForm :channel="channel" :autofocus="!inChannel" />
|
<XPostForm :channel="channel" :autofocus="!inChannel" />
|
||||||
</div>
|
</div>
|
||||||
<div ref="scroller" :class="$style.tl">
|
<div ref="scroller" class="tl">
|
||||||
<div v-if="queue > 0" :class="$style.new" :style="{ width: width + 'px', top: inChannel ? null : top + 'px', bottom: inChannel ? bottom + 'px' : null }">
|
<div v-if="queue > 0" class="new" :style="{ width: width + 'px', top: inChannel ? null : top + 'px', bottom: inChannel ? bottom + 'px' : null }">
|
||||||
<button class="_buttonPrimary" @click="scrollToNew()">{{ i18n.ts.newNoteRecived }}</button>
|
<button class="_buttonPrimary" @click="scrollToNew()">{{ i18n.ts.newNoteRecived }}</button>
|
||||||
</div>
|
</div>
|
||||||
<XNotes ref="tlElement" :pagination="pagination" :reversed="inChannel" @queue="queueUpdated"/>
|
<XNotes ref="tlElement" class="tl" :pagination="pagination" :reversed="inChannel" @queue="queueUpdated"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -35,19 +35,13 @@ import XNotes from '../notes.vue';
|
||||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
import { useStream } from '@/stream.js';
|
import { useStream } from '@/stream.js';
|
||||||
import * as sound from '@/scripts/sound.js';
|
import * as sound from '@/scripts/sound.js';
|
||||||
import * as os from '@/os.js';
|
|
||||||
import { scrollToBottom, scrollToTop } from '@/scripts/scroll.js';
|
import { scrollToBottom, scrollToTop } from '@/scripts/scroll.js';
|
||||||
import XPostForm from '../post-form.vue';
|
import XPostForm from '../post-form.vue';
|
||||||
import MkInfo from '@/components/MkInfo.vue';
|
import MkInfo from '@/components/MkInfo.vue';
|
||||||
import MkChannelFollowButton from '@/components/MkChannelFollowButton.vue';
|
|
||||||
import { definePageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { $i } from '@/account.js';
|
import { $i } from '@/account.js';
|
||||||
import { useRouter } from '@/router/supplier.js';
|
|
||||||
import { miLocalStorage } from '@/local-storage.js';
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
tlSrc?: string;
|
tlSrc?: string;
|
||||||
|
@ -60,7 +54,6 @@ const props = withDefaults(defineProps<Props>(), {
|
||||||
const emit = defineEmits(['note']);
|
const emit = defineEmits(['note']);
|
||||||
|
|
||||||
const tlElement = ref();
|
const tlElement = ref();
|
||||||
const tab = ref('timeline');
|
|
||||||
const inChannel = computed(() => props.tlSrc == 'channel');
|
const inChannel = computed(() => props.tlSrc == 'channel');
|
||||||
provide('inChannel', inChannel);
|
provide('inChannel', inChannel);
|
||||||
|
|
||||||
|
@ -70,15 +63,6 @@ watch(() => props.channelId, async () => {
|
||||||
channel.value = await misskeyApi('channels/show', {
|
channel.value = await misskeyApi('channels/show', {
|
||||||
channelId: props.channelId,
|
channelId: props.channelId,
|
||||||
});
|
});
|
||||||
|
|
||||||
if ((channel.value.isFavorited || channel.value.isFollowing) && channel.value.lastNotedAt) {
|
|
||||||
const lastReadedAt: number = miLocalStorage.getItemAsJson(`channelLastReadedAt:${channel.value.id}`) ?? 0;
|
|
||||||
const lastNotedAt = Date.parse(channel.value.lastNotedAt);
|
|
||||||
|
|
||||||
if (lastNotedAt > lastReadedAt) {
|
|
||||||
miLocalStorage.setItemAsJson(`channelLastReadedAt:${channel.value.id}`, lastNotedAt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, { immediate: true });
|
}, { immediate: true });
|
||||||
|
|
||||||
|
@ -204,145 +188,91 @@ function scrollToNew() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const headerActions = computed(() => {
|
|
||||||
if (channel.value && channel.value.userId) {
|
|
||||||
const headerItems: PageHeaderItem[] = [];
|
|
||||||
|
|
||||||
headerItems.push({
|
|
||||||
icon: 'ti ti-star',
|
|
||||||
text: i18n.ts.favorite,
|
|
||||||
highlighted: channel.value.isFavorited,
|
|
||||||
handler: async () => {
|
|
||||||
if (channel.value.isFavorited) {
|
|
||||||
const confirm = await os.confirm({
|
|
||||||
type: 'warning',
|
|
||||||
text: i18n.ts.unfavoriteConfirm,
|
|
||||||
});
|
|
||||||
if (confirm.canceled) return;
|
|
||||||
await os.apiWithDialog('channels/unfavorite', {
|
|
||||||
channelId: channel.value.id,
|
|
||||||
});
|
|
||||||
channel.value.isFavorited = false;
|
|
||||||
} else {
|
|
||||||
await os.apiWithDialog('channels/favorite', {
|
|
||||||
channelId: channel.value.id,
|
|
||||||
});
|
|
||||||
channel.value.isFavorited = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
headerItems.push({
|
|
||||||
icon: channel.value.isFollowing ? 'ti ti-minus' : 'ti ti-plus',
|
|
||||||
text: channel.value.isFollowing ? i18n.ts.unfollow : i18n.ts.follow,
|
|
||||||
highlighted: channel.value.isFollowing,
|
|
||||||
handler: async () => {
|
|
||||||
if (channel.value.isFollowing) {
|
|
||||||
await os.apiWithDialog('channels/unfollow', {
|
|
||||||
channelId: channel.value.id,
|
|
||||||
});
|
|
||||||
channel.value.isFollowing = false;
|
|
||||||
} else {
|
|
||||||
await os.apiWithDialog('channels/follow', {
|
|
||||||
channelId: channel.value.id,
|
|
||||||
});
|
|
||||||
channel.value.isFollowing = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
headerItems.push({
|
|
||||||
icon: 'ti ti-info-circle',
|
|
||||||
text: i18n.ts.about,
|
|
||||||
handler: () => router.push(`/channels/${channel.value?.id}/about`)
|
|
||||||
});
|
|
||||||
|
|
||||||
return headerItems.length > 0 ? headerItems : null;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
definePageMetadata(() => metadata.value);
|
definePageMetadata(() => metadata.value);
|
||||||
provideReactiveMetadata(metadata);
|
provideReactiveMetadata(metadata);
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" scoped>
|
||||||
.root {
|
.dbiokgaf {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: clip;
|
overflow: clip;
|
||||||
contain: strict;
|
contain: strict;
|
||||||
background: var(--panel);
|
background: var(--panel);
|
||||||
}
|
|
||||||
|
|
||||||
.info {
|
> .info {
|
||||||
padding: 16px 16px 0 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.body {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
.postform {
|
|
||||||
padding: 16px 16px 0 16px;
|
padding: 16px 16px 0 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.reverse {
|
> .body {
|
||||||
flex-direction: column-reverse;
|
flex: 1;
|
||||||
.postform {
|
display: flex;
|
||||||
padding: 0 16px 16px 16px;
|
flex-direction: column;
|
||||||
|
overflow: auto;
|
||||||
|
overflow-y: scroll;
|
||||||
|
|
||||||
|
> .postform {
|
||||||
|
padding: 16px 16px 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.reverse {
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
> .postform {
|
||||||
|
padding: 0 16px 16px 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .tl {
|
||||||
|
position: relative;
|
||||||
|
padding: 16px 0;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
> .new {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1000;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
> button {
|
||||||
|
display: block;
|
||||||
|
margin: 16px auto;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 32px;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.tl {
|
.bottom {
|
||||||
position: relative;
|
padding: 0 16px 16px 16px;
|
||||||
padding: 16px 0;
|
position: relative;
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.new {
|
> .typers {
|
||||||
position: fixed;
|
position: absolute;
|
||||||
z-index: 1000;
|
bottom: 100%;
|
||||||
pointer-events: none;
|
padding: 0 8px 0 8px;
|
||||||
|
font-size: 0.9em;
|
||||||
|
background: var(--panel);
|
||||||
|
border-radius: 0 8px 0 0;
|
||||||
|
color: var(--fgTransparentWeak);
|
||||||
|
|
||||||
> button {
|
> .users {
|
||||||
display: block;
|
> .user + .user:before {
|
||||||
margin: 16px auto;
|
content: ", ";
|
||||||
padding: 8px 16px;
|
font-weight: normal;
|
||||||
border-radius: 32px;
|
}
|
||||||
pointer-events: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.bottom {
|
> .user:last-of-type:after {
|
||||||
padding: 0 16px 16px 16px;
|
content: " ";
|
||||||
position: relative;
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.typers {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 100%;
|
|
||||||
padding: 0 8px 0 8px;
|
|
||||||
font-size: 0.9em;
|
|
||||||
background: var(--panel);
|
|
||||||
border-radius: 0 8px 0 0;
|
|
||||||
color: var(--fgTransparentWeak);
|
|
||||||
|
|
||||||
.user + .user:before {
|
|
||||||
content: ", ";
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.user:last-of-type:after {
|
|
||||||
content: " ";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
Loading…
Reference in New Issue