Merge branch 'develop' into sw-notification-action

This commit is contained in:
tamaina 2021-08-05 02:59:56 +09:00
commit 0a812bf50a
42 changed files with 271 additions and 81 deletions

View file

@ -1,13 +1,32 @@
## Summary
<!-- お読みください
PRありがとうございます PRを作成する前に、以下をご確認ください:
可能であればタイトルに、以下で示すようなPRの種類が分かるキーワードをプリフィクスしてください。
fix / refactor / feat / enhance / perf / chore
また、PRの粒度が適切であることを確認してください。ひとつのPRに複数の種類の変更や関心を含めることは避けてください。
このPRによって解決されるIssueがある場合は、そのIssue IDを本文内に記入してください。
CHANGELOG.mdに変更点を追記してください。リファクタリングなど、利用者に影響を与えない変更についてはこの限りではありません。
機能追加やバグ修正をした場合は、可能であればテストケースを追加してください。
ご協力ありがとうございます🤗
-->
<!-- README
Thank you for your PR! Before creating a PR, please check the following:
If possible, prefix the title with a keyword that identifies the type of this PR, as shown below.
fix / refactor / feat / enhance / perf / chore
Also, make sure that the granularity of this PR is appropriate. Please do not include more than one type of change or interest in a single PR.
If there is an issue to be resolved by this PR, please include the Issue ID in the text.
Please add the summary of the changes to CHANGELOG.md. However, this is not necessary for changes that do not affect the users, such as refactoring.
If you have added a feature or fixed a bug, please add a test case if possible.
Thanks for your cooperation 🤗
-->
<!--
-
- * Please describe your changes here *
-
- If you are going to resolve some issue, please add this context.
- Resolve #ISSUE_NUMBER
-
- If you are going to fix some bug issue, please add this context.
- Fix #ISSUE_NUMBER
-
-->
# What
<!-- このPRで何をしたのか どう変わるのか? -->
<!-- What did you do with this PR? How will it change things? -->
# Why
<!-- なぜそうするのか? どういう意図なのか? 何が困っているのか? -->
<!-- Why do you do it? What are your intentions? What is the problem? -->
# Additional info (optional)
<!-- テスト観点など -->
<!-- Test perspective, etc -->

View file

@ -766,6 +766,12 @@ customCssWarn: "この設定は必ず知識のある方が行ってください
global: "グローバル"
squareAvatars: "アイコンを四角形で表示"
_docs:
continueReading: "続きを読む"
features: "機能"
generalTopics: "一般的なトピック"
advancedTopics: "高度なトピック"
_ad:
back: "戻る"
reduceFrequencyOfThisAd: "この広告の表示頻度を下げる"

View file

@ -10,6 +10,7 @@
<span class="separator" v-if="folder != null"><i class="fas fa-angle-right"></i></span>
<span class="folder current" v-if="folder != null">{{ folder.name }}</span>
</div>
<button @click="showMenu" class="menu _button"><i class="fas fa-ellipsis-h"></i></button>
</nav>
<div class="main" :class="{ uploading: uploadings.length > 0, fetching }"
ref="main"
@ -627,8 +628,12 @@ export default defineComponent({
}];
},
onContextmenu(e) {
os.contextMenu(this.getMenu(), e);
showMenu(ev) {
os.modalMenu(this.getMenu(), ev.currentTarget || ev.target);
},
onContextmenu(ev) {
os.contextMenu(this.getMenu(), ev);
},
}
});
@ -641,7 +646,7 @@ export default defineComponent({
height: 100%;
> nav {
display: block;
display: flex;
z-index: 2;
width: 100%;
padding: 0 8px;
@ -696,6 +701,10 @@ export default defineComponent({
}
}
}
> .menu {
margin-left: auto;
}
}
> .main {

View file

@ -107,6 +107,7 @@ export default defineComponent({
padding: 32px;
max-width: 800px;
margin: 0 auto;
background: var(--panel);
&.max-width_500px {
padding: 16px;

View file

@ -1,14 +1,38 @@
<template>
<div>
<main class="_section">
<div class="_content">
<ul>
<li v-for="doc in docs" :key="doc.path">
<MkA :to="`/docs/${doc.path}`">{{ doc.title }}</MkA>
</li>
</ul>
<div class="vtaihdtm">
<div class="search">
<MkInput v-model:value="query" :debounce="true" type="search"><template #icon><i class="fas fa-search"></i></template><span>{{ $ts.search }}</span></MkInput>
</div>
<MkFolder>
<template #header>{{ $ts._docs.generalTopics }}</template>
<div class="docs">
<MkA v-for="doc in docs.filter(doc => doc.path.startsWith('general/'))" :key="doc.path" :to="`/docs/${doc.path}`" class="doc">
<div class="title">{{ doc.title }}</div>
<div class="summary">{{ doc.summary }}</div>
<div class="read">{{ $ts._docs.continueReading }}</div>
</MkA>
</div>
</main>
</MkFolder>
<MkFolder>
<template #header>{{ $ts._docs.features }}</template>
<div class="docs">
<MkA v-for="doc in docs.filter(doc => doc.path.startsWith('features/'))" :key="doc.path" :to="`/docs/${doc.path}`" class="doc">
<div class="title">{{ doc.title }}</div>
<div class="summary">{{ doc.summary }}</div>
<div class="read">{{ $ts._docs.continueReading }}</div>
</MkA>
</div>
</MkFolder>
<MkFolder>
<template #header>{{ $ts._docs.advancedTopics }}</template>
<div class="docs">
<MkA v-for="doc in docs.filter(doc => doc.path.startsWith('advanced/'))" :key="doc.path" :to="`/docs/${doc.path}`" class="doc">
<div class="title">{{ doc.title }}</div>
<div class="summary">{{ doc.summary }}</div>
<div class="read">{{ $ts._docs.continueReading }}</div>
</MkA>
</div>
</MkFolder>
</div>
</template>
@ -16,8 +40,15 @@
import { defineComponent } from 'vue';
import { url, lang } from '@client/config';
import * as symbols from '@client/symbols';
import MkFolder from '@client/components/ui/folder.vue';
import MkInput from '@client/components/ui/input.vue';
export default defineComponent({
components: {
MkFolder,
MkInput,
},
data() {
return {
[symbols.PAGE_INFO]: {
@ -25,6 +56,15 @@ export default defineComponent({
icon: 'fas fa-question-circle'
},
docs: [],
query: null,
}
},
watch: {
query() {
fetch(`${url}/docs.json?lang=${lang}&q=${this.query}`).then(res => res.json()).then(docs => {
this.docs = docs;
});
}
},
@ -35,3 +75,48 @@ export default defineComponent({
},
});
</script>
<style lang="scss" scoped>
.vtaihdtm {
background: var(--panel);
> .search {
padding: 8px;
}
.docs {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(270px, 1fr));
grid-gap: 12px;
margin: var(--margin);
> .doc {
display: inline-block;
padding: 16px;
border: solid 1px var(--divider);
border-radius: 6px;
&:hover {
border: solid 1px var(--accent);
text-decoration: none;
}
> .title {
font-weight: bold;
}
> .summary {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-size: 0.9em;
}
> .read {
color: var(--link);
font-size: 0.9em;
}
}
}
}
</style>

View file

@ -20,7 +20,6 @@ export default defineComponent({
[symbols.PAGE_INFO]: {
title: computed(() => this.folder ? this.folder.name : this.$ts.drive),
icon: 'fas fa-cloud',
menu: () => this.$refs.drive.getMenu()
},
folder: null,
};

View file

@ -167,13 +167,13 @@ export default defineComponent({
},
async resetPassword() {
os.apiWithDialog('admin/reset-password', {
const { password } = await os.api('admin/reset-password', {
userId: this.user.id,
}, undefined, ({ password }) => {
os.dialog({
type: 'success',
text: this.$t('newPasswordIs', { password })
});
});
os.dialog({
type: 'success',
text: this.$t('newPasswordIs', { password })
});
},

View file

@ -237,8 +237,8 @@ import Progress from '@client/scripts/loading';
import { parseAcct } from '@/misc/acct';
import { getScrollPosition } from '@client/scripts/scroll';
import { getUserMenu } from '@client/scripts/get-user-menu';
import number from '../../filters/number';
import { userPage, acct as getAcct } from '../../filters/user';
import number from '@client/filters/number';
import { userPage, acct as getAcct } from '@client/filters/user';
import * as os from '@client/os';
import * as symbols from '@client/symbols';
@ -282,7 +282,6 @@ export default defineComponent({
share: {
title: this.user.name,
},
menu: () => getUserMenu(this.user),
} : null),
user: null,
error: null,

View file

@ -31,7 +31,7 @@ export const router = createRouter({
{ path: '/docs', component: page('docs') },
{ path: '/theme-editor', component: page('theme-editor') },
{ path: '/advanced-theme-editor', component: page('advanced-theme-editor') },
{ path: '/docs/:doc', component: page('doc'), props: route => ({ doc: route.params.doc }) },
{ path: '/docs/:doc(.*)', component: page('doc'), props: route => ({ doc: route.params.doc }) },
{ path: '/explore', component: page('explore') },
{ path: '/explore/tags/:tag', props: true, component: page('explore') },
{ path: '/search', component: page('search') },

View file

@ -94,7 +94,11 @@ export default (opts) => ({
for (let i = 0; i < items.length; i++) {
const item = items[i];
markRaw(item);
if (i === 3) item._shouldInsertAd_ = true;
if (this.pagination.reversed) {
if (i === items.length - 2) item._shouldInsertAd_ = true;
} else {
if (i === 3) item._shouldInsertAd_ = true;
}
}
if (!this.pagination.noPaging && (items.length > (this.pagination.limit || 10))) {
items.pop();
@ -133,7 +137,11 @@ export default (opts) => ({
for (let i = 0; i < items.length; i++) {
const item = items[i];
markRaw(item);
if (i === 10) item._shouldInsertAd_ = true;
if (this.pagination.reversed) {
if (i === items.length - 9) item._shouldInsertAd_ = true;
} else {
if (i === 10) item._shouldInsertAd_ = true;
}
}
if (items.length > SECOND_FETCH_LIMIT) {
items.pop();

View file

@ -239,8 +239,7 @@ hr {
._panel {
background: var(--panel);
border-radius: var(--radius);
//border: var(--panelBorder);
box-shadow: var(--panelShadow);
border: var(--panelBorder);
overflow: clip;
}
@ -357,7 +356,7 @@ hr {
._flat_ {
--root-margin: 0px;
--baseContentWidth: 100%;
--panelShadow: none;
--panelBorder: none;
._block {
//border-top: solid 0.5px var(--divider);

View file

@ -26,8 +26,7 @@
panelHeaderBg: ':lighten<3<@panel',
panelHeaderFg: '@fg',
panelHeaderDivider: 'rgba(0, 0, 0, 0)',
panelBorder: 'rgba(0, 0, 0, 0)',
panelShadow: '" 0 8px 24px rgba(0, 0, 0, 0.12)',
panelBorder: '" solid 1px var(--divider)',
acrylicPanel: ':alpha<0.5<@panel',
shadow: 'rgba(0, 0, 0, 0.3)',
header: ':alpha<0.7<@panel',

View file

@ -26,8 +26,7 @@
panelHeaderBg: ':lighten<3<@panel',
panelHeaderFg: '@fg',
panelHeaderDivider: 'rgba(0, 0, 0, 0)',
panelBorder: 'rgba(0, 0, 0, 0)',
panelShadow: '" 0 8px 24px rgb(21 43 75 / 8%)',
panelBorder: '" solid 1px var(--divider)',
acrylicPanel: ':alpha<0.5<@panel',
shadow: 'rgba(0, 0, 0, 0.1)',
header: ':alpha<0.7<@panel',

View file

@ -39,8 +39,7 @@
navHoverFg: ':lighten<17<@fg',
dateLabelFg: '@fg',
inputBorder: '#959da2',
panelBorder: 'rgba(0, 0, 0, 0)',
panelShadow: '" 0 8px 24px rgba(0, 0, 0, 0.12)',
panelBorder: '" solid 1px var(--divider)',
accentDarken: ':darken<10<@accent',
acrylicPanel: ':alpha<0.5<@panel',
navIndicator: '@accent',

View file

@ -11,7 +11,6 @@
panel: '#131313',
panelHeaderBg: '@panel',
panelHeaderDivider: '@divider',
panelShadow: '" 0 8px 24px rgb(0 0 0 / 25%)',
shadow: 'rgba(255, 255, 255, 0.05)',
modalBg: 'rgba(255, 255, 255, 0.1)',
messageBg: '#1d1d1d',

View file

@ -13,7 +13,6 @@
fgHighlighted: '#fff',
divider: 'rgba(255, 255, 255, 0.14)',
panel: '#2d2d2d',
panelShadow: '" 0 8px 24px rgb(0 0 0 / 25%)',
panelHeaderBg: '@panel',
panelHeaderDivider: '@divider',
header: ':alpha<0.7<@panel',

View file

@ -13,7 +13,6 @@
fgHighlighted: '#fff',
divider: 'rgba(255, 255, 255, 0.14)',
panel: 'rgb(41, 43, 41)',
panelShadow: '" 0 8px 24px rgb(0 0 0 / 25%)',
infoFg: '@fg',
infoBg: '#333c3b',
navBg: '#141714',

View file

@ -14,7 +14,6 @@
header: ':alpha<0.7<@panel',
navBg: '#fff',
panel: '#fff',
panelShadow: '" 0 8px 24px rgb(21 43 75 / 8%)',
panelHeaderDivider: '@divider',
messageBg: '#dedede',
},

View file

@ -42,8 +42,7 @@
navHoverFg: ':darken<17<@fg',
dateLabelFg: '@fg',
inputBorder: '#dae0e4',
panelBorder: 'rgba(0, 0, 0, 0)',
panelShadow: '" 0 8px 24px rgb(21 43 75 / 8%)',
panelBorder: '" solid 1px var(--divider)',
accentDarken: ':darken<10<@accent',
acrylicPanel: ':alpha<0.5<@panel',
navIndicator: '@accent',

View file

@ -1,17 +1,23 @@
<script lang="ts">
import { defineComponent, h, TransitionGroup } from 'vue';
import { defineComponent, h, PropType, TransitionGroup } from 'vue';
import MkAd from '@client/components/global/ad.vue';
export default defineComponent({
props: {
items: {
type: Array,
type: Array as PropType<{ id: string; createdAt: string; _shouldInsertAd_: boolean; }[]>,
required: true,
},
reversed: {
type: Boolean,
required: false,
default: false
}
},
ad: {
type: Boolean,
required: false,
default: false
},
},
methods: {
@ -66,7 +72,15 @@ export default defineComponent({
return [el, separator];
} else {
return el;
if (this.ad && item._shouldInsertAd_) {
return [h(MkAd, {
class: 'a', // advertise()
key: item.id + ':ad',
prefer: ['horizontal', 'horizontal-big'],
}), el];
} else {
return el;
}
}
}));
},

View file

@ -55,6 +55,7 @@
<MkA to="/my/favorites" class="item"><i class="fas fa-star icon"></i>{{ $ts.favorites }}</MkA>
</div>
</div>
<MkAd class="a" prefer="square"/>
</div>
<footer class="footer">
<div class="left">
@ -64,7 +65,7 @@
</div>
<div class="right">
<button class="_button item search" @click="search" v-tooltip="$ts.search">
<i class="fas fa-search"></i>
<i class="fas fa-search icon"></i>
</button>
<MkA class="item" to="/settings" v-tooltip="$ts.settings"><i class="fas fa-cog icon"></i></MkA>
</div>
@ -351,7 +352,7 @@ export default defineComponent({
flex-direction: column;
width: 250px;
height: 100vh;
border-right: solid 0.5px var(--divider);
border-right: solid 4px var(--divider);
> .header, > .footer {
$padding: 8px;
@ -373,7 +374,7 @@ export default defineComponent({
> .left, > .right {
> .item, > .menu {
display: inline-block;
display: inline-flex;
vertical-align: middle;
height: ($header-height - ($padding * 2));
width: ($header-height - ($padding * 2));
@ -387,11 +388,6 @@ export default defineComponent({
}
> .icon {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
}
@ -503,6 +499,10 @@ export default defineComponent({
}
}
}
> .a {
margin: 12px;
}
}
}
@ -596,7 +596,7 @@ export default defineComponent({
> .side {
width: 350px;
border-left: solid 0.5px var(--divider);
border-left: solid 4px var(--divider);
&.widgets.sideViewOpening {
@media (max-width: 1400px) {

View file

@ -16,7 +16,7 @@
</MkButton>
</div>
<XList ref="notes" :items="notes" v-slot="{ item: note }" :direction="reversed ? 'up' : 'down'" :reversed="reversed">
<XList ref="notes" :items="notes" v-slot="{ item: note }" :direction="reversed ? 'up' : 'down'" :reversed="reversed" :ad="true">
<XNote :note="note" @update:note="updated(note, $event)" :key="note._featuredId_ || note._prId_ || note.id"/>
</XList>

View file

@ -5,7 +5,7 @@
<XHeader class="title" :info="pageInfo" :with-back="false" :center="false"/>
<button class="_button" @click="close()"><i class="fas fa-times"></i></button>
</header>
<component :is="component" v-bind="props" :ref="changePage"/>
<component :is="component" v-bind="props" :ref="changePage" class="_flat_"/>
</div>
</template>

View file

@ -64,7 +64,7 @@ export default defineComponent({
<style lang="scss" scoped>
.wtdtxvec {
--margin: 8px;
--panelShadow: none;
--panelBorder: none;
padding: 0 var(--margin);
}

View file

@ -143,7 +143,7 @@ export default defineComponent({
},
attachSticky(ref) {
const sticky = new StickySidebar(this.$refs[ref], this.$store.state.menuDisplay === 'top' ? 1 : 16, this.$store.state.menuDisplay === 'top' ? 60 : 0); // TODO: 60px
const sticky = new StickySidebar(this.$refs[ref], this.$store.state.menuDisplay === 'top' ? 0 : 16, this.$store.state.menuDisplay === 'top' ? 60 : 0); // TODO: 60px
window.addEventListener('scroll', () => {
sticky.calc(window.scrollY);
}, { passive: true });
@ -230,8 +230,6 @@ export default defineComponent({
$widgets-hide-threshold: 1200px;
$nav-icon-only-width: 78px; // TODO:
--panelShadow: 0 0 0 1px var(--divider);
// 100vh ... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
min-height: calc(var(--vh, 1vh) * 100);
box-sizing: border-box;
@ -290,7 +288,6 @@ export default defineComponent({
width: 750px;
margin: 0 16px 0 0;
background: var(--bg);
box-shadow: 0 0 0 1px var(--divider);
border-radius: 0;
--margin: 12px;
@ -321,7 +318,7 @@ export default defineComponent({
}
> .widgets {
//--panelShadow: none;
//--panelBorder: none;
width: 300px;
margin-top: 16px;
@ -342,14 +339,13 @@ export default defineComponent({
--globalHeaderHeight: 60px; // TODO: 60px
> .main {
margin-top: 1px;
margin-top: 0;
border-radius: var(--radius);
box-shadow: 0 0 0 1px var(--divider);
}
> .widgets {
--stickyTop: var(--globalHeaderHeight);
margin-top: 1px;
margin-top: 0;
}
}

View file

@ -9,7 +9,6 @@
## リモート投稿へのリアクションについて
リアクションはMisskeyオリジナルの機能であるため、リモートインスタンスがMisskeyでない限りは、ほとんどの場合「Like」としてアクティビティが送信されます。一般的にはLikeは「お気に入り」として実装されているようです。
また、相手がMisskeyであったとしても、カスタム絵文字リアクションは伝わらず、自動的に「👍」等にフォールバックされます。
## リモートからのリアクションについて
リモートから「Like」アクティビティを受信したとき、Misskeyでは「👍」のリアクションとして解釈されます。

View file

@ -0,0 +1,43 @@
# Misskeyについて
Misskeyはオープンソースの分散型マイクロブログプラットフォームプロジェクトです。
開発はsyuiloによって2014年から開始されました。
## 分散型とは何か?
分散(distributed)型とは、非中央集権(decentralized)とも呼ばれ、コミュニティが多数のサーバーに分散して存在し、それらが相互に通信することでコンテンツ共有ネットワークを形成していることが特徴のサービスです。
単一のサーバーしか存在しない、もしくは複数存在しても互いに独立している場合は中央集権なサービスと言われ、例えばTwitterやFacebookなどほとんどのサービスがそれに該当します。
## 常にオープンソース
Misskeyはこれまでもこれからも、オープンソースであり続けます。オープンソースとは、ソフトウェアのソースコード(プログラム)が公開されていることです。ソースコードの修正や再配布が可能であることを定義に含めることもあります。
Misskeyのすべてのソースコードは公開されていて、誰でも自由に閲覧、使用、修正、改変、再配布をすることができます。
オープンソースは、自分で好きなように変えたり、有害な処理が含まれていないことを確認することができたり、誰でも開発に参加できるなどの、様々なメリットがあります。
上述の分散型を実現するためにも、オープンソースであるということは必要不可欠な要素です。
再び引き合いに出しますが、TwitterやFacebookなどの利益を得ているほとんどのサービスはオープンソースではありません。
Misskeyのソースコードは、[GitHub上で公開されています。](https://github.com/misskey-dev)
## 開発に参加する、開発を支援する
Misskeyの開発に貢献するにはいろいろな方法があります。
### 機能を追加したり、バグを修正する
ソフトウェアエンジニアのスキルをお持ちの方であれば、ソースコードを編集する形でプロジェクトに貢献することができます。
### 議論に参加する
新しい機能、または既存の機能について意見を述べたり、不具合を報告したりすることでも貢献できます。
そのようなディスカッションは[GitHub](https://github.com/misskey-dev)上か、[フォーラム](https://forum.misskey.io/)等で行われます。
### テキストを翻訳する
Misskeyは様々な言語に対応しています(i18n -internationalizationの略- と呼ばれます)。元の言語は基本的に日本語ですが、有志によって他の言語へと翻訳されています。
その翻訳作業に加わっていただくことでもMisskeyに貢献できます。
Misskeyは[Crowdinというサービスを使用して翻訳の管理を行っています。](https://crowdin.com/project/misskey)
### 感想を投稿する
不具合報告等だけではなく、Misskeyの良い点、楽しい点といったポジティブな意見もぜひ共有してください。開発の励みになり、それは間接的ですがプロジェクトへの貢献です。
### 寄付をする
Misskeyはビジネスではなく、利用は無料であるため、収益は皆様からの寄付のみです。(インスタンスによっては広告収入を得ているような場合もありますが、それは運営者の収入であり開発者への収入にはなりません)
寄付をしていただければ、今後も開発を続けることが可能になり、プロジェクトへの貢献になります。
寄付は[Patreon](https://www.patreon.com/syuilo)で受け付けています。
## クレジット
Misskeyの開発者や、Misskeyに寄付をしてくださった方の一覧は[こちら](/about-misskey)で見ることができます。

View file

@ -121,14 +121,22 @@ router.get('/api.json', async ctx => {
router.get('/docs.json', async ctx => {
const lang = ctx.query.lang;
const query = ctx.query.q;
if (!Object.keys(locales).includes(lang)) {
ctx.body = [];
return;
}
const paths = glob.sync(__dirname + `/../../../src/docs/${lang}/*.md`);
const docs: { path: string; title: string; }[] = [];
const dirPath = `${__dirname}/../../../src/docs/${lang}`.replace(/\\/g, '/');
const paths = glob.sync(`${dirPath}/**/*.md`);
const docs: { path: string; title: string; summary: string; }[] = [];
for (const path of paths) {
const md = fs.readFileSync(path, { encoding: 'utf8' });
if (query && query.length > 0) {
// TODO: カタカナをひらがなにして比較するなどしたい
if (!md.includes(query)) continue;
}
const parsed = markdown.parse(md, {});
if (parsed.length === 0) return;
@ -147,9 +155,22 @@ router.get('/docs.json', async ctx => {
}
}
const firstParagrapfTokens = [];
while (buf[0].type !== 'paragraph_open') {
buf.shift();
}
buf.shift();
while (buf[0].type as string !== 'paragraph_close') {
const token = buf.shift();
if (token) {
firstParagrapfTokens.push(token);
}
}
docs.push({
path: path.split('/').pop()!.split('.')[0],
title: markdown.renderer.render(headingTokens, {}, {})
path: path.replace(`${dirPath}/`, '').split('.')[0],
title: markdown.renderer.render(headingTokens, {}, {}),
summary: markdown.renderer.render(firstParagrapfTokens, {}, {}),
});
}