Merge branch 'develop' into swn

This commit is contained in:
tamaina 2021-10-16 15:30:57 +09:00
commit 8899cda211
29 changed files with 484 additions and 218 deletions

View file

@ -14,17 +14,18 @@
### Improvements
- アカウント登録にメールアドレスの設定を必須にするオプション
- クライアント: アニメーションを減らす設定をメニューのアニメーションにも適用するように
- クライアント: 全体的なUIのブラッシュアップ
- クライアント: MFM関数構文のサジェストを実装
- クライアント: ノート本文を投稿フォーム内でプレビューできるように
- クライアント: 未読の通知のみ表示する機能
- クライアント: 通知ページで通知の種類によるフィルタ
- クライアント: アニメーションを減らす設定の適用範囲を拡充
- クライアント: 新しいダークテーマを追加
- クライアント: テーマコンパイラに hue と saturate 関数を追加
- ActivityPub: HTML -> MFMの変換を強化
- API: i/notifications に unreadOnly オプションを追加
- API: ap系のエンドポイントをログイン必須化+レートリミット追加
- Misskeyのコマンドラインオプションを廃止
- 代わりに環境変数で設定することができます
- MFM: Add tag syntaxes of bold <b></b> and strikethrough <s></s>
### Bugfixes
- Fix createDeleteAccountJob
@ -33,7 +34,17 @@
- クライアント: ヘッダーにタブが表示されている状態でタイトルをクリックしたときにタブ選択が表示されるのを修正
- クライアント: ユーザーページのタブが機能していない問題を修正
- クライアント: ピン留めユーザーの設定項目がない問題を修正
- クライアント: Deck UIにおいて、重ねたカラムの片方を畳んだ状態で右に出すと表示が壊れる問題を修正
- API: 管理者およびモデレーターをブロックできてしまう問題を修正
- MFM: Mentions in the link label are parsed as text
- MFM: Add a property to the URL node indicating whether it was enclosed in <>
- MFM: Disallows < and > in hashtags
### Changes
- 保守性やユーザビリティの観点から、Misskeyのコマンドラインオプションが削除されました。
- 必要であれば、代わりに環境変数で設定することができます
- MFM: パフォーマンス、保守性、構文誤認識抑制の観点から、旧関数構文のサポートが削除されました。
- 旧構文(`[foo bar]`)を使用せず、現行の構文(`$[foo bar]`)を使用してください。
## 12.91.0 (2021/09/22)

View file

@ -766,6 +766,7 @@ middle: "中"
low: "低"
emailNotConfiguredWarning: "メールアドレスの設定がされていません。"
ratio: "比率"
previewNoteText: "本文をプレビュー"
customCss: "カスタムCSS"
customCssWarn: "この設定は必ず知識のある方が行ってください。不適切な設定を行うとクライアントが正常に使用できなくなる恐れがあります。"
global: "グローバル"

View file

@ -1,7 +1,7 @@
<template>
<div class="fdidabkb" :class="{ slim: narrow, thin }" :style="{ background: bg }" @click="onClick" ref="el">
<div class="fdidabkb" :class="{ slim: narrow, thin: thin_ }" :style="{ background: bg }" @click="onClick" ref="el">
<template v-if="info">
<div class="titleContainer" @click="showTabsPopup">
<div class="titleContainer" @click="showTabsPopup" v-if="!hideTitle">
<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"/>
@ -17,7 +17,7 @@
</div>
</div>
</div>
<div class="tabs" v-if="!narrow">
<div class="tabs" v-if="!narrow || hideTitle">
<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>
@ -37,7 +37,7 @@
</template>
<script lang="ts">
import { computed, defineComponent, onMounted, onUnmounted, PropType, ref } from 'vue';
import { computed, defineComponent, onMounted, onUnmounted, PropType, ref, inject } from 'vue';
import * as tinycolor from 'tinycolor2';
import { popupMenu } from '@client/os';
import { url } from '@client/config';
@ -182,6 +182,8 @@ export default defineComponent({
showTabsPopup,
preventDrag,
onClick,
hideTitle: inject('shouldOmitHeaderTitle', false),
thin_: props.thin || inject('shouldHeaderThin', false)
};
},
});
@ -207,12 +209,16 @@ export default defineComponent({
text-align: center;
> .titleContainer {
flex: 1;
margin: 0 auto;
}
margin-left: var(--height);
> .buttons {
&.right {
margin-left: 0;
> *:first-child {
margin-left: auto;
}
> *:last-child {
margin-right: auto;
}
}
}

View file

@ -0,0 +1,70 @@
<template>
<div ref="root" :class="$style.root" :style="{ padding: margin + 'px' }">
<div ref="content" :class="$style.content">
<slot></slot>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, onMounted, onUnmounted, ref } from 'vue';
export default defineComponent({
props: {
contentMax: {
type: Number,
required: false,
default: null,
}
},
setup(props, context) {
let ro: ResizeObserver;
const root = ref<HTMLElement>(null);
const content = ref<HTMLElement>(null);
const margin = ref(0);
const adjust = (rect: { width: number; height: number; }) => {
if (rect.width > (props.contentMax || 500)) {
margin.value = 32;
} else {
margin.value = 12;
}
};
onMounted(() => {
ro = new ResizeObserver((entries) => {
adjust({
width: entries[0].borderBoxSize[0].inlineSize,
height: entries[0].borderBoxSize[0].blockSize,
});
});
ro.observe(root.value);
if (props.contentMax) {
content.value.style.maxWidth = `${props.contentMax}px`;
}
});
onUnmounted(() => {
ro.disconnect();
});
return {
root,
content,
margin,
};
},
});
</script>
<style lang="scss" module>
.root {
box-sizing: border-box;
width: 100%;
}
.content {
margin: 0 auto;
}
</style>

View file

@ -14,6 +14,7 @@ import loading from './global/loading.vue';
import error from './global/error.vue';
import ad from './global/ad.vue';
import header from './global/header.vue';
import spacer from './global/spacer.vue';
export default function(app: App) {
app.component('I18n', i18n);
@ -30,4 +31,5 @@ export default function(app: App) {
app.component('MkError', error);
app.component('MkAd', ad);
app.component('MkHeader', header);
app.component('MkSpacer', spacer);
}

View file

@ -2,7 +2,7 @@
<MkModal ref="modal" @click="$emit('click')" @closed="$emit('closed')">
<div class="hrmcaedk _window _narrow_" :style="{ width: `${width}px`, height: (height ? `min(${height}px, 100%)` : '100%') }">
<div class="header" @contextmenu="onContextmenu">
<button v-if="history.length > 0" class="_button" @click="back()"><i class="fas fa-arrow-left"></i></button>
<button v-if="history.length > 0" class="_button" @click="back()" v-tooltip="$ts.goBack"><i class="fas fa-arrow-left"></i></button>
<span v-else style="display: inline-block; width: 20px"></span>
<span v-if="pageInfo" class="title">
<i v-if="pageInfo.icon" class="icon" :class="pageInfo.icon"></i>
@ -44,7 +44,8 @@ export default defineComponent({
return {
navHook: (path) => {
this.navigate(path);
}
},
shouldHeaderThin: true,
};
},

View file

@ -80,7 +80,7 @@
</div>
<XPoll v-if="appearNote.poll" :note="appearNote" ref="pollViewer" class="poll"/>
<MkUrlPreview v-for="url in urls" :url="url" :key="url" :compact="true" :detail="true" class="url-preview"/>
<div class="renote" v-if="appearNote.renote"><XNotePreview :note="appearNote.renote"/></div>
<div class="renote" v-if="appearNote.renote"><XNoteSimple :note="appearNote.renote"/></div>
</div>
<MkA v-if="appearNote.channel && !inChannel" class="channel" :to="`/channels/${appearNote.channel.id}`"><i class="fas fa-satellite-dish"></i> {{ appearNote.channel.name }}</MkA>
</div>
@ -132,7 +132,7 @@ import * as mfm from 'mfm-js';
import { sum } from '../../prelude/array';
import XSub from './note.sub.vue';
import XNoteHeader from './note-header.vue';
import XNotePreview from './note-preview.vue';
import XNoteSimple from './note-simple.vue';
import XReactionsViewer from './reactions-viewer.vue';
import XMediaList from './media-list.vue';
import XCwButton from './cw-button.vue';
@ -153,7 +153,7 @@ export default defineComponent({
components: {
XSub,
XNoteHeader,
XNotePreview,
XNoteSimple,
XReactionsViewer,
XMediaList,
XCwButton,

View file

@ -1,15 +1,13 @@
<template>
<div class="yohlumlk" v-size="{ min: [350, 500] }">
<MkAvatar class="avatar" :user="note.user"/>
<div class="fefdfafb" v-size="{ min: [350, 500] }">
<MkAvatar class="avatar" :user="$i"/>
<div class="main">
<XNoteHeader class="header" :note="note" :mini="true"/>
<div class="header">
<MkUserName :user="$i"/>
</div>
<div class="body">
<p v-if="note.cw != null" class="cw">
<span class="text" v-if="note.cw != ''">{{ note.cw }}</span>
<XCwButton v-model="showContent" :note="note"/>
</p>
<div class="content" v-show="note.cw == null || showContent">
<XSubNote-content class="text" :note="note"/>
<div class="content">
<Mfm :text="text" :author="$i" :i="$i"/>
</div>
</div>
</div>
@ -18,35 +16,22 @@
<script lang="ts">
import { defineComponent } from 'vue';
import XNoteHeader from './note-header.vue';
import XSubNoteContent from './sub-note-content.vue';
import XCwButton from './cw-button.vue';
import * as os from '@client/os';
export default defineComponent({
components: {
XNoteHeader,
XSubNoteContent,
XCwButton,
},
props: {
note: {
type: Object,
text: {
type: String,
required: true
}
},
data() {
return {
showContent: false
};
}
});
</script>
<style lang="scss" scoped>
.yohlumlk {
.fefdfafb {
display: flex;
margin: 0;
padding: 0;

View file

@ -0,0 +1,113 @@
<template>
<div class="yohlumlk" v-size="{ min: [350, 500] }">
<MkAvatar class="avatar" :user="note.user"/>
<div class="main">
<XNoteHeader class="header" :note="note" :mini="true"/>
<div class="body">
<p v-if="note.cw != null" class="cw">
<span class="text" v-if="note.cw != ''">{{ note.cw }}</span>
<XCwButton v-model="showContent" :note="note"/>
</p>
<div class="content" v-show="note.cw == null || showContent">
<XSubNote-content class="text" :note="note"/>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import XNoteHeader from './note-header.vue';
import XSubNoteContent from './sub-note-content.vue';
import XCwButton from './cw-button.vue';
import * as os from '@client/os';
export default defineComponent({
components: {
XNoteHeader,
XSubNoteContent,
XCwButton,
},
props: {
note: {
type: Object,
required: true
}
},
data() {
return {
showContent: false
};
}
});
</script>
<style lang="scss" scoped>
.yohlumlk {
display: flex;
margin: 0;
padding: 0;
overflow: clip;
font-size: 0.95em;
&.min-width_350px {
> .avatar {
margin: 0 10px 0 0;
width: 44px;
height: 44px;
}
}
&.min-width_500px {
> .avatar {
margin: 0 12px 0 0;
width: 48px;
height: 48px;
}
}
> .avatar {
flex-shrink: 0;
display: block;
margin: 0 10px 0 0;
width: 40px;
height: 40px;
border-radius: 8px;
}
> .main {
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 {
cursor: default;
margin: 0;
padding: 0;
}
}
}
}
}
</style>

View file

@ -64,7 +64,7 @@
</div>
<XPoll v-if="appearNote.poll" :note="appearNote" ref="pollViewer" class="poll"/>
<MkUrlPreview v-for="url in urls" :url="url" :key="url" :compact="true" :detail="false" class="url-preview"/>
<div class="renote" v-if="appearNote.renote"><XNotePreview :note="appearNote.renote"/></div>
<div class="renote" v-if="appearNote.renote"><XNoteSimple :note="appearNote.renote"/></div>
<button v-if="collapsed" class="fade _button" @click="collapsed = false">
<span>{{ $ts.showMore }}</span>
</button>
@ -114,7 +114,7 @@ import * as mfm from 'mfm-js';
import { sum } from '../../prelude/array';
import XSub from './note.sub.vue';
import XNoteHeader from './note-header.vue';
import XNotePreview from './note-preview.vue';
import XNoteSimple from './note-simple.vue';
import XReactionsViewer from './reactions-viewer.vue';
import XMediaList from './media-list.vue';
import XCwButton from './cw-button.vue';
@ -134,7 +134,7 @@ export default defineComponent({
components: {
XSub,
XNoteHeader,
XNotePreview,
XNoteSimple,
XReactionsViewer,
XMediaList,
XCwButton,

View file

@ -14,7 +14,7 @@
</template>
</template>
<template #headerLeft>
<button v-if="history.length > 0" class="_button" @click="back()"><i class="fas fa-arrow-left"></i></button>
<button v-if="history.length > 0" class="_button" @click="back()" v-tooltip="$ts.goBack"><i class="fas fa-arrow-left"></i></button>
</template>
<div class="yrolvcoq _flat_">
<component :is="component" v-bind="props" :ref="changePage"/>
@ -46,7 +46,8 @@ export default defineComponent({
return {
navHook: (path) => {
this.navigate(path);
}
},
shouldHeaderThin: true,
};
},

View file

@ -1,6 +1,6 @@
<template>
<div class="gafaadew" :class="{ modal, _popup: modal }"
v-size="{ max: [500] }"
v-size="{ max: [310, 500] }"
@dragover.stop="onDragover"
@dragenter="onDragenter"
@dragleave="onDragleave"
@ -17,12 +17,13 @@
<span v-if="visibility === 'followers'"><i class="fas fa-unlock"></i></span>
<span v-if="visibility === 'specified'"><i class="fas fa-envelope"></i></span>
</button>
<button class="submit _buttonPrimary" :disabled="!canPost" @click="post" data-cy-open-post-form-submit>{{ submitText }}<i :class="reply ? 'fas fa-reply' : renote ? 'fas fa-quote-right' : 'fas fa-paper-plane'"></i></button>
<button class="_button preview" @click="showPreview = !showPreview" :class="{ active: showPreview }" v-tooltip="$ts.previewNoteText"><i class="fas fa-file-code"></i></button>
<button class="submit _buttonGradate" :disabled="!canPost" @click="post" data-cy-open-post-form-submit>{{ submitText }}<i :class="reply ? 'fas fa-reply' : renote ? 'fas fa-quote-right' : 'fas fa-paper-plane'"></i></button>
</div>
</header>
<div class="form" :class="{ fixed }">
<XNotePreview class="preview" v-if="reply" :note="reply"/>
<XNotePreview class="preview" v-if="renote" :note="renote"/>
<XNoteSimple class="preview" v-if="reply" :note="reply"/>
<XNoteSimple class="preview" v-if="renote" :note="renote"/>
<div class="with-quote" v-if="quoteId"><i class="fas fa-quote-left"></i> {{ $ts.quoteAttached }}<button @click="quoteId = null"><i class="fas fa-times"></i></button></div>
<div v-if="visibility === 'specified'" class="to-specified">
<span style="margin-right: 8px;">{{ $ts.recipient }}</span>
@ -40,6 +41,7 @@
<input v-show="withHashtags" ref="hashtags" class="hashtags" v-model="hashtags" :placeholder="$ts.hashtags" list="hashtags">
<XPostFormAttaches class="attaches" :files="files" @updated="updateFiles" @detach="detachFile" @changeSensitive="updateFileSensitive" @changeName="updateFileName"/>
<XPollEditor v-if="poll" :poll="poll" @destroyed="poll = null" @updated="onPollUpdate"/>
<XNotePreview class="preview" v-if="showPreview" :text="text"/>
<footer>
<button class="_button" @click="chooseFileFrom" v-tooltip="$ts.attachFile"><i class="fas fa-photo-video"></i></button>
<button class="_button" @click="togglePoll" :class="{ active: poll }" v-tooltip="$ts.poll"><i class="fas fa-poll-h"></i></button>
@ -61,6 +63,7 @@ import { defineComponent, defineAsyncComponent } from 'vue';
import insertTextAtCursor from 'insert-text-at-cursor';
import { length } from 'stringz';
import { toASCII } from 'punycode/';
import XNoteSimple from './note-simple.vue';
import XNotePreview from './note-preview.vue';
import * as mfm from 'mfm-js';
import { host, url } from '@client/config';
@ -80,6 +83,7 @@ import { defaultStore } from '@client/store';
export default defineComponent({
components: {
XNoteSimple,
XNotePreview,
XPostFormAttaches: defineAsyncComponent(() => import('./post-form-attaches.vue')),
XPollEditor: defineAsyncComponent(() => import('./poll-editor.vue')),
@ -160,6 +164,7 @@ export default defineComponent({
files: [],
poll: null,
useCw: false,
showPreview: false,
cw: null,
localOnly: this.$store.state.rememberNoteVisibility ? this.$store.state.localOnly : this.$store.state.defaultNoteLocalOnly,
visibility: (this.$store.state.rememberNoteVisibility ? this.$store.state.visibility : this.$store.state.defaultNoteVisibility) as typeof noteVisibilities[number],
@ -743,7 +748,7 @@ export default defineComponent({
> .visibility {
height: 34px;
width: 34px;
margin: 0 8px;
margin: 0 0 0 8px;
& + .localOnly {
margin-left: 0 !important;
@ -755,6 +760,24 @@ export default defineComponent({
opacity: 0.7;
}
> .preview {
display: inline-block;
padding: 0;
margin: 0 8px 0 0;
font-size: 16px;
width: 34px;
height: 34px;
border-radius: 6px;
&:hover {
background: var(--X5);
}
&.active {
color: var(--accent);
}
}
> .submit {
margin: 16px 16px 16px 0;
padding: 0 12px;
@ -762,6 +785,7 @@ export default defineComponent({
font-weight: bold;
vertical-align: bottom;
border-radius: 4px;
font-size: 0.9em;
&:disabled {
opacity: 0.7;
@ -940,5 +964,17 @@ export default defineComponent({
}
}
}
&.max-width_310px {
> .form {
> footer {
> button {
font-size: 14px;
width: 44px;
height: 44px;
}
}
}
}
}
</style>

View file

@ -188,11 +188,11 @@ export default defineComponent({
background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
&:not(:disabled):hover {
background: var(--X8);
background: linear-gradient(90deg, var(--X8), var(--X8));
}
&:not(:disabled):active {
background: var(--X8);
background: linear-gradient(90deg, var(--X8), var(--X8));
}
}

View file

@ -152,7 +152,7 @@ export default defineComponent({
> .item {
display: block;
position: relative;
padding: 8px 16px;
padding: 8px 18px;
width: 100%;
box-sizing: border-box;
white-space: nowrap;
@ -170,10 +170,9 @@ export default defineComponent({
left: 0;
right: 0;
margin: auto;
//width: calc(100% - 16px);
width: 100%;
width: calc(100% - 16px);
height: 100%;
//border-radius: 6px;
border-radius: 6px;
}
> * {
@ -243,12 +242,12 @@ export default defineComponent({
}
> i {
margin-right: 4px;
margin-right: 5px;
width: 20px;
}
> .avatar {
margin-right: 4px;
margin-right: 5px;
width: 20px;
height: 20px;
}

View file

@ -65,8 +65,8 @@ export default defineComponent({
align-items: center;
width: 100%;
box-sizing: border-box;
padding: 10px 16px 10px 14px;
border-radius: 999px;
padding: 10px 16px 10px 8px;
border-radius: 9px;
font-size: 0.9em;
&:hover {

View file

@ -23,12 +23,14 @@ export default defineComponent({
},
mounted() {
VanillaTilt.init(this.$el, {
reverse: true,
gyroscope: false,
scale: 1.1,
speed: 500,
});
if (this.$store.animation) {
VanillaTilt.init(this.$el, {
reverse: true,
gyroscope: false,
scale: 1.1,
speed: 500,
});
}
},
methods: {

View file

@ -2,77 +2,79 @@
<div>
<MkHeader :info="header"/>
<div class="lznhrdub _root">
<div v-if="tab === 'local'">
<div class="localfedi7 _block _isolated" v-if="meta && stats && tag == null" :style="{ backgroundImage: meta.bannerUrl ? `url(${meta.bannerUrl})` : null }">
<header><span>{{ $t('explore', { host: meta.name || 'Misskey' }) }}</span></header>
<div><span>{{ $t('exploreUsersCount', { count: num(stats.originalUsersCount) }) }}</span></div>
</div>
<template v-if="tag == null">
<MkFolder class="_gap" persist-key="explore-pinned-users">
<template #header><i class="fas fa-bookmark fa-fw" style="margin-right: 0.5em;"></i>{{ $ts.pinnedUsers }}</template>
<XUserList :pagination="pinnedUsers"/>
</MkFolder>
<MkFolder class="_gap" persist-key="explore-popular-users">
<template #header><i class="fas fa-chart-line fa-fw" style="margin-right: 0.5em;"></i>{{ $ts.popularUsers }}</template>
<XUserList :pagination="popularUsers"/>
</MkFolder>
<MkFolder class="_gap" persist-key="explore-recently-updated-users">
<template #header><i class="fas fa-comment-alt fa-fw" style="margin-right: 0.5em;"></i>{{ $ts.recentlyUpdatedUsers }}</template>
<XUserList :pagination="recentlyUpdatedUsers"/>
</MkFolder>
<MkFolder class="_gap" persist-key="explore-recently-registered-users">
<template #header><i class="fas fa-plus fa-fw" style="margin-right: 0.5em;"></i>{{ $ts.recentlyRegisteredUsers }}</template>
<XUserList :pagination="recentlyRegisteredUsers"/>
</MkFolder>
</template>
</div>
<div v-else-if="tab === 'remote'">
<div class="localfedi7 _block _isolated" v-if="tag == null" :style="{ backgroundImage: `url(/static-assets/client/fedi.jpg)` }">
<header><span>{{ $ts.exploreFediverse }}</span></header>
</div>
<MkFolder :foldable="true" :expanded="false" ref="tags" class="_gap">
<template #header><i class="fas fa-hashtag fa-fw" style="margin-right: 0.5em;"></i>{{ $ts.popularTags }}</template>
<div class="vxjfqztj">
<MkA v-for="tag in tagsLocal" :to="`/explore/tags/${tag.tag}`" :key="'local:' + tag.tag" class="local">{{ tag.tag }}</MkA>
<MkA v-for="tag in tagsRemote" :to="`/explore/tags/${tag.tag}`" :key="'remote:' + tag.tag">{{ tag.tag }}</MkA>
<MkSpacer :content-max="1200">
<div class="lznhrdub">
<div v-if="tab === 'local'">
<div class="localfedi7 _block _isolated" v-if="meta && stats && tag == null" :style="{ backgroundImage: meta.bannerUrl ? `url(${meta.bannerUrl})` : null }">
<header><span>{{ $t('explore', { host: meta.name || 'Misskey' }) }}</span></header>
<div><span>{{ $t('exploreUsersCount', { count: num(stats.originalUsersCount) }) }}</span></div>
</div>
</MkFolder>
<MkFolder v-if="tag != null" :key="`${tag}`" class="_gap">
<template #header><i class="fas fa-hashtag fa-fw" style="margin-right: 0.5em;"></i>{{ tag }}</template>
<XUserList :pagination="tagUsers"/>
</MkFolder>
<template v-if="tag == null">
<MkFolder class="_gap">
<template #header><i class="fas fa-chart-line fa-fw" style="margin-right: 0.5em;"></i>{{ $ts.popularUsers }}</template>
<XUserList :pagination="popularUsersF"/>
</MkFolder>
<MkFolder class="_gap">
<template #header><i class="fas fa-comment-alt fa-fw" style="margin-right: 0.5em;"></i>{{ $ts.recentlyUpdatedUsers }}</template>
<XUserList :pagination="recentlyUpdatedUsersF"/>
</MkFolder>
<MkFolder class="_gap">
<template #header><i class="fas fa-rocket fa-fw" style="margin-right: 0.5em;"></i>{{ $ts.recentlyDiscoveredUsers }}</template>
<XUserList :pagination="recentlyRegisteredUsersF"/>
</MkFolder>
</template>
</div>
<div v-else-if="tab === 'search'">
<div class="_isolated">
<MkInput v-model="query" :debounce="true" type="search">
<template #prefix><i class="fas fa-search"></i></template>
<template #label>{{ $ts.searchUser }}</template>
</MkInput>
<template v-if="tag == null">
<MkFolder class="_gap" persist-key="explore-pinned-users">
<template #header><i class="fas fa-bookmark fa-fw" style="margin-right: 0.5em;"></i>{{ $ts.pinnedUsers }}</template>
<XUserList :pagination="pinnedUsers"/>
</MkFolder>
<MkFolder class="_gap" persist-key="explore-popular-users">
<template #header><i class="fas fa-chart-line fa-fw" style="margin-right: 0.5em;"></i>{{ $ts.popularUsers }}</template>
<XUserList :pagination="popularUsers"/>
</MkFolder>
<MkFolder class="_gap" persist-key="explore-recently-updated-users">
<template #header><i class="fas fa-comment-alt fa-fw" style="margin-right: 0.5em;"></i>{{ $ts.recentlyUpdatedUsers }}</template>
<XUserList :pagination="recentlyUpdatedUsers"/>
</MkFolder>
<MkFolder class="_gap" persist-key="explore-recently-registered-users">
<template #header><i class="fas fa-plus fa-fw" style="margin-right: 0.5em;"></i>{{ $ts.recentlyRegisteredUsers }}</template>
<XUserList :pagination="recentlyRegisteredUsers"/>
</MkFolder>
</template>
</div>
<div v-else-if="tab === 'remote'">
<div class="localfedi7 _block _isolated" v-if="tag == null" :style="{ backgroundImage: `url(/static-assets/client/fedi.jpg)` }">
<header><span>{{ $ts.exploreFediverse }}</span></header>
</div>
<XUserList v-if="query" class="_gap" :pagination="searchPagination" ref="search"/>
<MkFolder :foldable="true" :expanded="false" ref="tags" class="_gap">
<template #header><i class="fas fa-hashtag fa-fw" style="margin-right: 0.5em;"></i>{{ $ts.popularTags }}</template>
<div class="vxjfqztj">
<MkA v-for="tag in tagsLocal" :to="`/explore/tags/${tag.tag}`" :key="'local:' + tag.tag" class="local">{{ tag.tag }}</MkA>
<MkA v-for="tag in tagsRemote" :to="`/explore/tags/${tag.tag}`" :key="'remote:' + tag.tag">{{ tag.tag }}</MkA>
</div>
</MkFolder>
<MkFolder v-if="tag != null" :key="`${tag}`" class="_gap">
<template #header><i class="fas fa-hashtag fa-fw" style="margin-right: 0.5em;"></i>{{ tag }}</template>
<XUserList :pagination="tagUsers"/>
</MkFolder>
<template v-if="tag == null">
<MkFolder class="_gap">
<template #header><i class="fas fa-chart-line fa-fw" style="margin-right: 0.5em;"></i>{{ $ts.popularUsers }}</template>
<XUserList :pagination="popularUsersF"/>
</MkFolder>
<MkFolder class="_gap">
<template #header><i class="fas fa-comment-alt fa-fw" style="margin-right: 0.5em;"></i>{{ $ts.recentlyUpdatedUsers }}</template>
<XUserList :pagination="recentlyUpdatedUsersF"/>
</MkFolder>
<MkFolder class="_gap">
<template #header><i class="fas fa-rocket fa-fw" style="margin-right: 0.5em;"></i>{{ $ts.recentlyDiscoveredUsers }}</template>
<XUserList :pagination="recentlyRegisteredUsersF"/>
</MkFolder>
</template>
</div>
<div v-else-if="tab === 'search'">
<div class="_isolated">
<MkInput v-model="query" :debounce="true" type="search">
<template #prefix><i class="fas fa-search"></i></template>
<template #label>{{ $ts.searchUser }}</template>
</MkInput>
</div>
<XUserList v-if="query" class="_gap" :pagination="searchPagination" ref="search"/>
</div>
</div>
</div>
</MkSpacer>
</div>
</template>
@ -214,12 +216,6 @@ export default defineComponent({
</script>
<style lang="scss" scoped>
.lznhrdub {
max-width: 1400px;
margin: 0 auto;
padding: 16px;
}
.localfedi7 {
color: #fff;
padding: 16px;

View file

@ -1,9 +1,11 @@
<template>
<div>
<MkHeader :info="header"/>
<div class="clupoqwt" v-size="{ min: [800] }">
<XNotifications class="notifications" @before="before" @after="after" :include-types="includeTypes" :unread-only="tab === 'unread'"/>
</div>
<MkSpacer :content-max="800">
<div class="clupoqwt">
<XNotifications class="notifications" @before="before" @after="after" :include-types="includeTypes" :unread-only="tab === 'unread'"/>
</div>
</MkSpacer>
</div>
</template>
@ -90,14 +92,5 @@ export default defineComponent({
<style lang="scss" scoped>
.clupoqwt {
&.min-width_800px {
background: var(--bg);
padding: 32px 0;
> .notifications {
max-width: 800px;
margin: 0 auto;
}
}
}
</style>

View file

@ -1,61 +1,65 @@
<template>
<div class="_root">
<transition name="fade" mode="out-in">
<div v-if="page" class="xcukqgmh" :key="page.id" v-size="{ max: [450] }">
<div class="_block main">
<!--
<div class="header">
<h1>{{ page.title }}</h1>
</div>
-->
<div class="banner">
<img :src="page.eyeCatchingImage.url" v-if="page.eyeCatchingImageId"/>
</div>
<div class="content">
<XPage :page="page"/>
</div>
<div class="actions">
<div class="like">
<MkButton class="button" @click="unlike()" v-if="page.isLiked" v-tooltip="$ts._pages.unlike" primary><i class="fas fa-heart"></i><span class="count" v-if="page.likedCount > 0">{{ page.likedCount }}</span></MkButton>
<MkButton class="button" @click="like()" v-else v-tooltip="$ts._pages.like"><i class="far fa-heart"></i><span class="count" v-if="page.likedCount > 0">{{ page.likedCount }}</span></MkButton>
<div>
<MkHeader :info="header"/>
<div class="_root">
<transition name="fade" mode="out-in">
<div v-if="page" class="xcukqgmh" :key="page.id" v-size="{ max: [450] }">
<div class="_block main">
<!--
<div class="header">
<h1>{{ page.title }}</h1>
</div>
<div class="other">
<button class="_button" @click="shareWithNote" v-tooltip="$ts.shareWithNote" v-click-anime><i class="fas fa-retweet fa-fw"></i></button>
<button class="_button" @click="share" v-tooltip="$ts.share" v-click-anime><i class="fas fa-share-alt fa-fw"></i></button>
-->
<div class="banner">
<img :src="page.eyeCatchingImage.url" v-if="page.eyeCatchingImageId"/>
</div>
<div class="content">
<XPage :page="page"/>
</div>
<div class="actions">
<div class="like">
<MkButton class="button" @click="unlike()" v-if="page.isLiked" v-tooltip="$ts._pages.unlike" primary><i class="fas fa-heart"></i><span class="count" v-if="page.likedCount > 0">{{ page.likedCount }}</span></MkButton>
<MkButton class="button" @click="like()" v-else v-tooltip="$ts._pages.like"><i class="far fa-heart"></i><span class="count" v-if="page.likedCount > 0">{{ page.likedCount }}</span></MkButton>
</div>
<div class="other">
<button class="_button" @click="shareWithNote" v-tooltip="$ts.shareWithNote" v-click-anime><i class="fas fa-retweet fa-fw"></i></button>
<button class="_button" @click="share" v-tooltip="$ts.share" v-click-anime><i class="fas fa-share-alt fa-fw"></i></button>
</div>
</div>
<div class="user">
<MkAvatar :user="page.user" class="avatar"/>
<div class="name">
<MkUserName :user="page.user" style="display: block;"/>
<MkAcct :user="page.user"/>
</div>
<MkFollowButton v-if="!$i || $i.id != page.user.id" :user="page.user" :inline="true" :transparent="false" :full="true" large class="koudoku"/>
</div>
<div class="links">
<MkA :to="`/@${username}/pages/${pageName}/view-source`" class="link">{{ $ts._pages.viewSource }}</MkA>
<template v-if="$i && $i.id === page.userId">
<MkA :to="`/pages/edit/${page.id}`" class="link">{{ $ts._pages.editThisPage }}</MkA>
<button v-if="$i.pinnedPageId === page.id" @click="pin(false)" class="link _textButton">{{ $ts.unpin }}</button>
<button v-else @click="pin(true)" class="link _textButton">{{ $ts.pin }}</button>
</template>
</div>
</div>
<div class="user">
<MkAvatar :user="page.user" class="avatar"/>
<div class="name">
<MkUserName :user="page.user" style="display: block;"/>
<MkAcct :user="page.user"/>
</div>
<MkFollowButton v-if="!$i || $i.id != page.user.id" :user="page.user" :inline="true" :transparent="false" :full="true" large class="koudoku"/>
</div>
<div class="links">
<MkA :to="`/@${username}/pages/${pageName}/view-source`" class="link">{{ $ts._pages.viewSource }}</MkA>
<template v-if="$i && $i.id === page.userId">
<MkA :to="`/pages/edit/${page.id}`" class="link">{{ $ts._pages.editThisPage }}</MkA>
<button v-if="$i.pinnedPageId === page.id" @click="pin(false)" class="link _textButton">{{ $ts.unpin }}</button>
<button v-else @click="pin(true)" class="link _textButton">{{ $ts.pin }}</button>
</template>
<div class="footer">
<div><i class="far fa-clock"></i> {{ $ts.createdAt }}: <MkTime :time="page.createdAt" mode="detail"/></div>
<div v-if="page.createdAt != page.updatedAt"><i class="far fa-clock"></i> {{ $ts.updatedAt }}: <MkTime :time="page.updatedAt" mode="detail"/></div>
</div>
<MkAd :prefer="['horizontal', 'horizontal-big']"/>
<MkContainer :max-height="300" :foldable="true" class="other">
<template #header><i class="fas fa-clock"></i> {{ $ts.recentPosts }}</template>
<MkPagination :pagination="otherPostsPagination" #default="{items}">
<MkPagePreview v-for="page in items" :page="page" :key="page.id" class="_gap"/>
</MkPagination>
</MkContainer>
</div>
<div class="footer">
<div><i class="far fa-clock"></i> {{ $ts.createdAt }}: <MkTime :time="page.createdAt" mode="detail"/></div>
<div v-if="page.createdAt != page.updatedAt"><i class="far fa-clock"></i> {{ $ts.updatedAt }}: <MkTime :time="page.updatedAt" mode="detail"/></div>
</div>
<MkAd :prefer="['horizontal', 'horizontal-big']"/>
<MkContainer :max-height="300" :foldable="true" class="other">
<template #header><i class="fas fa-clock"></i> {{ $ts.recentPosts }}</template>
<MkPagination :pagination="otherPostsPagination" #default="{items}">
<MkPagePreview v-for="page in items" :page="page" :key="page.id" class="_gap"/>
</MkPagination>
</MkContainer>
</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>
@ -97,6 +101,10 @@ export default defineComponent({
[symbols.PAGE_INFO]: computed(() => this.page ? {
title: computed(() => this.page.title || this.page.name),
avatar: this.page.user,
} : null),
header: computed(() => this.page ? {
title: computed(() => this.page.title || this.page.name),
avatar: this.page.user,
path: `/@${this.page.user.username}/pages/${this.page.name}`,
share: {
title: this.page.title || this.page.name,

View file

@ -271,6 +271,12 @@ export default defineComponent({
<style lang="scss" scoped>
.vvcocwet {
> .nav {
> .title {
margin: 16px;
font-size: 1.5em;
font-weight: bold;
}
> .info {
margin: 0 16px;
}
@ -295,6 +301,10 @@ export default defineComponent({
width: 32%;
box-sizing: border-box;
overflow: auto;
> .title {
margin: 24px;
}
}
> .main {

View file

@ -215,6 +215,10 @@ export default defineComponent({
}
}
> .post-form {
border-radius: var(--radius);
}
> .tl {
background: var(--bg);
border-radius: var(--radius);

View file

@ -202,6 +202,20 @@ hr {
}
}
._buttonGradate {
@extend ._buttonPrimary;
color: var(--fgOnAccent);
background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
&:not(:disabled):hover {
background: linear-gradient(90deg, var(--X8), var(--X8));
}
&:not(:disabled):active {
background: linear-gradient(90deg, var(--X8), var(--X8));
}
}
._help {
color: var(--accent);
cursor: help

View file

@ -20,7 +20,7 @@
renote: '@accent',
mention: 'rgb(212, 153, 76)',
mentionMe: 'rgb(212, 210, 76)',
hashtag: 'rgb(76, 212, 180)',
hashtag: '#5bcbb0',
link: '@accent',
},
}

View file

@ -56,7 +56,7 @@
</div>
<XPoll v-if="appearNote.poll" :note="appearNote" ref="pollViewer" class="poll"/>
<MkUrlPreview v-for="url in urls" :url="url" :key="url" :compact="true" :detail="false" class="url-preview"/>
<div class="renote" v-if="appearNote.renote"><XNotePreview :note="appearNote.renote"/></div>
<div class="renote" v-if="appearNote.renote"><XNoteSimple :note="appearNote.renote"/></div>
<button v-if="collapsed" class="fade _button" @click="collapsed = false">
<span>{{ $ts.showMore }}</span>
</button>
@ -106,7 +106,7 @@ import * as mfm from 'mfm-js';
import { sum } from '../../../prelude/array';
import XSub from './note.sub.vue';
import XNoteHeader from './note-header.vue';
import XNotePreview from './note-preview.vue';
import XNoteSimple from './note-preview.vue';
import XReactionsViewer from '@client/components/reactions-viewer.vue';
import XMediaList from '@client/components/media-list.vue';
import XCwButton from '@client/components/cw-button.vue';
@ -126,7 +126,7 @@ export default defineComponent({
components: {
XSub,
XNoteHeader,
XNotePreview,
XNoteSimple,
XReactionsViewer,
XMediaList,
XCwButton,

View file

@ -37,6 +37,11 @@ import { updateColumn, swapLeftColumn, swapRightColumn, swapUpColumn, swapDownCo
import { deckStore } from './deck-store';
export default defineComponent({
provide: {
shouldHeaderThin: true,
shouldOmitHeaderTitle: true,
},
props: {
column: {
type: Object,
@ -267,6 +272,7 @@ export default defineComponent({
height: 100%;
overflow: hidden;
contain: content;
box-shadow: 0 0 8px 0 var(--shadow);
&.draghover {
box-shadow: 0 0 0 2px var(--focus);
@ -320,15 +326,6 @@ export default defineComponent({
&.paged {
background: var(--bg) !important;
> header {
background: transparent;
box-shadow: none;
> button {
color: var(--fg);
}
}
}
> header {
@ -365,7 +362,7 @@ export default defineComponent({
}
> .toggleActive,
> .action > *,
> .action > ::v-deep(*),
> .menu {
z-index: 1;
width: var(--deckColumnHeaderHeight);

View file

@ -219,10 +219,20 @@ export function stackLeftColumn(id: Column['id']) {
export function popRightColumn(id: Column['id']) {
let layout = copy(deckStore.state.layout);
const i = deckStore.state.layout.findIndex(ids => ids.includes(id));
const affected = layout[i];
layout = layout.map(ids => ids.filter(_id => _id !== id));
layout.splice(i + 1, 0, [id]);
layout = layout.filter(ids => ids.length > 0);
deckStore.set('layout', layout);
const columns = copy(deckStore.state.columns);
for (const column of columns) {
if (affected.includes(column.id)) {
column.active = true;
}
}
deckStore.set('columns', columns);
saveDeck();
}

View file

@ -2,6 +2,7 @@
<XColumn v-if="deckStore.state.alwaysShowMainColumn || $route.name !== 'index'" :column="column" :is-stacked="isStacked">
<template #header>
<template v-if="pageInfo">
<i :class="pageInfo.icon"></i>
{{ pageInfo.title }}
</template>
</template>

View file

@ -80,6 +80,12 @@ export default defineComponent({
XWidgets: defineAsyncComponent(() => import('./default.widgets.vue')),
},
provide() {
return {
shouldHeaderThin: this.showMenuOnTop,
};
},
data() {
return {
pageInfo: null,

View file

@ -2,7 +2,7 @@
"short_name": "Misskey",
"name": "Misskey",
"start_url": "/",
"display": "standalone",
"display": "minimal-ui",
"background_color": "#313a42",
"theme_color": "#86b300",
"icons": [