Merge branch 'develop' into improve-media
This commit is contained in:
@ -18,6 +18,8 @@
|
||||
return;
|
||||
}
|
||||
|
||||
const langs = LANGS;
|
||||
|
||||
//#region Load settings
|
||||
let settings = null;
|
||||
const vuex = localStorage.getItem('vuex');
|
||||
@ -40,10 +42,10 @@
|
||||
//#region Detect the user language
|
||||
let lang = null;
|
||||
|
||||
if (LANGS.includes(navigator.language)) {
|
||||
if (langs.includes(navigator.language)) {
|
||||
lang = navigator.language;
|
||||
} else {
|
||||
lang = LANGS.find(x => x.split('-')[0] == navigator.language);
|
||||
lang = langs.find(x => x.split('-')[0] == navigator.language);
|
||||
|
||||
if (lang == null) {
|
||||
// Fallback
|
||||
@ -52,7 +54,7 @@
|
||||
}
|
||||
|
||||
if (settings && settings.device.lang &&
|
||||
LANGS.includes(settings.device.lang)) {
|
||||
langs.includes(settings.device.lang)) {
|
||||
lang = settings.device.lang;
|
||||
}
|
||||
//#endregion
|
||||
@ -140,7 +142,7 @@
|
||||
// Random
|
||||
localStorage.setItem('salt', Math.random().toString());
|
||||
|
||||
// Clear cache (serive worker)
|
||||
// Clear cache (service worker)
|
||||
try {
|
||||
navigator.serviceWorker.controller.postMessage('clear');
|
||||
|
||||
|
@ -9,7 +9,7 @@ export default async function(mios: MiOS, force = false, silent = false) {
|
||||
localStorage.setItem('should-refresh', 'true');
|
||||
localStorage.setItem('v', newer);
|
||||
|
||||
// Clear cache (serive worker)
|
||||
// Clear cache (service worker)
|
||||
try {
|
||||
if (navigator.serviceWorker.controller) {
|
||||
navigator.serviceWorker.controller.postMessage('clear');
|
||||
|
@ -1,2 +0,0 @@
|
||||
const gcd = (a, b) => !b ? a : gcd(b, a % b);
|
||||
export default gcd;
|
@ -1,53 +0,0 @@
|
||||
export default function(qs: string) {
|
||||
const q = {
|
||||
text: ''
|
||||
};
|
||||
|
||||
qs.split(' ').forEach(x => {
|
||||
if (/^([a-z_]+?):(.+?)$/.test(x)) {
|
||||
const [key, value] = x.split(':');
|
||||
switch (key) {
|
||||
case 'user':
|
||||
q['includeUserUsernames'] = value.split(',');
|
||||
break;
|
||||
case 'exclude_user':
|
||||
q['excludeUserUsernames'] = value.split(',');
|
||||
break;
|
||||
case 'follow':
|
||||
q['following'] = value == 'null' ? null : value == 'true';
|
||||
break;
|
||||
case 'reply':
|
||||
q['reply'] = value == 'null' ? null : value == 'true';
|
||||
break;
|
||||
case 'renote':
|
||||
q['renote'] = value == 'null' ? null : value == 'true';
|
||||
break;
|
||||
case 'media':
|
||||
q['media'] = value == 'null' ? null : value == 'true';
|
||||
break;
|
||||
case 'poll':
|
||||
q['poll'] = value == 'null' ? null : value == 'true';
|
||||
break;
|
||||
case 'until':
|
||||
case 'since':
|
||||
// YYYY-MM-DD
|
||||
if (/^[0-9]+\-[0-9]+\-[0-9]+$/) {
|
||||
const [yyyy, mm, dd] = value.split('-');
|
||||
q[`${key}_date`] = (new Date(parseInt(yyyy, 10), parseInt(mm, 10) - 1, parseInt(dd, 10))).getTime();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
q[key] = value;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
q.text += x + ' ';
|
||||
}
|
||||
});
|
||||
|
||||
if (q.text) {
|
||||
q.text = q.text.trim();
|
||||
}
|
||||
|
||||
return q;
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
import * as uuid from 'uuid';
|
||||
import Connection from './stream';
|
||||
import { erase } from '../../../../../prelude/array';
|
||||
|
||||
/**
|
||||
* ストリーム接続を管理するクラス
|
||||
@ -89,7 +90,7 @@ export default abstract class StreamManager<T extends Connection> extends EventE
|
||||
* @param userId use で発行したユーザーID
|
||||
*/
|
||||
public dispose(userId) {
|
||||
this.users = this.users.filter(id => id != userId);
|
||||
this.users = erase(userId, this.users);
|
||||
|
||||
this._connection.user = `Managed (${ this.users.length })`;
|
||||
|
||||
|
@ -1,19 +1,25 @@
|
||||
<template>
|
||||
<span class="mk-acct">
|
||||
<span class="name">@{{ user.username }}</span>
|
||||
<span class="host" v-if="user.host">@{{ user.host }}</span>
|
||||
<span class="host" :class="{ fade: $store.state.settings.contrastedAcct }" v-if="user.host || detail || $store.state.settings.showFullAcct">@{{ user.host || host }}</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { host } from '../../../config';
|
||||
export default Vue.extend({
|
||||
props: ['user']
|
||||
props: ['user', 'detail'],
|
||||
data() {
|
||||
return {
|
||||
host
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.mk-acct
|
||||
> .host
|
||||
> .host.fade
|
||||
opacity 0.5
|
||||
</style>
|
||||
|
@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<span class="mk-avatar" :class="{ cat }" :title="user | acct" v-if="disableLink && !disablePreview" v-user-preview="user.id" @click="onClick">
|
||||
<span class="inner" :style="style"></span>
|
||||
<span class="mk-avatar" :style="style" :class="{ cat }" :title="user | acct" v-if="disableLink && !disablePreview" v-user-preview="user.id" @click="onClick">
|
||||
<span class="inner" :style="icon"></span>
|
||||
</span>
|
||||
<span class="mk-avatar" :class="{ cat }" :title="user | acct" v-else-if="disableLink && disablePreview" @click="onClick">
|
||||
<span class="inner" :style="style"></span>
|
||||
<span class="mk-avatar" :style="style" :class="{ cat }" :title="user | acct" v-else-if="disableLink && disablePreview" @click="onClick">
|
||||
<span class="inner" :style="icon"></span>
|
||||
</span>
|
||||
<router-link class="mk-avatar" :class="{ cat }" :to="user | userPage" :title="user | acct" :target="target" v-else-if="!disableLink && !disablePreview" v-user-preview="user.id">
|
||||
<span class="inner" :style="style"></span>
|
||||
<router-link class="mk-avatar" :style="style" :class="{ cat }" :to="user | userPage" :title="user | acct" :target="target" v-else-if="!disableLink && !disablePreview" v-user-preview="user.id">
|
||||
<span class="inner" :style="icon"></span>
|
||||
</router-link>
|
||||
<router-link class="mk-avatar" :class="{ cat }" :to="user | userPage" :title="user | acct" :target="target" v-else-if="!disableLink && disablePreview">
|
||||
<span class="inner" :style="style"></span>
|
||||
<router-link class="mk-avatar" :style="style" :class="{ cat }" :to="user | userPage" :title="user | acct" :target="target" v-else-if="!disableLink && disablePreview">
|
||||
<span class="inner" :style="icon"></span>
|
||||
</router-link>
|
||||
</template>
|
||||
|
||||
@ -42,6 +42,11 @@ export default Vue.extend({
|
||||
return this.user.isCat && this.$store.state.settings.circleIcons;
|
||||
},
|
||||
style(): any {
|
||||
return {
|
||||
borderRadius: this.$store.state.settings.circleIcons ? '100%' : null
|
||||
};
|
||||
},
|
||||
icon(): any {
|
||||
return {
|
||||
backgroundColor: this.lightmode
|
||||
? `rgb(${this.user.avatarColor.slice(0, 3).join(',')})`
|
||||
|
44
src/client/app/common/views/components/cw-button.vue
Normal file
44
src/client/app/common/views/components/cw-button.vue
Normal file
@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<button class="nrvgflfuaxwgkxoynpnumyookecqrrvh" @click="toggle">{{ value ? '%i18n:@hide%' : '%i18n:@show%' }}</button>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
toggle() {
|
||||
this.$emit('input', !this.value);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
root(isDark)
|
||||
display inline-block
|
||||
padding 4px 8px
|
||||
font-size 0.7em
|
||||
color isDark ? #393f4f : #fff
|
||||
background isDark ? #687390 : #b1b9c1
|
||||
border-radius 2px
|
||||
cursor pointer
|
||||
user-select none
|
||||
|
||||
&:hover
|
||||
background isDark ? #707b97 : #bbc4ce
|
||||
|
||||
.nrvgflfuaxwgkxoynpnumyookecqrrvh[data-darkmode]
|
||||
root(true)
|
||||
|
||||
.nrvgflfuaxwgkxoynpnumyookecqrrvh:not([data-darkmode])
|
||||
root(false)
|
||||
|
||||
</style>
|
@ -50,15 +50,15 @@
|
||||
</div>
|
||||
|
||||
<div class="player" v-if="game.isEnded">
|
||||
<el-button-group>
|
||||
<el-button type="primary" @click="logPos = 0" :disabled="logPos == 0">%fa:angle-double-left%</el-button>
|
||||
<el-button type="primary" @click="logPos--" :disabled="logPos == 0">%fa:angle-left%</el-button>
|
||||
</el-button-group>
|
||||
<div>
|
||||
<button @click="logPos = 0" :disabled="logPos == 0">%fa:angle-double-left%</button>
|
||||
<button @click="logPos--" :disabled="logPos == 0">%fa:angle-left%</button>
|
||||
</div>
|
||||
<span>{{ logPos }} / {{ logs.length }}</span>
|
||||
<el-button-group>
|
||||
<el-button type="primary" @click="logPos++" :disabled="logPos == logs.length">%fa:angle-right%</el-button>
|
||||
<el-button type="primary" @click="logPos = logs.length" :disabled="logPos == logs.length">%fa:angle-double-right%</el-button>
|
||||
</el-button-group>
|
||||
<div>
|
||||
<button @click="logPos++" :disabled="logPos == logs.length">%fa:angle-right%</button>
|
||||
<button @click="logPos = logs.length" :disabled="logPos == logs.length">%fa:angle-double-right%</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info">
|
||||
|
@ -3,7 +3,6 @@
|
||||
<h1>%i18n:@title%</h1>
|
||||
<p>%i18n:@sub-title%</p>
|
||||
<div class="play">
|
||||
<!--<el-button round>フリーマッチ(準備中)</el-button>-->
|
||||
<form-button primary round @click="match">%i18n:@invite%</form-button>
|
||||
<details>
|
||||
<summary>%i18n:@rule%</summary>
|
||||
|
@ -59,11 +59,6 @@
|
||||
</header>
|
||||
|
||||
<div>
|
||||
<el-alert v-for="message in messages"
|
||||
:title="message.text"
|
||||
:type="message.type"
|
||||
:key="message.id"/>
|
||||
|
||||
<template v-for="item in form">
|
||||
<mk-switch v-if="item.type == 'switch'" v-model="item.value" :key="item.id" :text="item.label" @change="onChangeForm(item)">{{ item.desc || '' }}</mk-switch>
|
||||
|
||||
@ -93,7 +88,7 @@
|
||||
</header>
|
||||
|
||||
<div>
|
||||
<el-input v-model="item.value" @change="onChangeForm(item)"/>
|
||||
<input v-model="item.value" @change="onChangeForm(item)"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -1,5 +1,7 @@
|
||||
import Vue from 'vue';
|
||||
|
||||
import cwButton from './cw-button.vue';
|
||||
import tagCloud from './tag-cloud.vue';
|
||||
import trends from './trends.vue';
|
||||
import analogClock from './analog-clock.vue';
|
||||
import menu from './menu.vue';
|
||||
@ -42,6 +44,8 @@ import uiSelect from './ui/select.vue';
|
||||
import formButton from './ui/form/button.vue';
|
||||
import formRadio from './ui/form/radio.vue';
|
||||
|
||||
Vue.component('mk-cw-button', cwButton);
|
||||
Vue.component('mk-tag-cloud', tagCloud);
|
||||
Vue.component('mk-trends', trends);
|
||||
Vue.component('mk-analog-clock', analogClock);
|
||||
Vue.component('mk-menu', menu);
|
||||
|
@ -108,7 +108,7 @@ export default Vue.extend({
|
||||
easing: 'easeInBack',
|
||||
complete: () => {
|
||||
this.$emit('closed');
|
||||
this.$destroy();
|
||||
this.destroyDom();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import Vue from 'vue';
|
||||
import Vue, { VNode } from 'vue';
|
||||
import * as emojilib from 'emojilib';
|
||||
import { length } from 'stringz';
|
||||
import parse from '../../../../../mfm/parse';
|
||||
@ -6,10 +6,7 @@ import getAcct from '../../../../../misc/acct/render';
|
||||
import { url } from '../../../config';
|
||||
import MkUrl from './url.vue';
|
||||
import MkGoogle from './google.vue';
|
||||
|
||||
const flatten = list => list.reduce(
|
||||
(a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), []
|
||||
);
|
||||
import { concat } from '../../../../../prelude/array';
|
||||
|
||||
export default Vue.component('misskey-flavored-markdown', {
|
||||
props: {
|
||||
@ -32,20 +29,20 @@ export default Vue.component('misskey-flavored-markdown', {
|
||||
},
|
||||
|
||||
render(createElement) {
|
||||
let ast;
|
||||
let ast: any[];
|
||||
|
||||
if (this.ast == null) {
|
||||
// Parse text to ast
|
||||
ast = parse(this.text);
|
||||
} else {
|
||||
ast = this.ast;
|
||||
ast = this.ast as any[];
|
||||
}
|
||||
|
||||
let bigCount = 0;
|
||||
let motionCount = 0;
|
||||
|
||||
// Parse ast to DOM
|
||||
const els = flatten(ast.map(token => {
|
||||
const els = concat(ast.map((token): VNode[] => {
|
||||
switch (token.type) {
|
||||
case 'text': {
|
||||
const text = token.content.replace(/(\r\n|\n|\r)/g, '\n');
|
||||
@ -56,12 +53,12 @@ export default Vue.component('misskey-flavored-markdown', {
|
||||
x[x.length - 1].pop();
|
||||
return x;
|
||||
} else {
|
||||
return createElement('span', text.replace(/\n/g, ' '));
|
||||
return [createElement('span', text.replace(/\n/g, ' '))];
|
||||
}
|
||||
}
|
||||
|
||||
case 'bold': {
|
||||
return createElement('b', token.bold);
|
||||
return [createElement('b', token.bold)];
|
||||
}
|
||||
|
||||
case 'big': {
|
||||
@ -95,23 +92,23 @@ export default Vue.component('misskey-flavored-markdown', {
|
||||
}
|
||||
|
||||
case 'url': {
|
||||
return createElement(MkUrl, {
|
||||
return [createElement(MkUrl, {
|
||||
props: {
|
||||
url: token.content,
|
||||
target: '_blank'
|
||||
}
|
||||
});
|
||||
})];
|
||||
}
|
||||
|
||||
case 'link': {
|
||||
return createElement('a', {
|
||||
return [createElement('a', {
|
||||
attrs: {
|
||||
class: 'link',
|
||||
href: token.url,
|
||||
target: '_blank',
|
||||
title: token.url
|
||||
}
|
||||
}, token.title);
|
||||
}, token.title)];
|
||||
}
|
||||
|
||||
case 'mention': {
|
||||
@ -129,16 +126,16 @@ export default Vue.component('misskey-flavored-markdown', {
|
||||
}
|
||||
|
||||
case 'hashtag': {
|
||||
return createElement('a', {
|
||||
return [createElement('a', {
|
||||
attrs: {
|
||||
href: `${url}/tags/${encodeURIComponent(token.hashtag)}`,
|
||||
target: '_blank'
|
||||
}
|
||||
}, token.content);
|
||||
}, token.content)];
|
||||
}
|
||||
|
||||
case 'code': {
|
||||
return createElement('pre', {
|
||||
return [createElement('pre', {
|
||||
class: 'code'
|
||||
}, [
|
||||
createElement('code', {
|
||||
@ -146,15 +143,15 @@ export default Vue.component('misskey-flavored-markdown', {
|
||||
innerHTML: token.html
|
||||
}
|
||||
})
|
||||
]);
|
||||
])];
|
||||
}
|
||||
|
||||
case 'inline-code': {
|
||||
return createElement('code', {
|
||||
return [createElement('code', {
|
||||
domProps: {
|
||||
innerHTML: token.html
|
||||
}
|
||||
});
|
||||
})];
|
||||
}
|
||||
|
||||
case 'quote': {
|
||||
@ -164,43 +161,45 @@ export default Vue.component('misskey-flavored-markdown', {
|
||||
const x = text2.split('\n')
|
||||
.map(t => [createElement('span', t), createElement('br')]);
|
||||
x[x.length - 1].pop();
|
||||
return createElement('div', {
|
||||
return [createElement('div', {
|
||||
attrs: {
|
||||
class: 'quote'
|
||||
}
|
||||
}, x);
|
||||
}, x)];
|
||||
} else {
|
||||
return createElement('span', {
|
||||
return [createElement('span', {
|
||||
attrs: {
|
||||
class: 'quote'
|
||||
}
|
||||
}, text2.replace(/\n/g, ' '));
|
||||
}, text2.replace(/\n/g, ' '))];
|
||||
}
|
||||
}
|
||||
|
||||
case 'title': {
|
||||
return createElement('div', {
|
||||
return [createElement('div', {
|
||||
attrs: {
|
||||
class: 'title'
|
||||
}
|
||||
}, token.title);
|
||||
}, token.title)];
|
||||
}
|
||||
|
||||
case 'emoji': {
|
||||
const emoji = emojilib.lib[token.emoji];
|
||||
return createElement('span', emoji ? emoji.char : token.content);
|
||||
return [createElement('span', emoji ? emoji.char : token.content)];
|
||||
}
|
||||
|
||||
case 'search': {
|
||||
return createElement(MkGoogle, {
|
||||
return [createElement(MkGoogle, {
|
||||
props: {
|
||||
q: token.query
|
||||
}
|
||||
});
|
||||
})];
|
||||
}
|
||||
|
||||
default: {
|
||||
console.log('unknown ast type:', token.type);
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
@ -64,7 +64,7 @@ export default Vue.extend({
|
||||
(this as any).api('i/pin', {
|
||||
noteId: this.note.id
|
||||
}).then(() => {
|
||||
this.$destroy();
|
||||
this.destroyDom();
|
||||
});
|
||||
},
|
||||
|
||||
@ -73,7 +73,7 @@ export default Vue.extend({
|
||||
(this as any).api('notes/delete', {
|
||||
noteId: this.note.id
|
||||
}).then(() => {
|
||||
this.$destroy();
|
||||
this.destroyDom();
|
||||
});
|
||||
},
|
||||
|
||||
@ -81,13 +81,13 @@ export default Vue.extend({
|
||||
(this as any).api('notes/favorites/create', {
|
||||
noteId: this.note.id
|
||||
}).then(() => {
|
||||
this.$destroy();
|
||||
this.destroyDom();
|
||||
});
|
||||
},
|
||||
|
||||
closed() {
|
||||
this.$nextTick(() => {
|
||||
this.$destroy();
|
||||
this.destroyDom();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { erase } from '../../../../../prelude/array';
|
||||
export default Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
@ -53,7 +54,7 @@ export default Vue.extend({
|
||||
|
||||
get() {
|
||||
return {
|
||||
choices: this.choices.filter(choice => choice != '')
|
||||
choices: erase('', this.choices)
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -21,6 +21,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { sum } from '../../../../../prelude/array';
|
||||
export default Vue.extend({
|
||||
props: ['note'],
|
||||
data() {
|
||||
@ -33,7 +34,7 @@ export default Vue.extend({
|
||||
return this.note.poll;
|
||||
},
|
||||
total(): number {
|
||||
return this.poll.choices.reduce((a, b) => a + b.votes, 0);
|
||||
return sum(this.poll.choices.map(x => x.votes));
|
||||
},
|
||||
isVoted(): boolean {
|
||||
return this.poll.choices.some(c => c.isVoted);
|
||||
|
@ -1,17 +1,17 @@
|
||||
<template>
|
||||
<span class="mk-reaction-icon">
|
||||
<img v-if="reaction == 'like'" src="/assets/reactions/like.png" alt="%i18n:common.reactions.like%">
|
||||
<img v-if="reaction == 'love'" src="/assets/reactions/love.png" alt="%i18n:common.reactions.love%">
|
||||
<img v-if="reaction == 'laugh'" src="/assets/reactions/laugh.png" alt="%i18n:common.reactions.laugh%">
|
||||
<img v-if="reaction == 'hmm'" src="/assets/reactions/hmm.png" alt="%i18n:common.reactions.hmm%">
|
||||
<img v-if="reaction == 'surprise'" src="/assets/reactions/surprise.png" alt="%i18n:common.reactions.surprise%">
|
||||
<img v-if="reaction == 'congrats'" src="/assets/reactions/congrats.png" alt="%i18n:common.reactions.congrats%">
|
||||
<img v-if="reaction == 'angry'" src="/assets/reactions/angry.png" alt="%i18n:common.reactions.angry%">
|
||||
<img v-if="reaction == 'confused'" src="/assets/reactions/confused.png" alt="%i18n:common.reactions.confused%">
|
||||
<img v-if="reaction == 'rip'" src="/assets/reactions/rip.png" alt="%i18n:common.reactions.rip%">
|
||||
<img v-if="reaction == 'like'" src="https://twemoji.maxcdn.com/2/svg/1f44d.svg" alt="%i18n:common.reactions.like%">
|
||||
<img v-if="reaction == 'love'" src="https://twemoji.maxcdn.com/2/svg/2764.svg" alt="%i18n:common.reactions.love%">
|
||||
<img v-if="reaction == 'laugh'" src="https://twemoji.maxcdn.com/2/svg/1f606.svg" alt="%i18n:common.reactions.laugh%">
|
||||
<img v-if="reaction == 'hmm'" src="https://twemoji.maxcdn.com/2/svg/1f914.svg" alt="%i18n:common.reactions.hmm%">
|
||||
<img v-if="reaction == 'surprise'" src="https://twemoji.maxcdn.com/2/svg/1f62e.svg" alt="%i18n:common.reactions.surprise%">
|
||||
<img v-if="reaction == 'congrats'" src="https://twemoji.maxcdn.com/2/svg/1f389.svg" alt="%i18n:common.reactions.congrats%">
|
||||
<img v-if="reaction == 'angry'" src="https://twemoji.maxcdn.com/2/svg/1f4a2.svg" alt="%i18n:common.reactions.angry%">
|
||||
<img v-if="reaction == 'confused'" src="https://twemoji.maxcdn.com/2/svg/1f625.svg" alt="%i18n:common.reactions.confused%">
|
||||
<img v-if="reaction == 'rip'" src="https://twemoji.maxcdn.com/2/svg/1f607.svg" alt="%i18n:common.reactions.rip%">
|
||||
<template v-if="reaction == 'pudding'">
|
||||
<img v-if="$store.getters.isSignedIn && $store.state.settings.iLikeSushi" src="/assets/reactions/sushi.png" alt="%i18n:common.reactions.pudding%">
|
||||
<img v-else src="/assets/reactions/pudding.png" alt="%i18n:common.reactions.pudding%">
|
||||
<img v-if="$store.getters.isSignedIn && $store.state.settings.iLikeSushi" src="https://twemoji.maxcdn.com/2/svg/1f363.svg" alt="%i18n:common.reactions.pudding%">
|
||||
<img v-else src="https://twemoji.maxcdn.com/2/svg/1f36e.svg" alt="%i18n:common.reactions.pudding%">
|
||||
</template>
|
||||
</span>
|
||||
</template>
|
||||
|
@ -95,7 +95,7 @@ export default Vue.extend({
|
||||
reaction: reaction
|
||||
}).then(() => {
|
||||
if (this.cb) this.cb();
|
||||
this.$destroy();
|
||||
this.destroyDom();
|
||||
});
|
||||
},
|
||||
onMouseover(e) {
|
||||
@ -120,7 +120,7 @@ export default Vue.extend({
|
||||
scale: 0.5,
|
||||
duration: 200,
|
||||
easing: 'easeInBack',
|
||||
complete: () => this.$destroy()
|
||||
complete: () => this.destroyDom()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
90
src/client/app/common/views/components/tag-cloud.vue
Normal file
90
src/client/app/common/views/components/tag-cloud.vue
Normal file
@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<div class="jtivnzhfwquxpsfidertopbmwmchmnmo">
|
||||
<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p>
|
||||
<p class="empty" v-else-if="tags.length == 0">%fa:exclamation-circle%%i18n:@empty%</p>
|
||||
<div v-else>
|
||||
<vue-word-cloud
|
||||
:words="tags.slice(0, 20).map(x => [x.name, x.count])"
|
||||
:color="color"
|
||||
:spacing="1">
|
||||
<template slot-scope="{word, text, weight}">
|
||||
<div style="cursor: pointer;" :title="weight">
|
||||
{{ text }}
|
||||
</div>
|
||||
</template>
|
||||
</vue-word-cloud>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import * as VueWordCloud from 'vuewordcloud';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
[VueWordCloud.name]: VueWordCloud
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tags: [],
|
||||
fetching: true,
|
||||
clock: null
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.fetch();
|
||||
this.clock = setInterval(this.fetch, 1000 * 60);
|
||||
},
|
||||
beforeDestroy() {
|
||||
clearInterval(this.clock);
|
||||
},
|
||||
methods: {
|
||||
fetch() {
|
||||
(this as any).api('aggregation/hashtags').then(tags => {
|
||||
this.tags = tags;
|
||||
this.fetching = false;
|
||||
});
|
||||
},
|
||||
color([, weight]) {
|
||||
const peak = Math.max.apply(null, this.tags.map(x => x.count));
|
||||
const w = weight / peak;
|
||||
|
||||
if (w > 0.9) {
|
||||
return this.$store.state.device.darkmode ? '#ff4e69' : '#ff4e69';
|
||||
} else if (w > 0.5) {
|
||||
return this.$store.state.device.darkmode ? '#3bc4c7' : '#3bc4c7';
|
||||
} else {
|
||||
return this.$store.state.device.darkmode ? '#fff' : '#555';
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
root(isDark)
|
||||
height 100%
|
||||
width 100%
|
||||
|
||||
> .fetching
|
||||
> .empty
|
||||
margin 0
|
||||
padding 16px
|
||||
text-align center
|
||||
color #aaa
|
||||
|
||||
> [data-fa]
|
||||
margin-right 4px
|
||||
|
||||
> div
|
||||
height 100%
|
||||
width 100%
|
||||
|
||||
.jtivnzhfwquxpsfidertopbmwmchmnmo[data-darkmode]
|
||||
root(true)
|
||||
|
||||
.jtivnzhfwquxpsfidertopbmwmchmnmo:not([data-darkmode])
|
||||
root(false)
|
||||
|
||||
</style>
|
@ -24,19 +24,34 @@ export default Vue.extend({
|
||||
|
||||
root(isDark)
|
||||
margin 16px
|
||||
padding 16px
|
||||
color isDark ? #fff : #000
|
||||
background isDark ? #282C37 : #fff
|
||||
box-shadow 0 3px 1px -2px rgba(#000, 0.2), 0 2px 2px 0 rgba(#000, 0.14), 0 1px 5px 0 rgba(#000, 0.12)
|
||||
|
||||
@media (min-width 500px)
|
||||
padding 32px
|
||||
|
||||
> header
|
||||
font-weight normal
|
||||
font-size 24px
|
||||
padding 16px
|
||||
font-weight bold
|
||||
font-size 20px
|
||||
color isDark ? #fff : #444
|
||||
|
||||
@media (min-width 500px)
|
||||
padding 24px 32px
|
||||
|
||||
> section
|
||||
padding 20px 16px
|
||||
border-top solid 1px isDark ? rgba(#000, 0.3) : rgba(#000, 0.1)
|
||||
|
||||
@media (min-width 500px)
|
||||
padding 32px
|
||||
|
||||
&.fit-top
|
||||
padding-top 0
|
||||
|
||||
> header
|
||||
margin-bottom 16px
|
||||
font-weight bold
|
||||
color isDark ? #fff : #444
|
||||
|
||||
.ui-card[data-darkmode]
|
||||
root(true)
|
||||
|
||||
|
@ -55,7 +55,7 @@ export default Vue.extend({
|
||||
|
||||
root(isDark)
|
||||
display inline-block
|
||||
margin 32px 32px 32px 0
|
||||
margin 0 32px 0 0
|
||||
cursor pointer
|
||||
transition all 0.3s
|
||||
|
||||
|
@ -64,6 +64,12 @@ root(isDark)
|
||||
cursor pointer
|
||||
transition all 0.3s
|
||||
|
||||
&:first-child
|
||||
margin-top 0
|
||||
|
||||
&:last-child
|
||||
margin-bottom 0
|
||||
|
||||
> *
|
||||
user-select none
|
||||
|
||||
@ -89,6 +95,7 @@ root(isDark)
|
||||
|
||||
> .button
|
||||
display inline-block
|
||||
flex-shrink 0
|
||||
margin 3px 0 0 0
|
||||
width 34px
|
||||
height 14px
|
||||
|
@ -14,7 +14,7 @@
|
||||
<header>
|
||||
<h1>{{ title }}</h1>
|
||||
</header>
|
||||
<p>{{ description }}</p>
|
||||
<p>{{ description.length > 85 ? description.slice(0, 85) + '…' : description }}</p>
|
||||
<footer>
|
||||
<img class="icon" v-if="icon" :src="icon"/>
|
||||
<p>{{ sitename }}</p>
|
||||
|
@ -47,7 +47,7 @@ export default Vue.extend({
|
||||
props: ['source', 'compact'],
|
||||
data() {
|
||||
return {
|
||||
v: this.$store.state.device.visibility || 'public'
|
||||
v: this.$store.state.settings.rememberNoteVisibility ? (this.$store.state.device.visibility || this.$store.state.settings.defaultNoteVisibility) : this.$store.state.settings.defaultNoteVisibility
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@ -97,9 +97,11 @@ export default Vue.extend({
|
||||
},
|
||||
methods: {
|
||||
choose(visibility) {
|
||||
this.$store.commit('device/setVisibility', visibility);
|
||||
if (this.$store.state.settings.rememberNoteVisibility) {
|
||||
this.$store.commit('device/setVisibility', visibility);
|
||||
}
|
||||
this.$emit('chosen', visibility);
|
||||
this.$destroy();
|
||||
this.destroyDom();
|
||||
},
|
||||
close() {
|
||||
(this.$refs.backdrop as any).style.pointerEvents = 'none';
|
||||
@ -117,7 +119,7 @@ export default Vue.extend({
|
||||
scale: 0.5,
|
||||
duration: 200,
|
||||
easing: 'easeInBack',
|
||||
complete: () => this.$destroy()
|
||||
complete: () => this.destroyDom()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,24 @@
|
||||
<template>
|
||||
<div class="mk-welcome-timeline">
|
||||
<div v-for="note in notes">
|
||||
<mk-avatar class="avatar" :user="note.user" target="_blank"/>
|
||||
<div class="body">
|
||||
<header>
|
||||
<router-link class="name" :to="note.user | userPage" v-user-preview="note.user.id">{{ note.user | userName }}</router-link>
|
||||
<span class="username">@{{ note.user | acct }}</span>
|
||||
<div class="info">
|
||||
<router-link class="created-at" :to="note | notePage">
|
||||
<mk-time :time="note.createdAt"/>
|
||||
</router-link>
|
||||
<transition-group name="ldzpakcixzickvggyixyrhqwjaefknon" tag="div">
|
||||
<div v-for="note in notes" :key="note.id">
|
||||
<mk-avatar class="avatar" :user="note.user" target="_blank"/>
|
||||
<div class="body">
|
||||
<header>
|
||||
<router-link class="name" :to="note.user | userPage" v-user-preview="note.user.id">{{ note.user | userName }}</router-link>
|
||||
<span class="username">@{{ note.user | acct }}</span>
|
||||
<div class="info">
|
||||
<router-link class="created-at" :to="note | notePage">
|
||||
<mk-time :time="note.createdAt"/>
|
||||
</router-link>
|
||||
</div>
|
||||
</header>
|
||||
<div class="text">
|
||||
<misskey-flavored-markdown v-if="note.text" :text="note.text"/>
|
||||
</div>
|
||||
</header>
|
||||
<div class="text">
|
||||
<misskey-flavored-markdown v-if="note.text" :text="note.text"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -63,7 +65,7 @@ export default Vue.extend({
|
||||
local: true,
|
||||
reply: false,
|
||||
renote: false,
|
||||
media: false,
|
||||
file: false,
|
||||
poll: false
|
||||
}).then(notes => {
|
||||
this.notes = notes;
|
||||
@ -83,64 +85,73 @@ export default Vue.extend({
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.ldzpakcixzickvggyixyrhqwjaefknon-enter
|
||||
.ldzpakcixzickvggyixyrhqwjaefknon-leave-to
|
||||
opacity 0
|
||||
transform translateY(-30px)
|
||||
|
||||
root(isDark)
|
||||
background isDark ? #282C37 : #fff
|
||||
|
||||
> div
|
||||
padding 16px
|
||||
overflow-wrap break-word
|
||||
font-size .9em
|
||||
color isDark ? #fff : #4C4C4C
|
||||
border-bottom 1px solid isDark ? rgba(#000, 0.1) : rgba(#000, 0.05)
|
||||
> *
|
||||
transition transform .3s ease, opacity .3s ease
|
||||
|
||||
&:after
|
||||
content ""
|
||||
display block
|
||||
clear both
|
||||
> div
|
||||
padding 16px
|
||||
overflow-wrap break-word
|
||||
font-size .9em
|
||||
color isDark ? #fff : #4C4C4C
|
||||
border-bottom 1px solid isDark ? rgba(#000, 0.1) : rgba(#000, 0.05)
|
||||
|
||||
> .avatar
|
||||
display block
|
||||
float left
|
||||
position -webkit-sticky
|
||||
position sticky
|
||||
top 16px
|
||||
width 42px
|
||||
height 42px
|
||||
border-radius 6px
|
||||
&:after
|
||||
content ""
|
||||
display block
|
||||
clear both
|
||||
|
||||
> .body
|
||||
float right
|
||||
width calc(100% - 42px)
|
||||
padding-left 12px
|
||||
> .avatar
|
||||
display block
|
||||
float left
|
||||
position -webkit-sticky
|
||||
position sticky
|
||||
top 16px
|
||||
width 42px
|
||||
height 42px
|
||||
border-radius 6px
|
||||
|
||||
> header
|
||||
display flex
|
||||
align-items center
|
||||
margin-bottom 4px
|
||||
white-space nowrap
|
||||
> .body
|
||||
float right
|
||||
width calc(100% - 42px)
|
||||
padding-left 12px
|
||||
|
||||
> .name
|
||||
display block
|
||||
margin 0 .5em 0 0
|
||||
padding 0
|
||||
overflow hidden
|
||||
font-weight bold
|
||||
text-overflow ellipsis
|
||||
color isDark ? #fff : #627079
|
||||
> header
|
||||
display flex
|
||||
align-items center
|
||||
margin-bottom 4px
|
||||
white-space nowrap
|
||||
|
||||
> .username
|
||||
margin 0 .5em 0 0
|
||||
color isDark ? #606984 : #ccc
|
||||
> .name
|
||||
display block
|
||||
margin 0 .5em 0 0
|
||||
padding 0
|
||||
overflow hidden
|
||||
font-weight bold
|
||||
text-overflow ellipsis
|
||||
color isDark ? #fff : #627079
|
||||
|
||||
> .info
|
||||
margin-left auto
|
||||
font-size 0.9em
|
||||
> .username
|
||||
margin 0 .5em 0 0
|
||||
color isDark ? #606984 : #ccc
|
||||
|
||||
> .created-at
|
||||
color isDark ? #606984 : #c0c0c0
|
||||
> .info
|
||||
margin-left auto
|
||||
font-size 0.9em
|
||||
|
||||
> .text
|
||||
text-align left
|
||||
> .created-at
|
||||
color isDark ? #606984 : #c0c0c0
|
||||
|
||||
> .text
|
||||
text-align left
|
||||
|
||||
.mk-welcome-timeline[data-darkmode]
|
||||
root(true)
|
||||
|
@ -167,7 +167,7 @@ class Autocomplete {
|
||||
private close() {
|
||||
if (this.suggestion == null) return;
|
||||
|
||||
this.suggestion.$destroy();
|
||||
this.suggestion.destroyDom();
|
||||
this.suggestion = null;
|
||||
|
||||
this.textarea.focus();
|
||||
|
@ -32,7 +32,6 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import parseAcct from '../../../../../misc/acct/parse';
|
||||
import getUserName from '../../../../../misc/get-user-name';
|
||||
import Progress from '../../../common/scripts/loading';
|
||||
|
||||
export default Vue.extend({
|
||||
|
@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div class="mkw-analog-clock">
|
||||
<mk-widget-container :naked="!(props.design % 2)" :show-header="false">
|
||||
<mk-widget-container :naked="props.style % 2 === 0" :show-header="false">
|
||||
<div class="mkw-analog-clock--body">
|
||||
<mk-analog-clock :dark="$store.state.device.darkmode" :smooth="!(props.design && ~props.design)"/>
|
||||
<mk-analog-clock :dark="$store.state.device.darkmode" :smooth="props.style < 2"/>
|
||||
</div>
|
||||
</mk-widget-container>
|
||||
</div>
|
||||
@ -13,13 +13,12 @@ import define from '../../../common/define-widget';
|
||||
export default define({
|
||||
name: 'analog-clock',
|
||||
props: () => ({
|
||||
design: -1
|
||||
style: 0
|
||||
})
|
||||
}).extend({
|
||||
methods: {
|
||||
func() {
|
||||
if (++this.props.design > 2)
|
||||
this.props.design = -1;
|
||||
this.props.style = (this.props.style + 1) % 4;
|
||||
this.save();
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="anltbovirfeutcigvwgmgxipejaeozxi"
|
||||
:data-found="broadcasts.length != 0"
|
||||
:data-found="announcements && announcements.length != 0"
|
||||
:data-melt="props.design == 1"
|
||||
:data-mobile="platform == 'mobile'"
|
||||
>
|
||||
@ -14,12 +14,12 @@
|
||||
</svg>
|
||||
</div>
|
||||
<p class="fetching" v-if="fetching">%i18n:@fetching%<mk-ellipsis/></p>
|
||||
<h1 v-if="!fetching">{{ broadcasts.length == 0 ? '%i18n:@no-broadcasts%' : broadcasts[i].title }}</h1>
|
||||
<h1 v-if="!fetching">{{ announcements.length == 0 ? '%i18n:@no-broadcasts%' : announcements[i].title }}</h1>
|
||||
<p v-if="!fetching">
|
||||
<span v-if="broadcasts.length != 0" v-html="broadcasts[i].text"></span>
|
||||
<template v-if="broadcasts.length == 0">%i18n:@have-a-nice-day%</template>
|
||||
<span v-if="announcements.length != 0" v-html="announcements[i].text"></span>
|
||||
<template v-if="announcements.length == 0">%i18n:@have-a-nice-day%</template>
|
||||
</p>
|
||||
<a v-if="broadcasts.length > 1" @click="next">%i18n:@next% >></a>
|
||||
<a v-if="announcements.length > 1" @click="next">%i18n:@next% >></a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -36,18 +36,18 @@ export default define({
|
||||
return {
|
||||
i: 0,
|
||||
fetching: true,
|
||||
broadcasts: []
|
||||
announcements: []
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
(this as any).os.getMeta().then(meta => {
|
||||
this.broadcasts = meta.broadcasts;
|
||||
this.announcements = meta.broadcasts;
|
||||
this.fetching = false;
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
next() {
|
||||
if (this.i == this.broadcasts.length - 1) {
|
||||
if (this.i == this.announcements.length - 1) {
|
||||
this.i = 0;
|
||||
} else {
|
||||
this.i++;
|
||||
@ -126,7 +126,7 @@ root(isDark)
|
||||
margin 0
|
||||
font-size 0.95em
|
||||
font-weight normal
|
||||
color #4078c0
|
||||
color isDark ? #539eff : #4078c0
|
||||
|
||||
> p
|
||||
display block
|
||||
|
@ -6,7 +6,6 @@ import VueRouter from 'vue-router';
|
||||
|
||||
// Style
|
||||
import './style.styl';
|
||||
import '../../element.scss';
|
||||
|
||||
import init from '../init';
|
||||
import fuckAdBlock from '../common/scripts/fuck-ad-block';
|
||||
|
@ -19,6 +19,11 @@
|
||||
<option value="drive">%i18n:@charts.drive%</option>
|
||||
<option value="drive-total">%i18n:@charts.drive-total%</option>
|
||||
</optgroup>
|
||||
<optgroup label="%i18n:@network%">
|
||||
<option value="network-requests">%i18n:@charts.network-requests%</option>
|
||||
<option value="network-time">%i18n:@charts.network-time%</option>
|
||||
<option value="network-usage">%i18n:@charts.network-usage%</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
<div>
|
||||
<span @click="span = 'day'" :class="{ active: span == 'day' }">%i18n:@per-day%</span> | <span @click="span = 'hour'" :class="{ active: span == 'hour' }">%i18n:@per-hour%</span>
|
||||
@ -41,7 +46,10 @@ const colors = {
|
||||
localPlus: 'rgb(52, 178, 118)',
|
||||
remotePlus: 'rgb(158, 255, 209)',
|
||||
localMinus: 'rgb(255, 97, 74)',
|
||||
remoteMinus: 'rgb(255, 149, 134)'
|
||||
remoteMinus: 'rgb(255, 149, 134)',
|
||||
|
||||
incoming: 'rgb(52, 178, 118)',
|
||||
outgoing: 'rgb(255, 97, 74)',
|
||||
};
|
||||
|
||||
const rgba = (color: string): string => {
|
||||
@ -75,6 +83,9 @@ export default Vue.extend({
|
||||
case 'drive-total': return this.driveTotalChart();
|
||||
case 'drive-files': return this.driveFilesChart();
|
||||
case 'drive-files-total': return this.driveFilesTotalChart();
|
||||
case 'network-requests': return this.networkRequestsChart();
|
||||
case 'network-time': return this.networkTimeChart();
|
||||
case 'network-usage': return this.networkUsageChart();
|
||||
}
|
||||
},
|
||||
|
||||
@ -544,7 +555,95 @@ export default Vue.extend({
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
},
|
||||
|
||||
networkRequestsChart(): any {
|
||||
const data = this.stats.slice().reverse().map(x => ({
|
||||
date: new Date(x.date),
|
||||
requests: x.network.requests
|
||||
}));
|
||||
|
||||
return [{
|
||||
datasets: [{
|
||||
label: 'Requests',
|
||||
fill: true,
|
||||
backgroundColor: rgba(colors.localPlus),
|
||||
borderColor: colors.localPlus,
|
||||
borderWidth: 2,
|
||||
pointBackgroundColor: '#fff',
|
||||
lineTension: 0,
|
||||
data: data.map(x => ({ t: x.date, y: x.requests }))
|
||||
}]
|
||||
}];
|
||||
},
|
||||
|
||||
networkTimeChart(): any {
|
||||
const data = this.stats.slice().reverse().map(x => ({
|
||||
date: new Date(x.date),
|
||||
time: x.network.requests != 0 ? (x.network.totalTime / x.network.requests) : 0,
|
||||
}));
|
||||
|
||||
return [{
|
||||
datasets: [{
|
||||
label: 'Avg time (ms)',
|
||||
fill: true,
|
||||
backgroundColor: rgba(colors.localPlus),
|
||||
borderColor: colors.localPlus,
|
||||
borderWidth: 2,
|
||||
pointBackgroundColor: '#fff',
|
||||
lineTension: 0,
|
||||
data: data.map(x => ({ t: x.date, y: x.time }))
|
||||
}]
|
||||
}];
|
||||
},
|
||||
|
||||
networkUsageChart(): any {
|
||||
const data = this.stats.slice().reverse().map(x => ({
|
||||
date: new Date(x.date),
|
||||
incoming: x.network.incomingBytes,
|
||||
outgoing: x.network.outgoingBytes
|
||||
}));
|
||||
|
||||
return [{
|
||||
datasets: [{
|
||||
label: 'Incoming',
|
||||
fill: true,
|
||||
backgroundColor: rgba(colors.incoming),
|
||||
borderColor: colors.incoming,
|
||||
borderWidth: 2,
|
||||
pointBackgroundColor: '#fff',
|
||||
lineTension: 0,
|
||||
data: data.map(x => ({ t: x.date, y: x.incoming }))
|
||||
}, {
|
||||
label: 'Outgoing',
|
||||
fill: true,
|
||||
backgroundColor: rgba(colors.outgoing),
|
||||
borderColor: colors.outgoing,
|
||||
borderWidth: 2,
|
||||
pointBackgroundColor: '#fff',
|
||||
lineTension: 0,
|
||||
data: data.map(x => ({ t: x.date, y: x.outgoing }))
|
||||
}]
|
||||
}, {
|
||||
scales: {
|
||||
yAxes: [{
|
||||
ticks: {
|
||||
callback: value => {
|
||||
return Vue.filter('bytes')(value, 1);
|
||||
}
|
||||
}
|
||||
}]
|
||||
},
|
||||
tooltips: {
|
||||
callbacks: {
|
||||
label: (tooltipItem, data) => {
|
||||
const label = data.datasets[tooltipItem.datasetIndex].label || '';
|
||||
return `${label}: ${Vue.filter('bytes')(tooltipItem.yLabel, 1)}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
},
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -64,7 +64,7 @@ export default Vue.extend({
|
||||
});
|
||||
|
||||
this.$emit('closed');
|
||||
this.$destroy();
|
||||
this.destroyDom();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -78,7 +78,7 @@ export default Vue.extend({
|
||||
scale: 0.8,
|
||||
duration: 300,
|
||||
easing: [ 0.5, -0.5, 1, 0.5 ],
|
||||
complete: () => this.$destroy()
|
||||
complete: () => this.destroyDom()
|
||||
});
|
||||
},
|
||||
onBgClick() {
|
||||
|
@ -14,7 +14,7 @@
|
||||
<p class="empty" v-if="!fetching && users.length == 0">%i18n:@empty%</p>
|
||||
<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:@fetching%<mk-ellipsis/></p>
|
||||
<a class="refresh" @click="refresh">%i18n:@refresh%</a>
|
||||
<button class="close" @click="$destroy()" title="%i18n:@close%">%fa:times%</button>
|
||||
<button class="close" @click="destroyDom()" title="%i18n:@close%">%fa:times%</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -26,7 +26,7 @@ export default Vue.extend({
|
||||
opacity: 0,
|
||||
duration: 100,
|
||||
easing: 'linear',
|
||||
complete: () => this.$destroy()
|
||||
complete: () => this.destroyDom()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="ldwbgwstjsdgcjruamauqdrffetqudry" v-if="image.isSensitive && hide" @click="hide = false">
|
||||
<div class="ldwbgwstjsdgcjruamauqdrffetqudry" v-if="image.isSensitive && hide && !$store.state.device.alwaysShowNsfw" @click="hide = false">
|
||||
<div>
|
||||
<b>%fa:exclamation-triangle% %i18n:@sensitive%</b>
|
||||
<span>%i18n:@click-to-show%</span>
|
||||
|
@ -28,7 +28,7 @@ export default Vue.extend({
|
||||
opacity: 0,
|
||||
duration: 100,
|
||||
easing: 'linear',
|
||||
complete: () => this.$destroy()
|
||||
complete: () => this.destroyDom()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -37,20 +37,26 @@
|
||||
</router-link>
|
||||
</header>
|
||||
<div class="body">
|
||||
<div class="text">
|
||||
<span v-if="p.isHidden" style="opacity: 0.5">%i18n:@private%</span>
|
||||
<span v-if="p.deletedAt" style="opacity: 0.5">%i18n:@deleted%</span>
|
||||
<misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i"/>
|
||||
</div>
|
||||
<div class="media" v-if="p.media.length > 0">
|
||||
<mk-media-list :media-list="p.media" :raw="true"/>
|
||||
</div>
|
||||
<mk-poll v-if="p.poll" :note="p"/>
|
||||
<mk-url-preview v-for="url in urls" :url="url" :key="url" :detail="true"/>
|
||||
<a class="location" v-if="p.geo" :href="`https://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% %i18n:@location%</a>
|
||||
<div class="map" v-if="p.geo" ref="map"></div>
|
||||
<div class="renote" v-if="p.renote">
|
||||
<mk-note-preview :note="p.renote"/>
|
||||
<p v-if="p.cw != null" class="cw">
|
||||
<span class="text" v-if="p.cw != ''">{{ p.cw }}</span>
|
||||
<mk-cw-button v-model="showContent"/>
|
||||
</p>
|
||||
<div class="content" v-show="p.cw == null || showContent">
|
||||
<div class="text">
|
||||
<span v-if="p.isHidden" style="opacity: 0.5">%i18n:@private%</span>
|
||||
<span v-if="p.deletedAt" style="opacity: 0.5">%i18n:@deleted%</span>
|
||||
<misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i"/>
|
||||
</div>
|
||||
<div class="files" v-if="p.files.length > 0">
|
||||
<mk-media-list :media-list="p.files" :raw="true"/>
|
||||
</div>
|
||||
<mk-poll v-if="p.poll" :note="p"/>
|
||||
<mk-url-preview v-for="url in urls" :url="url" :key="url" :detail="true"/>
|
||||
<a class="location" v-if="p.geo" :href="`https://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% %i18n:@location%</a>
|
||||
<div class="map" v-if="p.geo" ref="map"></div>
|
||||
<div class="renote" v-if="p.renote">
|
||||
<mk-note-preview :note="p.renote"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
@ -86,6 +92,7 @@ import MkRenoteFormWindow from './renote-form-window.vue';
|
||||
import MkNoteMenu from '../../../common/views/components/note-menu.vue';
|
||||
import MkReactionPicker from '../../../common/views/components/reaction-picker.vue';
|
||||
import XSub from './notes.note.sub.vue';
|
||||
import { sum } from '../../../../../prelude/array';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
@ -104,6 +111,7 @@ export default Vue.extend({
|
||||
|
||||
data() {
|
||||
return {
|
||||
showContent: false,
|
||||
conversation: [],
|
||||
conversationFetching: false,
|
||||
replies: []
|
||||
@ -114,22 +122,24 @@ export default Vue.extend({
|
||||
isRenote(): boolean {
|
||||
return (this.note.renote &&
|
||||
this.note.text == null &&
|
||||
this.note.mediaIds.length == 0 &&
|
||||
this.note.fileIds.length == 0 &&
|
||||
this.note.poll == null);
|
||||
},
|
||||
|
||||
p(): any {
|
||||
return this.isRenote ? this.note.renote : this.note;
|
||||
},
|
||||
|
||||
reactionsCount(): number {
|
||||
return this.p.reactionCounts
|
||||
? Object.keys(this.p.reactionCounts)
|
||||
.map(key => this.p.reactionCounts[key])
|
||||
.reduce((a, b) => a + b)
|
||||
? sum(Object.values(this.p.reactionCounts))
|
||||
: 0;
|
||||
},
|
||||
|
||||
title(): string {
|
||||
return new Date(this.p.createdAt).toLocaleString();
|
||||
},
|
||||
|
||||
urls(): string[] {
|
||||
if (this.p.text) {
|
||||
const ast = parse(this.p.text);
|
||||
@ -184,22 +194,26 @@ export default Vue.extend({
|
||||
this.conversation = conversation.reverse();
|
||||
});
|
||||
},
|
||||
|
||||
reply() {
|
||||
(this as any).os.new(MkPostFormWindow, {
|
||||
reply: this.p
|
||||
});
|
||||
},
|
||||
|
||||
renote() {
|
||||
(this as any).os.new(MkRenoteFormWindow, {
|
||||
note: this.p
|
||||
});
|
||||
},
|
||||
|
||||
react() {
|
||||
(this as any).os.new(MkReactionPicker, {
|
||||
source: this.$refs.reactButton,
|
||||
note: this.p
|
||||
});
|
||||
},
|
||||
|
||||
menu() {
|
||||
(this as any).os.new(MkNoteMenu, {
|
||||
source: this.$refs.menuButton,
|
||||
@ -327,37 +341,49 @@ root(isDark)
|
||||
> .body
|
||||
padding 8px 0
|
||||
|
||||
> .text
|
||||
> .cw
|
||||
cursor default
|
||||
display block
|
||||
margin 0
|
||||
padding 0
|
||||
overflow-wrap break-word
|
||||
font-size 1.5em
|
||||
color isDark ? #fff : #717171
|
||||
|
||||
> .renote
|
||||
margin 8px 0
|
||||
> .text
|
||||
margin-right 8px
|
||||
|
||||
> .mk-note-preview
|
||||
padding 16px
|
||||
border dashed 1px #c0dac6
|
||||
border-radius 8px
|
||||
> .content
|
||||
> .text
|
||||
cursor default
|
||||
display block
|
||||
margin 0
|
||||
padding 0
|
||||
overflow-wrap break-word
|
||||
font-size 1.5em
|
||||
color isDark ? #fff : #717171
|
||||
|
||||
> .location
|
||||
margin 4px 0
|
||||
font-size 12px
|
||||
color #ccc
|
||||
> .renote
|
||||
margin 8px 0
|
||||
|
||||
> .map
|
||||
width 100%
|
||||
height 300px
|
||||
> *
|
||||
padding 16px
|
||||
border dashed 1px #c0dac6
|
||||
border-radius 8px
|
||||
|
||||
&:empty
|
||||
display none
|
||||
> .location
|
||||
margin 4px 0
|
||||
font-size 12px
|
||||
color #ccc
|
||||
|
||||
> .mk-url-preview
|
||||
margin-top 8px
|
||||
> .map
|
||||
width 100%
|
||||
height 300px
|
||||
|
||||
&:empty
|
||||
display none
|
||||
|
||||
> .mk-url-preview
|
||||
margin-top 8px
|
||||
|
||||
> footer
|
||||
font-size 1.2em
|
||||
|
@ -1,10 +1,16 @@
|
||||
<template>
|
||||
<div class="mk-note-preview" :title="title">
|
||||
<div class="qiziqtywpuaucsgarwajitwaakggnisj" :title="title">
|
||||
<mk-avatar class="avatar" :user="note.user" v-if="!mini"/>
|
||||
<div class="main">
|
||||
<mk-note-header class="header" :note="note" :mini="true"/>
|
||||
<div class="body">
|
||||
<mk-sub-note-content class="text" :note="note"/>
|
||||
<p v-if="note.cw != null" class="cw">
|
||||
<span class="text" v-if="note.cw != ''">{{ note.cw }}</span>
|
||||
<mk-cw-button v-model="showContent"/>
|
||||
</p>
|
||||
<div class="content" v-show="note.cw == null || showContent">
|
||||
<mk-sub-note-content class="text" :note="note"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -25,6 +31,13 @@ export default Vue.extend({
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
showContent: false
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
title(): string {
|
||||
return new Date(this.note.createdAt).toLocaleString();
|
||||
@ -52,16 +65,28 @@ root(isDark)
|
||||
|
||||
> .body
|
||||
|
||||
> .text
|
||||
> .cw
|
||||
cursor default
|
||||
display block
|
||||
margin 0
|
||||
padding 0
|
||||
color isDark ? #959ba7 : #717171
|
||||
overflow-wrap break-word
|
||||
color isDark ? #fff : #717171
|
||||
|
||||
.mk-note-preview[data-darkmode]
|
||||
> .text
|
||||
margin-right 8px
|
||||
|
||||
> .content
|
||||
> .text
|
||||
cursor default
|
||||
margin 0
|
||||
padding 0
|
||||
color isDark ? #959ba7 : #717171
|
||||
|
||||
.qiziqtywpuaucsgarwajitwaakggnisj[data-darkmode]
|
||||
root(true)
|
||||
|
||||
.mk-note-preview:not([data-darkmode])
|
||||
.qiziqtywpuaucsgarwajitwaakggnisj:not([data-darkmode])
|
||||
root(false)
|
||||
|
||||
</style>
|
||||
|
@ -1,10 +1,16 @@
|
||||
<template>
|
||||
<div class="sub" :title="title">
|
||||
<div class="tkfdzaxtkdeianobciwadajxzbddorql" :title="title">
|
||||
<mk-avatar class="avatar" :user="note.user"/>
|
||||
<div class="main">
|
||||
<mk-note-header class="header" :note="note"/>
|
||||
<div class="body">
|
||||
<mk-sub-note-content class="text" :note="note"/>
|
||||
<p v-if="note.cw != null" class="cw">
|
||||
<span class="text" v-if="note.cw != ''">{{ note.cw }}</span>
|
||||
<mk-cw-button v-model="showContent"/>
|
||||
</p>
|
||||
<div class="content" v-show="note.cw == null || showContent">
|
||||
<mk-sub-note-content class="text" :note="note"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -14,7 +20,19 @@
|
||||
import Vue from 'vue';
|
||||
|
||||
export default Vue.extend({
|
||||
props: ['note'],
|
||||
props: {
|
||||
note: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
showContent: false
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
title(): string {
|
||||
return new Date(this.note.createdAt).toLocaleString();
|
||||
@ -48,20 +66,32 @@ root(isDark)
|
||||
|
||||
> .body
|
||||
|
||||
> .text
|
||||
> .cw
|
||||
cursor default
|
||||
display block
|
||||
margin 0
|
||||
padding 0
|
||||
color isDark ? #959ba7 : #717171
|
||||
overflow-wrap break-word
|
||||
color isDark ? #fff : #717171
|
||||
|
||||
pre
|
||||
max-height 120px
|
||||
font-size 80%
|
||||
> .text
|
||||
margin-right 8px
|
||||
|
||||
.sub[data-darkmode]
|
||||
> .content
|
||||
> .text
|
||||
cursor default
|
||||
margin 0
|
||||
padding 0
|
||||
color isDark ? #959ba7 : #717171
|
||||
|
||||
pre
|
||||
max-height 120px
|
||||
font-size 80%
|
||||
|
||||
.tkfdzaxtkdeianobciwadajxzbddorql[data-darkmode]
|
||||
root(true)
|
||||
|
||||
.sub:not([data-darkmode])
|
||||
.tkfdzaxtkdeianobciwadajxzbddorql:not([data-darkmode])
|
||||
root(false)
|
||||
|
||||
</style>
|
||||
|
@ -18,7 +18,7 @@
|
||||
<div class="body">
|
||||
<p v-if="p.cw != null" class="cw">
|
||||
<span class="text" v-if="p.cw != ''">{{ p.cw }}</span>
|
||||
<span class="toggle" @click="showContent = !showContent">{{ showContent ? '%i18n:@hide%' : '%i18n:@see-more%' }}</span>
|
||||
<mk-cw-button v-model="showContent"/>
|
||||
</p>
|
||||
<div class="content" v-show="p.cw == null || showContent">
|
||||
<div class="text">
|
||||
@ -28,15 +28,13 @@
|
||||
<misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i" :class="$style.text"/>
|
||||
<a class="rp" v-if="p.renote">RP:</a>
|
||||
</div>
|
||||
<div class="media" v-if="p.media.length > 0">
|
||||
<mk-media-list :media-list="p.media"/>
|
||||
<div class="files" v-if="p.files.length > 0">
|
||||
<mk-media-list :media-list="p.files"/>
|
||||
</div>
|
||||
<mk-poll v-if="p.poll" :note="p" ref="pollViewer"/>
|
||||
<a class="location" v-if="p.geo" :href="`https://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% 位置情報</a>
|
||||
<div class="map" v-if="p.geo" ref="map"></div>
|
||||
<div class="renote" v-if="p.renote">
|
||||
<mk-note-preview :note="p.renote"/>
|
||||
</div>
|
||||
<div class="renote" v-if="p.renote"><mk-note-preview :note="p.renote"/></div>
|
||||
<mk-url-preview v-for="url in urls" :url="url" :key="url"/>
|
||||
</div>
|
||||
</div>
|
||||
@ -78,6 +76,7 @@ import MkRenoteFormWindow from './renote-form-window.vue';
|
||||
import MkNoteMenu from '../../../common/views/components/note-menu.vue';
|
||||
import MkReactionPicker from '../../../common/views/components/reaction-picker.vue';
|
||||
import XSub from './notes.note.sub.vue';
|
||||
import { sum } from '../../../../../prelude/array';
|
||||
|
||||
function focus(el, fn) {
|
||||
const target = fn(el);
|
||||
@ -95,7 +94,12 @@ export default Vue.extend({
|
||||
XSub
|
||||
},
|
||||
|
||||
props: ['note'],
|
||||
props: {
|
||||
note: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
@ -110,7 +114,7 @@ export default Vue.extend({
|
||||
isRenote(): boolean {
|
||||
return (this.note.renote &&
|
||||
this.note.text == null &&
|
||||
this.note.mediaIds.length == 0 &&
|
||||
this.note.fileIds.length == 0 &&
|
||||
this.note.poll == null);
|
||||
},
|
||||
|
||||
@ -120,9 +124,7 @@ export default Vue.extend({
|
||||
|
||||
reactionsCount(): number {
|
||||
return this.p.reactionCounts
|
||||
? Object.keys(this.p.reactionCounts)
|
||||
.map(key => this.p.reactionCounts[key])
|
||||
.reduce((a, b) => a + b)
|
||||
? sum(Object.values(this.p.reactionCounts))
|
||||
: 0;
|
||||
},
|
||||
|
||||
@ -399,19 +401,6 @@ root(isDark)
|
||||
> .text
|
||||
margin-right 8px
|
||||
|
||||
> .toggle
|
||||
display inline-block
|
||||
padding 4px 8px
|
||||
font-size 0.7em
|
||||
color isDark ? #393f4f : #fff
|
||||
background isDark ? #687390 : #b1b9c1
|
||||
border-radius 2px
|
||||
cursor pointer
|
||||
user-select none
|
||||
|
||||
&:hover
|
||||
background isDark ? #707b97 : #bbc4ce
|
||||
|
||||
> .content
|
||||
|
||||
> .text
|
||||
@ -470,7 +459,7 @@ root(isDark)
|
||||
> .renote
|
||||
margin 8px 0
|
||||
|
||||
> .mk-note-preview
|
||||
> *
|
||||
padding 16px
|
||||
border dashed 1px isDark ? #4e945e : #c0dac6
|
||||
border-radius 8px
|
||||
|
@ -10,8 +10,7 @@
|
||||
</div>
|
||||
|
||||
<!-- トランジションを有効にするとなぜかメモリリークする -->
|
||||
<!--<transition-group name="mk-notes" class="transition">-->
|
||||
<div class="notes">
|
||||
<transition-group name="mk-notes" class="notes transition" tag="div">
|
||||
<template v-for="(note, i) in _notes">
|
||||
<x-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)"/>
|
||||
<p class="date" :key="note.id + '_date'" v-if="i != notes.length - 1 && note._date != _notes[i + 1]._date">
|
||||
@ -19,8 +18,7 @@
|
||||
<span>%fa:angle-down%{{ _notes[i + 1]._datetext }}</span>
|
||||
</p>
|
||||
</template>
|
||||
</div>
|
||||
<!--</transition-group>-->
|
||||
</transition-group>
|
||||
|
||||
<footer v-if="more">
|
||||
<button @click="loadMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }">
|
||||
@ -122,7 +120,7 @@ export default Vue.extend({
|
||||
prepend(note, silent = false) {
|
||||
//#region 弾く
|
||||
const isMyNote = note.userId == this.$store.state.i.id;
|
||||
const isPureRenote = note.renoteId != null && note.text == null && note.mediaIds.length == 0 && note.poll == null;
|
||||
const isPureRenote = note.renoteId != null && note.text == null && note.fileIds.length == 0 && note.poll == null;
|
||||
|
||||
if (this.$store.state.settings.showMyRenotes === false) {
|
||||
if (isMyNote && isPureRenote) {
|
||||
|
@ -2,8 +2,7 @@
|
||||
<div class="mk-notifications">
|
||||
<div class="notifications" v-if="notifications.length != 0">
|
||||
<!-- トランジションを有効にするとなぜかメモリリークする -->
|
||||
<!-- <transition-group name="mk-notifications" class="transition"> -->
|
||||
<div>
|
||||
<transition-group name="mk-notifications" class="transition" tag="div">
|
||||
<template v-for="(notification, i) in _notifications">
|
||||
<div class="notification" :class="notification.type" :key="notification.id">
|
||||
<mk-time :time="notification.createdAt"/>
|
||||
@ -97,8 +96,7 @@
|
||||
<span>%fa:angle-down%{{ _notifications[i + 1]._datetext }}</span>
|
||||
</p>
|
||||
</template>
|
||||
</div>
|
||||
<!-- </transition-group> -->
|
||||
</transition-group>
|
||||
</div>
|
||||
<button class="more" :class="{ fetching: fetchingMoreNotifications }" v-if="moreNotifications" @click="fetchMoreNotifications" :disabled="fetchingMoreNotifications">
|
||||
<template v-if="fetchingMoreNotifications">%fa:spinner .pulse .fw%</template>{{ fetchingMoreNotifications ? '%i18n:common.loading%' : '%i18n:@more%' }}
|
||||
|
@ -4,7 +4,7 @@
|
||||
<span class="icon" v-if="geo">%fa:map-marker-alt%</span>
|
||||
<span v-if="!reply">%i18n:@note%</span>
|
||||
<span v-if="reply">%i18n:@reply%</span>
|
||||
<span class="count" v-if="media.length != 0">{{ '%i18n:@attaches%'.replace('{}', media.length) }}</span>
|
||||
<span class="count" v-if="files.length != 0">{{ '%i18n:@attaches%'.replace('{}', files.length) }}</span>
|
||||
<span class="count" v-if="uploadings.length != 0">{{ '%i18n:@uploading-media%'.replace('{}', uploadings.length) }}<mk-ellipsis/></span>
|
||||
</span>
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
:reply="reply"
|
||||
@posted="onPosted"
|
||||
@change-uploadings="onChangeUploadings"
|
||||
@change-attached-media="onChangeMedia"
|
||||
@change-attached-files="onChangeFiles"
|
||||
@geo-attached="onGeoAttached"
|
||||
@geo-dettached="onGeoDettached"/>
|
||||
</div>
|
||||
@ -29,7 +29,7 @@ export default Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
uploadings: [],
|
||||
media: [],
|
||||
files: [],
|
||||
geo: null
|
||||
};
|
||||
},
|
||||
@ -42,8 +42,8 @@ export default Vue.extend({
|
||||
onChangeUploadings(files) {
|
||||
this.uploadings = files;
|
||||
},
|
||||
onChangeMedia(media) {
|
||||
this.media = media;
|
||||
onChangeFiles(files) {
|
||||
this.files = files;
|
||||
},
|
||||
onGeoAttached(geo) {
|
||||
this.geo = geo;
|
||||
|
@ -20,7 +20,7 @@
|
||||
@keydown="onKeydown" @paste="onPaste" :placeholder="placeholder"
|
||||
v-autocomplete="'text'"
|
||||
></textarea>
|
||||
<div class="medias" :class="{ with: poll }" v-show="files.length != 0">
|
||||
<div class="files" :class="{ with: poll }" v-show="files.length != 0">
|
||||
<x-draggable :list="files" :options="{ animation: 150 }">
|
||||
<div v-for="file in files" :key="file.id">
|
||||
<div class="img" :style="{ backgroundImage: `url(${file.thumbnailUrl})` }" :title="file.name"></div>
|
||||
@ -45,7 +45,7 @@
|
||||
<span v-if="visibility === 'specified'">%fa:envelope%</span>
|
||||
<span v-if="visibility === 'private'">%fa:lock%</span>
|
||||
</button>
|
||||
<p class="text-count" :class="{ over: text.length > 1000 }">{{ 1000 - text.length }}</p>
|
||||
<p class="text-count" :class="{ over: this.trimmedLength(text) > 1000 }">{{ 1000 - this.trimmedLength(text) }}</p>
|
||||
<button :class="{ posting }" class="submit" :disabled="!canPost" @click="post">
|
||||
{{ posting ? '%i18n:@posting%' : submitText }}<mk-ellipsis v-if="posting"/>
|
||||
</button>
|
||||
@ -62,6 +62,9 @@ import getFace from '../../../common/scripts/get-face';
|
||||
import MkVisibilityChooser from '../../../common/views/components/visibility-chooser.vue';
|
||||
import parse from '../../../../../mfm/parse';
|
||||
import { host } from '../../../config';
|
||||
import { erase, unique } from '../../../../../prelude/array';
|
||||
import { length } from 'stringz';
|
||||
import parseAcct from '../../../../../misc/acct/parse';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
@ -99,7 +102,7 @@ export default Vue.extend({
|
||||
useCw: false,
|
||||
cw: null,
|
||||
geo: null,
|
||||
visibility: this.$store.state.device.visibility || 'public',
|
||||
visibility: this.$store.state.settings.rememberNoteVisibility ? (this.$store.state.device.visibility || this.$store.state.settings.defaultNoteVisibility) : this.$store.state.settings.defaultNoteVisibility,
|
||||
visibleUsers: [],
|
||||
autocomplete: null,
|
||||
draghover: false,
|
||||
@ -145,7 +148,7 @@ export default Vue.extend({
|
||||
canPost(): boolean {
|
||||
return !this.posting &&
|
||||
(1 <= this.text.length || 1 <= this.files.length || this.poll || this.renote) &&
|
||||
(this.text.trim().length <= 1000);
|
||||
(length(this.text.trim()) <= 1000);
|
||||
}
|
||||
},
|
||||
|
||||
@ -188,7 +191,7 @@ export default Vue.extend({
|
||||
(this.$refs.poll as any).set(draft.data.poll);
|
||||
});
|
||||
}
|
||||
this.$emit('change-attached-media', this.files);
|
||||
this.$emit('change-attached-files', this.files);
|
||||
}
|
||||
}
|
||||
|
||||
@ -197,6 +200,10 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
methods: {
|
||||
trimmedLength(text: string) {
|
||||
return length(text.trim());
|
||||
},
|
||||
|
||||
addTag(tag: string) {
|
||||
insertTextAtCursor(this.$refs.text, ` #${tag} `);
|
||||
},
|
||||
@ -225,12 +232,12 @@ export default Vue.extend({
|
||||
|
||||
attachMedia(driveFile) {
|
||||
this.files.push(driveFile);
|
||||
this.$emit('change-attached-media', this.files);
|
||||
this.$emit('change-attached-files', this.files);
|
||||
},
|
||||
|
||||
detachMedia(id) {
|
||||
this.files = this.files.filter(x => x.id != id);
|
||||
this.$emit('change-attached-media', this.files);
|
||||
this.$emit('change-attached-files', this.files);
|
||||
},
|
||||
|
||||
onChangeFile() {
|
||||
@ -249,7 +256,7 @@ export default Vue.extend({
|
||||
this.text = '';
|
||||
this.files = [];
|
||||
this.poll = false;
|
||||
this.$emit('change-attached-media', this.files);
|
||||
this.$emit('change-attached-files', this.files);
|
||||
},
|
||||
|
||||
onKeydown(e) {
|
||||
@ -297,7 +304,7 @@ export default Vue.extend({
|
||||
if (driveFile != null && driveFile != '') {
|
||||
const file = JSON.parse(driveFile);
|
||||
this.files.push(file);
|
||||
this.$emit('change-attached-media', this.files);
|
||||
this.$emit('change-attached-files', this.files);
|
||||
e.preventDefault();
|
||||
}
|
||||
//#endregion
|
||||
@ -336,17 +343,16 @@ export default Vue.extend({
|
||||
addVisibleUser() {
|
||||
(this as any).apis.input({
|
||||
title: '%i18n:@enter-username%'
|
||||
}).then(username => {
|
||||
(this as any).api('users/show', {
|
||||
username
|
||||
}).then(user => {
|
||||
}).then(acct => {
|
||||
if (acct.startsWith('@')) acct = acct.substr(1);
|
||||
(this as any).api('users/show', parseAcct(acct)).then(user => {
|
||||
this.visibleUsers.push(user);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
removeVisibleUser(user) {
|
||||
this.visibleUsers = this.visibleUsers.filter(u => u != user);
|
||||
this.visibleUsers = erase(user, this.visibleUsers);
|
||||
},
|
||||
|
||||
post() {
|
||||
@ -354,7 +360,7 @@ export default Vue.extend({
|
||||
|
||||
(this as any).api('notes/create', {
|
||||
text: this.text == '' ? undefined : this.text,
|
||||
mediaIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined,
|
||||
fileIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined,
|
||||
replyId: this.reply ? this.reply.id : undefined,
|
||||
renoteId: this.renote ? this.renote.id : undefined,
|
||||
poll: this.poll ? (this.$refs.poll as any).get() : undefined,
|
||||
@ -391,7 +397,7 @@ export default Vue.extend({
|
||||
if (this.text && this.text != '') {
|
||||
const hashtags = parse(this.text).filter(x => x.type == 'hashtag').map(x => x.hashtag);
|
||||
const history = JSON.parse(localStorage.getItem('hashtags') || '[]') as string[];
|
||||
localStorage.setItem('hashtags', JSON.stringify(hashtags.concat(history).reduce((a, c) => a.includes(c) ? a : [...a, c], [])));
|
||||
localStorage.setItem('hashtags', JSON.stringify(unique(hashtags.concat(history))));
|
||||
}
|
||||
},
|
||||
|
||||
@ -514,7 +520,7 @@ root(isDark)
|
||||
margin-right 8px
|
||||
white-space nowrap
|
||||
|
||||
> .medias
|
||||
> .files
|
||||
margin 0
|
||||
padding 0
|
||||
background isDark ? #181b23 : lighten($theme-color, 98%)
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="mk-renote-form">
|
||||
<mk-note-preview :note="note"/>
|
||||
<mk-note-preview class="preview" :note="note"/>
|
||||
<template v-if="!quote">
|
||||
<footer>
|
||||
<a class="quote" v-if="!quote" @click="onQuote">%i18n:@quote%</a>
|
||||
@ -61,7 +61,7 @@ export default Vue.extend({
|
||||
|
||||
root(isDark)
|
||||
|
||||
> .mk-note-preview
|
||||
> .preview
|
||||
margin 16px 22px
|
||||
|
||||
> footer
|
||||
|
@ -1,7 +1,6 @@
|
||||
<template>
|
||||
<div class="root">
|
||||
<template v-if="!fetching">
|
||||
<el-progress :text-inside="true" :stroke-width="18" :percentage="Math.floor((usage / capacity) * 100)"/>
|
||||
<p><b>{{ capacity | bytes }}</b>%i18n:max%<b>{{ usage | bytes }}</b>%i18n:in-use%</p>
|
||||
</template>
|
||||
</div>
|
||||
|
@ -19,7 +19,7 @@
|
||||
</label>
|
||||
<label class="ui from group">
|
||||
<p>%i18n:@birthday%</p>
|
||||
<el-date-picker v-model="birthday" type="date" value-format="yyyy-MM-dd"/>
|
||||
<input type="date" v-model="birthday"/>
|
||||
</label>
|
||||
<button class="ui primary" @click="save">%i18n:@save%</button>
|
||||
<section>
|
||||
@ -30,6 +30,7 @@
|
||||
<h2>%i18n:@other%</h2>
|
||||
<mk-switch v-model="$store.state.i.isBot" @change="onChangeIsBot" text="%i18n:@is-bot%"/>
|
||||
<mk-switch v-model="$store.state.i.isCat" @change="onChangeIsCat" text="%i18n:@is-cat%"/>
|
||||
<mk-switch v-model="alwaysMarkNsfw" text="%i18n:common.always-mark-nsfw%"/>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
@ -46,6 +47,12 @@ export default Vue.extend({
|
||||
birthday: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
alwaysMarkNsfw: {
|
||||
get() { return this.$store.state.i.settings.alwaysMarkNsfw; },
|
||||
set(value) { (this as any).api('i/update', { alwaysMarkNsfw: value }); }
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.name = this.$store.state.i.name || '';
|
||||
this.location = this.$store.state.i.profile.location;
|
||||
|
@ -20,12 +20,28 @@
|
||||
|
||||
<section class="web" v-show="page == 'web'">
|
||||
<h1>%i18n:@behaviour%</h1>
|
||||
<mk-switch v-model="$store.state.settings.fetchOnScroll" @change="onChangeFetchOnScroll" text="%i18n:@fetch-on-scroll%">
|
||||
<mk-switch v-model="fetchOnScroll" text="%i18n:@fetch-on-scroll%">
|
||||
<span>%i18n:@fetch-on-scroll-desc%</span>
|
||||
</mk-switch>
|
||||
<mk-switch v-model="autoPopout" text="%i18n:@auto-popout%">
|
||||
<span>%i18n:@auto-popout-desc%</span>
|
||||
</mk-switch>
|
||||
|
||||
<section>
|
||||
<header>%i18n:@note-visibility%</header>
|
||||
<mk-switch v-model="rememberNoteVisibility" text="%i18n:@remember-note-visibility%"/>
|
||||
<section>
|
||||
<header>%i18n:@default-note-visibility%</header>
|
||||
<ui-select v-model="defaultNoteVisibility">
|
||||
<option value="public">%i18n:common.note-visibility.public%</option>
|
||||
<option value="home">%i18n:common.note-visibility.home%</option>
|
||||
<option value="followers">%i18n:common.note-visibility.followers%</option>
|
||||
<option value="specified">%i18n:common.note-visibility.specified%</option>
|
||||
<option value="private">%i18n:common.note-visibility.private%</option>
|
||||
</ui-select>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<details>
|
||||
<summary>%i18n:@advanced%</summary>
|
||||
<mk-switch v-model="apiViaStream" text="%i18n:@api-via-stream%">
|
||||
@ -43,23 +59,26 @@
|
||||
<button class="ui" @click="updateWallpaper">%i18n:@choose-wallpaper%</button>
|
||||
<button class="ui" @click="deleteWallpaper">%i18n:@delete-wallpaper%</button>
|
||||
<mk-switch v-model="darkmode" text="%i18n:@dark-mode%"/>
|
||||
<mk-switch v-model="$store.state.settings.circleIcons" @change="onChangeCircleIcons" text="%i18n:@circle-icons%"/>
|
||||
<mk-switch v-model="$store.state.settings.gradientWindowHeader" @change="onChangeGradientWindowHeader" text="%i18n:@gradient-window-header%"/>
|
||||
<mk-switch v-model="$store.state.settings.iLikeSushi" @change="onChangeILikeSushi" text="%i18n:common.i-like-sushi%"/>
|
||||
<mk-switch v-model="circleIcons" text="%i18n:@circle-icons%"/>
|
||||
<mk-switch v-model="contrastedAcct" text="%i18n:@contrasted-acct%"/>
|
||||
<mk-switch v-model="showFullAcct" text="%i18n:common.show-full-acct%"/>
|
||||
<mk-switch v-model="gradientWindowHeader" text="%i18n:@gradient-window-header%"/>
|
||||
<mk-switch v-model="iLikeSushi" text="%i18n:common.i-like-sushi%"/>
|
||||
</div>
|
||||
<mk-switch v-model="$store.state.settings.showPostFormOnTopOfTl" @change="onChangeShowPostFormOnTopOfTl" text="%i18n:@post-form-on-timeline%"/>
|
||||
<mk-switch v-model="$store.state.settings.suggestRecentHashtags" @change="onChangeSuggestRecentHashtags" text="%i18n:@suggest-recent-hashtags%"/>
|
||||
<mk-switch v-model="$store.state.settings.showClockOnHeader" @change="onChangeShowClockOnHeader" text="%i18n:@show-clock-on-header%"/>
|
||||
<mk-switch v-model="$store.state.settings.showReplyTarget" @change="onChangeShowReplyTarget" text="%i18n:@show-reply-target%"/>
|
||||
<mk-switch v-model="$store.state.settings.showMyRenotes" @change="onChangeShowMyRenotes" text="%i18n:@show-my-renotes%"/>
|
||||
<mk-switch v-model="$store.state.settings.showRenotedMyNotes" @change="onChangeShowRenotedMyNotes" text="%i18n:@show-renoted-my-notes%"/>
|
||||
<mk-switch v-model="$store.state.settings.showLocalRenotes" @change="onChangeShowLocalRenotes" text="%i18n:@show-local-renotes%"/>
|
||||
<mk-switch v-model="$store.state.settings.showMaps" @change="onChangeShowMaps" text="%i18n:@show-maps%">
|
||||
<mk-switch v-model="showPostFormOnTopOfTl" text="%i18n:@post-form-on-timeline%"/>
|
||||
<mk-switch v-model="suggestRecentHashtags" text="%i18n:@suggest-recent-hashtags%"/>
|
||||
<mk-switch v-model="showClockOnHeader" text="%i18n:@show-clock-on-header%"/>
|
||||
<mk-switch v-model="alwaysShowNsfw" text="%i18n:common.always-show-nsfw%"/>
|
||||
<mk-switch v-model="showReplyTarget" text="%i18n:@show-reply-target%"/>
|
||||
<mk-switch v-model="showMyRenotes" text="%i18n:@show-my-renotes%"/>
|
||||
<mk-switch v-model="showRenotedMyNotes" text="%i18n:@show-renoted-my-notes%"/>
|
||||
<mk-switch v-model="showLocalRenotes" text="%i18n:@show-local-renotes%"/>
|
||||
<mk-switch v-model="showMaps" text="%i18n:@show-maps%">
|
||||
<span>%i18n:@show-maps-desc%</span>
|
||||
</mk-switch>
|
||||
<mk-switch v-model="$store.state.settings.disableAnimatedMfm" @change="onChangeDisableAnimatedMfm" text="%i18n:common.disable-animated-mfm%"/>
|
||||
<mk-switch v-model="$store.state.settings.games.reversi.showBoardLabels" @change="onChangeReversiBoardLabels" text="%i18n:common.show-reversi-board-labels%"/>
|
||||
<mk-switch v-model="$store.state.settings.games.reversi.useContrastStones" @change="onChangeUseContrastReversiStones" text="%i18n:common.use-contrast-reversi-stones%"/>
|
||||
<mk-switch v-model="disableAnimatedMfm" text="%i18n:common.disable-animated-mfm%"/>
|
||||
<mk-switch v-model="games_reversi_showBoardLabels" text="%i18n:common.show-reversi-board-labels%"/>
|
||||
<mk-switch v-model="games_reversi_useContrastStones" text="%i18n:common.use-contrast-reversi-stones%"/>
|
||||
</section>
|
||||
|
||||
<section class="web" v-show="page == 'web'">
|
||||
@ -68,32 +87,31 @@
|
||||
<span>%i18n:@enable-sounds-desc%</span>
|
||||
</mk-switch>
|
||||
<label>%i18n:@volume%</label>
|
||||
<el-slider
|
||||
<input type="range"
|
||||
v-model="soundVolume"
|
||||
:show-input="true"
|
||||
:format-tooltip="v => `${v * 100}%`"
|
||||
:disabled="!enableSounds"
|
||||
:max="1"
|
||||
:step="0.1"
|
||||
max="1"
|
||||
step="0.1"
|
||||
/>
|
||||
<button class="ui button" @click="soundTest">%fa:volume-up% %i18n:@test%</button>
|
||||
</section>
|
||||
|
||||
<section class="web" v-show="page == 'web'">
|
||||
<h1>%i18n:@mobile%</h1>
|
||||
<mk-switch v-model="$store.state.settings.disableViaMobile" @change="onChangeDisableViaMobile" text="%i18n:@disable-via-mobile%"/>
|
||||
<mk-switch v-model="disableViaMobile" text="%i18n:@disable-via-mobile%"/>
|
||||
</section>
|
||||
|
||||
<section class="web" v-show="page == 'web'">
|
||||
<h1>%i18n:@language%</h1>
|
||||
<el-select v-model="lang" placeholder="%i18n:@pick-language%">
|
||||
<el-option-group label="%i18n:@recommended%">
|
||||
<el-option label="%i18n:@auto%" :value="null"/>
|
||||
</el-option-group>
|
||||
<el-option-group label="%i18n:@specify-language%">
|
||||
<el-option v-for="x in langs" :label="x[1]" :value="x[0]" :key="x[0]"/>
|
||||
</el-option-group>
|
||||
</el-select>
|
||||
<select v-model="lang" placeholder="%i18n:@pick-language%">
|
||||
<optgroup label="%i18n:@recommended%">
|
||||
<option value="">%i18n:@auto%</option>
|
||||
</optgroup>
|
||||
|
||||
<optgroup label="%i18n:@specify-language%">
|
||||
<option v-for="x in langs" :value="x[0]" :key="x[0]">{{ x[1] }}</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
<div class="none ui info">
|
||||
<p>%fa:info-circle%%i18n:@language-desc%</p>
|
||||
</div>
|
||||
@ -188,10 +206,6 @@
|
||||
<mk-switch v-model="enableExperimentalFeatures" text="%i18n:@experimental%">
|
||||
<span>%i18n:@experimental-desc%</span>
|
||||
</mk-switch>
|
||||
<details v-if="debug">
|
||||
<summary>%i18n:@tools%</summary>
|
||||
<button class="ui button block" @click="taskmngr">%i18n:@task-manager%</button>
|
||||
</details>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
@ -209,7 +223,6 @@ import XSignins from './settings.signins.vue';
|
||||
import XDrive from './settings.drive.vue';
|
||||
import { url, langs, version } from '../../../config';
|
||||
import checkForUpdate from '../../../common/scripts/check-for-update';
|
||||
import MkTaskManager from './taskmanager.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
@ -276,7 +289,112 @@ export default Vue.extend({
|
||||
enableExperimentalFeatures: {
|
||||
get() { return this.$store.state.device.enableExperimentalFeatures; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'enableExperimentalFeatures', value }); }
|
||||
}
|
||||
},
|
||||
|
||||
alwaysShowNsfw: {
|
||||
get() { return this.$store.state.device.alwaysShowNsfw; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'alwaysShowNsfw', value }); }
|
||||
},
|
||||
|
||||
fetchOnScroll: {
|
||||
get() { return this.$store.state.settings.fetchOnScroll; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'fetchOnScroll', value }); }
|
||||
},
|
||||
|
||||
rememberNoteVisibility: {
|
||||
get() { return this.$store.state.settings.rememberNoteVisibility; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'rememberNoteVisibility', value }); }
|
||||
},
|
||||
|
||||
defaultNoteVisibility: {
|
||||
get() { return this.$store.state.settings.defaultNoteVisibility; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'defaultNoteVisibility', value }); }
|
||||
},
|
||||
|
||||
showReplyTarget: {
|
||||
get() { return this.$store.state.settings.showReplyTarget; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'showReplyTarget', value }); }
|
||||
},
|
||||
|
||||
showMyRenotes: {
|
||||
get() { return this.$store.state.settings.showMyRenotes; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'showMyRenotes', value }); }
|
||||
},
|
||||
|
||||
showRenotedMyNotes: {
|
||||
get() { return this.$store.state.settings.showRenotedMyNotes; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'showRenotedMyNotes', value }); }
|
||||
},
|
||||
|
||||
showLocalRenotes: {
|
||||
get() { return this.$store.state.settings.showLocalRenotes; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'showLocalRenotes', value }); }
|
||||
},
|
||||
|
||||
showPostFormOnTopOfTl: {
|
||||
get() { return this.$store.state.settings.showPostFormOnTopOfTl; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'showPostFormOnTopOfTl', value }); }
|
||||
},
|
||||
|
||||
suggestRecentHashtags: {
|
||||
get() { return this.$store.state.settings.suggestRecentHashtags; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'suggestRecentHashtags', value }); }
|
||||
},
|
||||
|
||||
showClockOnHeader: {
|
||||
get() { return this.$store.state.settings.showClockOnHeader; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'showClockOnHeader', value }); }
|
||||
},
|
||||
|
||||
showMaps: {
|
||||
get() { return this.$store.state.settings.showMaps; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'showMaps', value }); }
|
||||
},
|
||||
|
||||
circleIcons: {
|
||||
get() { return this.$store.state.settings.circleIcons; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'circleIcons', value }); }
|
||||
},
|
||||
|
||||
contrastedAcct: {
|
||||
get() { return this.$store.state.settings.contrastedAcct; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'contrastedAcct', value }); }
|
||||
},
|
||||
|
||||
showFullAcct: {
|
||||
get() { return this.$store.state.settings.showFullAcct; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'showFullAcct', value }); }
|
||||
},
|
||||
|
||||
iLikeSushi: {
|
||||
get() { return this.$store.state.settings.iLikeSushi; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'iLikeSushi', value }); }
|
||||
},
|
||||
|
||||
games_reversi_showBoardLabels: {
|
||||
get() { return this.$store.state.settings.games.reversi.showBoardLabels; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'games.reversi.showBoardLabels', value }); }
|
||||
},
|
||||
|
||||
games_reversi_useContrastStones: {
|
||||
get() { return this.$store.state.settings.games.reversi.useContrastStones; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'games.reversi.useContrastStones', value }); }
|
||||
},
|
||||
|
||||
disableAnimatedMfm: {
|
||||
get() { return this.$store.state.settings.disableAnimatedMfm; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'disableAnimatedMfm', value }); }
|
||||
},
|
||||
|
||||
disableViaMobile: {
|
||||
get() { return this.$store.state.settings.disableViaMobile; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'disableViaMobile', value }); }
|
||||
},
|
||||
|
||||
gradientWindowHeader: {
|
||||
get() { return this.$store.state.settings.gradientWindowHeader; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'gradientWindowHeader', value }); }
|
||||
},
|
||||
},
|
||||
created() {
|
||||
(this as any).os.getMeta().then(meta => {
|
||||
@ -284,9 +402,6 @@ export default Vue.extend({
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
taskmngr() {
|
||||
(this as any).os.new(MkTaskManager);
|
||||
},
|
||||
customizeHome() {
|
||||
this.$router.push('/i/customize-home');
|
||||
this.$emit('done');
|
||||
@ -305,113 +420,11 @@ export default Vue.extend({
|
||||
wallpaperId: null
|
||||
});
|
||||
},
|
||||
onChangeFetchOnScroll(v) {
|
||||
this.$store.dispatch('settings/set', {
|
||||
key: 'fetchOnScroll',
|
||||
value: v
|
||||
});
|
||||
},
|
||||
onChangeAutoWatch(v) {
|
||||
(this as any).api('i/update', {
|
||||
autoWatch: v
|
||||
});
|
||||
},
|
||||
onChangeDark(v) {
|
||||
this.$store.dispatch('settings/set', {
|
||||
key: 'dark',
|
||||
value: v
|
||||
});
|
||||
},
|
||||
onChangeShowPostFormOnTopOfTl(v) {
|
||||
this.$store.dispatch('settings/set', {
|
||||
key: 'showPostFormOnTopOfTl',
|
||||
value: v
|
||||
});
|
||||
},
|
||||
onChangeSuggestRecentHashtags(v) {
|
||||
this.$store.dispatch('settings/set', {
|
||||
key: 'suggestRecentHashtags',
|
||||
value: v
|
||||
});
|
||||
},
|
||||
onChangeShowClockOnHeader(v) {
|
||||
this.$store.dispatch('settings/set', {
|
||||
key: 'showClockOnHeader',
|
||||
value: v
|
||||
});
|
||||
},
|
||||
onChangeShowReplyTarget(v) {
|
||||
this.$store.dispatch('settings/set', {
|
||||
key: 'showReplyTarget',
|
||||
value: v
|
||||
});
|
||||
},
|
||||
onChangeShowMyRenotes(v) {
|
||||
this.$store.dispatch('settings/set', {
|
||||
key: 'showMyRenotes',
|
||||
value: v
|
||||
});
|
||||
},
|
||||
onChangeShowRenotedMyNotes(v) {
|
||||
this.$store.dispatch('settings/set', {
|
||||
key: 'showRenotedMyNotes',
|
||||
value: v
|
||||
});
|
||||
},
|
||||
onChangeShowLocalRenotes(v) {
|
||||
this.$store.dispatch('settings/set', {
|
||||
key: 'showLocalRenotes',
|
||||
value: v
|
||||
});
|
||||
},
|
||||
onChangeShowMaps(v) {
|
||||
this.$store.dispatch('settings/set', {
|
||||
key: 'showMaps',
|
||||
value: v
|
||||
});
|
||||
},
|
||||
onChangeCircleIcons(v) {
|
||||
this.$store.dispatch('settings/set', {
|
||||
key: 'circleIcons',
|
||||
value: v
|
||||
});
|
||||
},
|
||||
onChangeILikeSushi(v) {
|
||||
this.$store.dispatch('settings/set', {
|
||||
key: 'iLikeSushi',
|
||||
value: v
|
||||
});
|
||||
},
|
||||
onChangeReversiBoardLabels(v) {
|
||||
this.$store.dispatch('settings/set', {
|
||||
key: 'games.reversi.showBoardLabels',
|
||||
value: v
|
||||
});
|
||||
},
|
||||
onChangeUseContrastReversiStones(v) {
|
||||
this.$store.dispatch('settings/set', {
|
||||
key: 'games.reversi.useContrastStones',
|
||||
value: v
|
||||
});
|
||||
},
|
||||
onChangeDisableAnimatedMfm(v) {
|
||||
this.$store.dispatch('settings/set', {
|
||||
key: 'disableAnimatedMfm',
|
||||
value: v
|
||||
});
|
||||
},
|
||||
onChangeGradientWindowHeader(v) {
|
||||
this.$store.dispatch('settings/set', {
|
||||
key: 'gradientWindowHeader',
|
||||
value: v
|
||||
});
|
||||
},
|
||||
onChangeDisableViaMobile(v) {
|
||||
this.$store.dispatch('settings/set', {
|
||||
key: 'disableViaMobile',
|
||||
value: v
|
||||
});
|
||||
},
|
||||
checkForUpdate() {
|
||||
this.checkingForUpdate = true;
|
||||
checkForUpdate((this as any).os, true, true).then(newer => {
|
||||
|
@ -7,9 +7,9 @@
|
||||
<misskey-flavored-markdown v-if="note.text" :text="note.text" :i="$store.state.i"/>
|
||||
<a class="rp" v-if="note.renoteId" :href="`/notes/${note.renoteId}`">RP: ...</a>
|
||||
</div>
|
||||
<details v-if="note.media.length > 0">
|
||||
<summary>({{ '%i18n:@media-count%'.replace('{}', note.media.length) }})</summary>
|
||||
<mk-media-list :media-list="note.media"/>
|
||||
<details v-if="note.files.length > 0">
|
||||
<summary>({{ '%i18n:@media-count%'.replace('{}', note.files.length) }})</summary>
|
||||
<mk-media-list :media-list="note.files"/>
|
||||
</details>
|
||||
<details v-if="note.poll">
|
||||
<summary>%i18n:@poll%</summary>
|
||||
|
@ -1,219 +0,0 @@
|
||||
<template>
|
||||
<mk-window ref="window" width="750px" height="500px" @closed="$destroy" name="TaskManager">
|
||||
<span slot="header" :class="$style.header">%fa:stethoscope%%i18n:@title%</span>
|
||||
<el-tabs :class="$style.content">
|
||||
<el-tab-pane label="Requests">
|
||||
<el-table
|
||||
:data="os.requests"
|
||||
style="width: 100%"
|
||||
:default-sort="{prop: 'date', order: 'descending'}"
|
||||
>
|
||||
<el-table-column type="expand">
|
||||
<template slot-scope="props">
|
||||
<pre>{{ props.row.data }}</pre>
|
||||
<pre>{{ props.row.res }}</pre>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
label="Requested at"
|
||||
prop="date"
|
||||
sortable
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<b style="margin-right: 8px">{{ scope.row.date.getTime() }}</b>
|
||||
<span>(<mk-time :time="scope.row.date"/>)</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
label="Name"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<b>{{ scope.row.name }}</b>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
label="Status"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.status || '(pending)' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="Streams">
|
||||
<el-table
|
||||
:data="os.connections"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column
|
||||
label="Uptime"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<mk-timer v-if="scope.row.connectedAt" :time="scope.row.connectedAt"/>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
label="Name"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<b>{{ scope.row.name == '' ? '[Home]' : scope.row.name }}</b>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
label="User"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.user || '(anonymous)' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="state"
|
||||
label="State"
|
||||
/>
|
||||
|
||||
<el-table-column
|
||||
prop="in"
|
||||
label="In"
|
||||
/>
|
||||
|
||||
<el-table-column
|
||||
prop="out"
|
||||
label="Out"
|
||||
/>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="Streams (Inspect)">
|
||||
<el-tabs type="card" style="height:50%">
|
||||
<el-tab-pane v-for="c in os.connections" :label="c.name == '' ? '[Home]' : c.name" :key="c.id" :name="c.id" ref="connectionsTab">
|
||||
<div style="padding: 12px 0 0 12px">
|
||||
<el-button size="mini" @click="send(c)">Send</el-button>
|
||||
<el-button size="mini" type="warning" @click="c.isSuspended = true" v-if="!c.isSuspended">Suspend</el-button>
|
||||
<el-button size="mini" type="success" @click="c.isSuspended = false" v-else>Resume</el-button>
|
||||
<el-button size="mini" type="danger" @click="c.close">Disconnect</el-button>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
:data="c.inout"
|
||||
style="width: 100%"
|
||||
:default-sort="{prop: 'at', order: 'descending'}"
|
||||
>
|
||||
<el-table-column type="expand">
|
||||
<template slot-scope="props">
|
||||
<pre>{{ props.row.data }}</pre>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
label="Date"
|
||||
prop="at"
|
||||
sortable
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<b style="margin-right: 8px">{{ scope.row.at.getTime() }}</b>
|
||||
<span>(<mk-time :time="scope.row.at"/>)</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
label="Type"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<span>{{ getMessageType(scope.row.data) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
label="Incoming / Outgoing"
|
||||
prop="type"
|
||||
/>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="Windows">
|
||||
<el-table
|
||||
:data="Array.from(os.windows.windows)"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column
|
||||
label="Name"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<b>{{ scope.row.name || '(unknown)' }}</b>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
label="Operations"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="danger" @click="scope.row.close">Close</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</mk-window>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
|
||||
export default Vue.extend({
|
||||
mounted() {
|
||||
(this as any).os.windows.on('added', this.onWindowsChanged);
|
||||
(this as any).os.windows.on('removed', this.onWindowsChanged);
|
||||
},
|
||||
beforeDestroy() {
|
||||
(this as any).os.windows.off('added', this.onWindowsChanged);
|
||||
(this as any).os.windows.off('removed', this.onWindowsChanged);
|
||||
},
|
||||
methods: {
|
||||
getMessageType(data): string {
|
||||
return data.type ? data.type : '-';
|
||||
},
|
||||
onWindowsChanged() {
|
||||
this.$forceUpdate();
|
||||
},
|
||||
send(c) {
|
||||
(this as any).apis.input({
|
||||
title: 'Send a JSON message',
|
||||
allowEmpty: false
|
||||
}).then(json => {
|
||||
c.send(JSON.parse(json));
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" module>
|
||||
.header
|
||||
> [data-fa]
|
||||
margin-right 4px
|
||||
|
||||
.content
|
||||
height 100%
|
||||
overflow auto
|
||||
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.el-tabs__header {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.el-tabs__item {
|
||||
padding: 0 20px !important;
|
||||
}
|
||||
</style>
|
@ -2,8 +2,8 @@
|
||||
<div class="mk-timeline">
|
||||
<header>
|
||||
<span :data-active="src == 'home'" @click="src = 'home'">%fa:home% %i18n:@home%</span>
|
||||
<span :data-active="src == 'local'" @click="src = 'local'">%fa:R comments% %i18n:@local%</span>
|
||||
<span :data-active="src == 'hybrid'" @click="src = 'hybrid'">%fa:share-alt% %i18n:@hybrid%</span>
|
||||
<span :data-active="src == 'local'" @click="src = 'local'" v-if="enableLocalTimeline">%fa:R comments% %i18n:@local%</span>
|
||||
<span :data-active="src == 'hybrid'" @click="src = 'hybrid'" v-if="enableLocalTimeline">%fa:share-alt% %i18n:@hybrid%</span>
|
||||
<span :data-active="src == 'global'" @click="src = 'global'">%fa:globe% %i18n:@global%</span>
|
||||
<span :data-active="src == 'list'" @click="src = 'list'" v-if="list">%fa:list% {{ list.title }}</span>
|
||||
<button @click="chooseList" title="%i18n:@list%">%fa:list%</button>
|
||||
@ -29,7 +29,8 @@ export default Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
src: 'home',
|
||||
list: null
|
||||
list: null,
|
||||
enableLocalTimeline: false
|
||||
};
|
||||
},
|
||||
|
||||
@ -44,6 +45,10 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
created() {
|
||||
(this as any).os.getMeta().then(meta => {
|
||||
this.enableLocalTimeline = !meta.disableLocalTimeline;
|
||||
});
|
||||
|
||||
if (this.$store.state.device.tl) {
|
||||
this.src = this.$store.state.device.tl.src;
|
||||
if (this.src == 'list') {
|
||||
|
@ -27,7 +27,7 @@ export default Vue.extend({
|
||||
translateY: -64,
|
||||
duration: 500,
|
||||
easing: 'easeInElastic',
|
||||
complete: () => this.$destroy()
|
||||
complete: () => this.destroyDom()
|
||||
});
|
||||
}, 6000);
|
||||
});
|
||||
|
@ -75,7 +75,7 @@ export default Vue.extend({
|
||||
'margin-top': '-8px',
|
||||
duration: 200,
|
||||
easing: 'easeOutQuad',
|
||||
complete: () => this.$destroy()
|
||||
complete: () => this.destroyDom()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,16 @@
|
||||
<template>
|
||||
<div class="root item">
|
||||
<mk-avatar class="avatar" :user="user"/>
|
||||
<div class="main">
|
||||
<header>
|
||||
<router-link class="name" :to="user | userPage" v-user-preview="user.id">{{ user | userName }}</router-link>
|
||||
<span class="username">@{{ user | acct }}</span>
|
||||
</header>
|
||||
<div class="body">
|
||||
<p class="followed" v-if="user.isFollowed">%i18n:@followed%</p>
|
||||
<div class="description">{{ user.description }}</div>
|
||||
<div class="zvdbznxvfixtmujpsigoccczftvpiwqh">
|
||||
<div class="banner" :style="bannerStyle"></div>
|
||||
<mk-avatar class="avatar" :user="user" :disable-preview="true"/>
|
||||
<div class="body">
|
||||
<router-link :to="user | userPage" class="name">{{ user | userName }}</router-link>
|
||||
<span class="username">@{{ user | acct }}</span>
|
||||
<div class="description">
|
||||
<misskey-flavored-markdown v-if="user.description" :text="user.description" :i="$store.state.i"/>
|
||||
</div>
|
||||
<p class="followed" v-if="user.isFollowed">%i18n:@followed%</p>
|
||||
<mk-follow-button :user="user" :size="'big'"/>
|
||||
</div>
|
||||
<mk-follow-button :user="user"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -19,76 +18,69 @@
|
||||
import Vue from 'vue';
|
||||
|
||||
export default Vue.extend({
|
||||
props: ['user']
|
||||
props: ['user'],
|
||||
|
||||
computed: {
|
||||
bannerStyle(): any {
|
||||
if (this.user.bannerUrl == null) return {};
|
||||
return {
|
||||
backgroundColor: this.user.bannerColor && this.user.bannerColor.length == 3 ? `rgb(${ this.user.bannerColor.join(',') })` : null,
|
||||
backgroundImage: `url(${ this.user.bannerUrl })`
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.root.item
|
||||
padding 16px
|
||||
font-size 16px
|
||||
.zvdbznxvfixtmujpsigoccczftvpiwqh
|
||||
$bg = #fff
|
||||
|
||||
&:after
|
||||
content ""
|
||||
display block
|
||||
clear both
|
||||
margin 16px auto
|
||||
max-width calc(100% - 32px)
|
||||
font-size 16px
|
||||
text-align center
|
||||
background $bg
|
||||
box-shadow 0 2px 4px rgba(0, 0, 0, 0.1)
|
||||
|
||||
> .banner
|
||||
height 100px
|
||||
background-color #f9f4f4
|
||||
background-position center
|
||||
background-size cover
|
||||
|
||||
> .avatar
|
||||
display block
|
||||
float left
|
||||
margin 0 16px 0 0
|
||||
width 58px
|
||||
height 58px
|
||||
border-radius 8px
|
||||
margin -40px auto 0 auto
|
||||
width 80px
|
||||
height 80px
|
||||
border-radius 100%
|
||||
border solid 4px $bg
|
||||
|
||||
> .main
|
||||
float left
|
||||
width calc(100% - 74px)
|
||||
> .body
|
||||
padding 4px 32px 32px 32px
|
||||
|
||||
> header
|
||||
margin-bottom 2px
|
||||
@media (max-width 400px)
|
||||
padding 4px 16px 16px 16px
|
||||
|
||||
> .name
|
||||
display inline
|
||||
margin 0
|
||||
padding 0
|
||||
color #777
|
||||
font-size 1em
|
||||
font-weight 700
|
||||
text-align left
|
||||
text-decoration none
|
||||
> .name
|
||||
font-size 20px
|
||||
font-weight bold
|
||||
|
||||
&:hover
|
||||
text-decoration underline
|
||||
> .username
|
||||
display block
|
||||
opacity 0.7
|
||||
|
||||
> .username
|
||||
text-align left
|
||||
margin 0 0 0 8px
|
||||
color #ccc
|
||||
> .description
|
||||
margin 16px 0
|
||||
|
||||
> .body
|
||||
> .followed
|
||||
display inline-block
|
||||
margin 0 0 4px 0
|
||||
padding 2px 8px
|
||||
vertical-align top
|
||||
font-size 10px
|
||||
color #71afc7
|
||||
background #eefaff
|
||||
border-radius 4px
|
||||
|
||||
> .description
|
||||
cursor default
|
||||
display block
|
||||
margin 0
|
||||
padding 0
|
||||
overflow-wrap break-word
|
||||
font-size 1.1em
|
||||
color #717171
|
||||
|
||||
> .mk-follow-button
|
||||
position absolute
|
||||
top 16px
|
||||
right 16px
|
||||
> .followed
|
||||
margin 0 0 16px 0
|
||||
padding 0
|
||||
line-height 24px
|
||||
font-size 0.8em
|
||||
color #71afc7
|
||||
background #eefaff
|
||||
border-radius 4px
|
||||
|
||||
</style>
|
||||
|
@ -33,7 +33,7 @@ export default Vue.extend({
|
||||
props: ['fetch', 'count', 'youKnowCount'],
|
||||
data() {
|
||||
return {
|
||||
limit: 30,
|
||||
limit: 20,
|
||||
mode: 'all',
|
||||
fetching: true,
|
||||
moreFetching: false,
|
||||
@ -73,10 +73,14 @@ export default Vue.extend({
|
||||
|
||||
.mk-users-list
|
||||
height 100%
|
||||
background #fff
|
||||
overflow auto
|
||||
background #eee
|
||||
|
||||
> nav
|
||||
z-index 1
|
||||
z-index 10
|
||||
position sticky
|
||||
top 0
|
||||
background #fff
|
||||
box-shadow 0 1px 0 rgba(#000, 0.1)
|
||||
|
||||
> div
|
||||
@ -114,16 +118,14 @@ export default Vue.extend({
|
||||
background #eee
|
||||
border-radius 20px
|
||||
|
||||
> .users
|
||||
height calc(100% - 54px)
|
||||
overflow auto
|
||||
> button
|
||||
display block
|
||||
width calc(100% - 32px)
|
||||
margin 16px
|
||||
padding 16px
|
||||
|
||||
> *
|
||||
border-bottom solid 1px rgba(#000, 0.05)
|
||||
|
||||
> *
|
||||
max-width 600px
|
||||
margin 0 auto
|
||||
&:hover
|
||||
background rgba(#000, 0.1)
|
||||
|
||||
> .no
|
||||
margin 0
|
||||
|
@ -106,7 +106,7 @@ export default Vue.extend({
|
||||
|
||||
mounted() {
|
||||
if (this.preventMount) {
|
||||
this.$destroy();
|
||||
this.destroyDom();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -190,7 +190,7 @@ export default Vue.extend({
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
this.$destroy();
|
||||
this.destroyDom();
|
||||
this.$emit('closed');
|
||||
}, 300);
|
||||
},
|
||||
|
@ -1,22 +1,34 @@
|
||||
<template>
|
||||
<div class="obdskegsannmntldydackcpzezagxqfy mk-admin-card">
|
||||
<header>%i18n:@dashboard%</header>
|
||||
|
||||
<div v-if="stats" class="stats">
|
||||
<div><b>%fa:user% {{ stats.originalUsersCount | number }}</b><span>%i18n:@original-users%</span></div>
|
||||
<div><span>%fa:user% {{ stats.usersCount | number }}</span><span>%i18n:@all-users%</span></div>
|
||||
<div><b>%fa:pencil-alt% {{ stats.originalNotesCount | number }}</b><span>%i18n:@original-notes%</span></div>
|
||||
<div><span>%fa:pencil-alt% {{ stats.notesCount | number }}</span><span>%i18n:@all-notes%</span></div>
|
||||
</div>
|
||||
|
||||
<div class="cpu-memory">
|
||||
<x-cpu-memory :connection="connection"/>
|
||||
</div>
|
||||
<div>
|
||||
<label>
|
||||
<input type="checkbox" v-model="disableRegistration" @change="updateMeta">
|
||||
<span>disableRegistration</span>
|
||||
</label>
|
||||
<button class="ui" @click="invite">%i18n:@invite%</button>
|
||||
<p v-if="inviteCode">Code: <code>{{ inviteCode }}</code></p>
|
||||
|
||||
<div class="form">
|
||||
<div>
|
||||
<label>
|
||||
<input type="checkbox" v-model="disableRegistration" @change="updateMeta">
|
||||
<span>%i18n:@disableRegistration%</span>
|
||||
</label>
|
||||
<button class="ui" @click="invite">%i18n:@invite%</button>
|
||||
<p v-if="inviteCode">Code: <code>{{ inviteCode }}</code></p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label>
|
||||
<input type="checkbox" v-model="disableLocalTimeline" @change="updateMeta">
|
||||
<span>%i18n:@disableLocalTimeline%</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -33,6 +45,7 @@ export default Vue.extend({
|
||||
return {
|
||||
stats: null,
|
||||
disableRegistration: false,
|
||||
disableLocalTimeline: false,
|
||||
inviteCode: null,
|
||||
connection: null,
|
||||
connectionId: null
|
||||
@ -44,6 +57,7 @@ export default Vue.extend({
|
||||
|
||||
(this as any).os.getMeta().then(meta => {
|
||||
this.disableRegistration = meta.disableRegistration;
|
||||
this.disableLocalTimeline = meta.disableLocalTimeline;
|
||||
});
|
||||
|
||||
(this as any).api('stats').then(stats => {
|
||||
@ -61,7 +75,8 @@ export default Vue.extend({
|
||||
},
|
||||
updateMeta() {
|
||||
(this as any).api('admin/update-meta', {
|
||||
disableRegistration: this.disableRegistration
|
||||
disableRegistration: this.disableRegistration,
|
||||
disableLocalTimeline: this.disableLocalTimeline
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -97,4 +112,8 @@ export default Vue.extend({
|
||||
border solid 1px #eee
|
||||
border-radius: 8px
|
||||
|
||||
> .form
|
||||
> div
|
||||
border-bottom solid 1px #eee
|
||||
|
||||
</style>
|
||||
|
41
src/client/app/desktop/views/pages/admin/admin.hashtags.vue
Normal file
41
src/client/app/desktop/views/pages/admin/admin.hashtags.vue
Normal file
@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<div class="jdnqwkzlnxcfftthoybjxrebyolvoucw mk-admin-card">
|
||||
<header>%i18n:@hided-tags%</header>
|
||||
<textarea v-model="hidedTags"></textarea>
|
||||
<button class="ui" @click="save">%i18n:@save%</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from "vue";
|
||||
|
||||
export default Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
hidedTags: '',
|
||||
};
|
||||
},
|
||||
created() {
|
||||
(this as any).os.getMeta().then(meta => {
|
||||
this.hidedTags = meta.hidedTags.join('\n');
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
save() {
|
||||
(this as any).api('admin/update-meta', {
|
||||
hidedTags: this.hidedTags.split('\n')
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
@import '~const.styl'
|
||||
|
||||
.jdnqwkzlnxcfftthoybjxrebyolvoucw
|
||||
textarea
|
||||
width 100%
|
||||
min-height 300px
|
||||
|
||||
</style>
|
@ -5,6 +5,8 @@
|
||||
<li @click="nav('dashboard')" :class="{ active: page == 'dashboard' }">%fa:chalkboard .fw%%i18n:@dashboard%</li>
|
||||
<li @click="nav('users')" :class="{ active: page == 'users' }">%fa:users .fw%%i18n:@users%</li>
|
||||
<li @click="nav('announcements')" :class="{ active: page == 'announcements' }">%fa:broadcast-tower .fw%%i18n:@announcements%</li>
|
||||
<li @click="nav('hashtags')" :class="{ active: page == 'hashtags' }">%fa:hashtag .fw%%i18n:@hashtags%</li>
|
||||
|
||||
<!-- <li @click="nav('drive')" :class="{ active: page == 'drive' }">%fa:cloud .fw%%i18n:@drive%</li> -->
|
||||
<!-- <li @click="nav('update')" :class="{ active: page == 'update' }">%i18n:@update%</li> -->
|
||||
</ul>
|
||||
@ -17,6 +19,9 @@
|
||||
<div v-show="page == 'announcements'">
|
||||
<x-announcements/>
|
||||
</div>
|
||||
<div v-show="page == 'hashtags'">
|
||||
<x-hashtags/>
|
||||
</div>
|
||||
<div v-if="page == 'users'">
|
||||
<x-suspend-user/>
|
||||
<x-unsuspend-user/>
|
||||
@ -33,6 +38,7 @@
|
||||
import Vue from "vue";
|
||||
import XDashboard from "./admin.dashboard.vue";
|
||||
import XAnnouncements from "./admin.announcements.vue";
|
||||
import XHashtags from "./admin.hashtags.vue";
|
||||
import XSuspendUser from "./admin.suspend-user.vue";
|
||||
import XUnsuspendUser from "./admin.unsuspend-user.vue";
|
||||
import XVerifyUser from "./admin.verify-user.vue";
|
||||
@ -43,6 +49,7 @@ export default Vue.extend({
|
||||
components: {
|
||||
XDashboard,
|
||||
XAnnouncements,
|
||||
XHashtags,
|
||||
XSuspendUser,
|
||||
XUnsuspendUser,
|
||||
XVerifyUser,
|
||||
|
@ -28,6 +28,7 @@
|
||||
import Vue from 'vue';
|
||||
import Menu from '../../../../common/views/components/menu.vue';
|
||||
import contextmenu from '../../../api/contextmenu';
|
||||
import { countIf } from '../../../../../../prelude/array';
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
@ -117,7 +118,7 @@ export default Vue.extend({
|
||||
toggleActive() {
|
||||
if (!this.isStacked) return;
|
||||
const vms = this.$store.state.settings.deck.layout.find(ids => ids.indexOf(this.column.id) != -1).map(id => this.getColumnVm(id));
|
||||
if (this.active && vms.filter(vm => vm.$el.classList.contains('active')).length == 1) return;
|
||||
if (this.active && countIf(vm => vm.$el.classList.contains('active'), vms) == 1) return;
|
||||
this.active = !this.active;
|
||||
},
|
||||
|
||||
|
@ -68,7 +68,7 @@ export default Vue.extend({
|
||||
(this as any).api('notes/user-list-timeline', {
|
||||
listId: this.list.id,
|
||||
limit: fetchLimit + 1,
|
||||
mediaOnly: this.mediaOnly,
|
||||
withFiles: this.mediaOnly,
|
||||
includeMyRenotes: this.$store.state.settings.showMyRenotes,
|
||||
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
|
||||
includeLocalRenotes: this.$store.state.settings.showLocalRenotes
|
||||
@ -90,7 +90,7 @@ export default Vue.extend({
|
||||
listId: this.list.id,
|
||||
limit: fetchLimit + 1,
|
||||
untilId: (this.$refs.timeline as any).tail().id,
|
||||
mediaOnly: this.mediaOnly,
|
||||
withFiles: this.mediaOnly,
|
||||
includeMyRenotes: this.$store.state.settings.showMyRenotes,
|
||||
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
|
||||
includeLocalRenotes: this.$store.state.settings.showLocalRenotes
|
||||
@ -109,7 +109,7 @@ export default Vue.extend({
|
||||
return promise;
|
||||
},
|
||||
onNote(note) {
|
||||
if (this.mediaOnly && note.media.length == 0) return;
|
||||
if (this.mediaOnly && note.files.length == 0) return;
|
||||
|
||||
// Prepend a note
|
||||
(this.$refs.timeline as any).prepend(note);
|
||||
|
@ -18,7 +18,7 @@
|
||||
<div class="body">
|
||||
<p v-if="p.cw != null" class="cw">
|
||||
<span class="text" v-if="p.cw != ''">{{ p.cw }}</span>
|
||||
<span class="toggle" @click="showContent = !showContent">{{ showContent ? '%i18n:@less%' : '%i18n:@more%' }}</span>
|
||||
<mk-cw-button v-model="showContent"/>
|
||||
</p>
|
||||
<div class="content" v-show="p.cw == null || showContent">
|
||||
<div class="text">
|
||||
@ -28,8 +28,8 @@
|
||||
<misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i"/>
|
||||
<a class="rp" v-if="p.renote != null">RP:</a>
|
||||
</div>
|
||||
<div class="media" v-if="p.media.length > 0">
|
||||
<mk-media-list :media-list="p.media"/>
|
||||
<div class="files" v-if="p.files.length > 0">
|
||||
<mk-media-list :media-list="p.files"/>
|
||||
</div>
|
||||
<mk-poll v-if="p.poll" :note="p" ref="pollViewer"/>
|
||||
<a class="location" v-if="p.geo" :href="`https://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% %i18n:@location%</a>
|
||||
@ -54,11 +54,11 @@
|
||||
</article>
|
||||
</div>
|
||||
<div v-else class="srwrkujossgfuhrbnvqkybtzxpblgchi">
|
||||
<div v-if="note.media.length > 0">
|
||||
<mk-media-list :media-list="note.media"/>
|
||||
<div v-if="note.files.length > 0">
|
||||
<mk-media-list :media-list="note.files"/>
|
||||
</div>
|
||||
<div v-if="note.renote && note.renote.media.length > 0">
|
||||
<mk-media-list :media-list="note.renote.media"/>
|
||||
<div v-if="note.renote && note.renote.files.length > 0">
|
||||
<mk-media-list :media-list="note.renote.files"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -100,7 +100,7 @@ export default Vue.extend({
|
||||
isRenote(): boolean {
|
||||
return (this.note.renote &&
|
||||
this.note.text == null &&
|
||||
this.note.mediaIds.length == 0 &&
|
||||
this.note.fileIds.length == 0 &&
|
||||
this.note.poll == null);
|
||||
},
|
||||
|
||||
@ -371,7 +371,7 @@ root(isDark)
|
||||
.mk-url-preview
|
||||
margin-top 8px
|
||||
|
||||
> .media
|
||||
> .files
|
||||
> img
|
||||
display block
|
||||
max-width 100%
|
||||
@ -394,7 +394,7 @@ root(isDark)
|
||||
> .renote
|
||||
margin 8px 0
|
||||
|
||||
> .mk-note-preview
|
||||
> *
|
||||
padding 16px
|
||||
border dashed 1px isDark ? #4e945e : #c0dac6
|
||||
border-radius 8px
|
||||
|
@ -127,7 +127,7 @@ export default Vue.extend({
|
||||
prepend(note, silent = false) {
|
||||
//#region 弾く
|
||||
const isMyNote = note.userId == this.$store.state.i.id;
|
||||
const isPureRenote = note.renoteId != null && note.text == null && note.mediaIds.length == 0 && note.poll == null;
|
||||
const isPureRenote = note.renoteId != null && note.text == null && note.fileIds.length == 0 && note.poll == null;
|
||||
|
||||
if (this.$store.state.settings.showMyRenotes === false) {
|
||||
if (isMyNote && isPureRenote) {
|
||||
|
@ -96,7 +96,7 @@ export default Vue.extend({
|
||||
(this.$refs.timeline as any).init(() => new Promise((res, rej) => {
|
||||
(this as any).api(this.endpoint, {
|
||||
limit: fetchLimit + 1,
|
||||
mediaOnly: this.mediaOnly,
|
||||
withFiles: this.mediaOnly,
|
||||
includeMyRenotes: this.$store.state.settings.showMyRenotes,
|
||||
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
|
||||
includeLocalRenotes: this.$store.state.settings.showLocalRenotes
|
||||
@ -117,7 +117,7 @@ export default Vue.extend({
|
||||
|
||||
const promise = (this as any).api(this.endpoint, {
|
||||
limit: fetchLimit + 1,
|
||||
mediaOnly: this.mediaOnly,
|
||||
withFiles: this.mediaOnly,
|
||||
untilId: (this.$refs.timeline as any).tail().id,
|
||||
includeMyRenotes: this.$store.state.settings.showMyRenotes,
|
||||
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
|
||||
@ -138,7 +138,7 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
onNote(note) {
|
||||
if (this.mediaOnly && note.media.length == 0) return;
|
||||
if (this.mediaOnly && note.files.length == 0) return;
|
||||
|
||||
// Prepend a note
|
||||
(this.$refs.timeline as any).prepend(note);
|
||||
|
@ -85,6 +85,7 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
mounted() {
|
||||
document.title = (this as any).os.instanceName;
|
||||
document.documentElement.style.overflow = 'hidden';
|
||||
},
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
<div class="title">
|
||||
<p class="name">{{ user | userName }}</p>
|
||||
<div>
|
||||
<span class="username"><mk-acct :user="user"/></span>
|
||||
<span class="username"><mk-acct :user="user" :detail="true" /></span>
|
||||
<span v-if="user.isBot" title="%i18n:@is-bot%">%fa:robot%</span>
|
||||
<span class="location" v-if="user.host === null && user.profile.location">%fa:map-marker% {{ user.profile.location }}</span>
|
||||
<span class="birthday" v-if="user.host === null && user.profile.birthday">%fa:birthday-cake% {{ user.profile.birthday.replace('-', '年').replace('-', '月') + '日' }} ({{ age }}歳)</span>
|
||||
|
@ -24,12 +24,12 @@ export default Vue.extend({
|
||||
mounted() {
|
||||
(this as any).api('users/notes', {
|
||||
userId: this.user.id,
|
||||
withMedia: true,
|
||||
withFiles: true,
|
||||
limit: 9
|
||||
}).then(notes => {
|
||||
notes.forEach(note => {
|
||||
note.media.forEach(media => {
|
||||
if (this.images.length < 9) this.images.push(media);
|
||||
note.files.forEach(file => {
|
||||
if (this.images.length < 9) this.images.push(file);
|
||||
});
|
||||
});
|
||||
this.fetching = false;
|
||||
|
@ -66,7 +66,7 @@ export default Vue.extend({
|
||||
limit: fetchLimit + 1,
|
||||
untilDate: this.date ? this.date.getTime() : undefined,
|
||||
includeReplies: this.mode == 'with-replies',
|
||||
withMedia: this.mode == 'with-media'
|
||||
withFiles: this.mode == 'with-media'
|
||||
}).then(notes => {
|
||||
if (notes.length == fetchLimit + 1) {
|
||||
notes.pop();
|
||||
@ -86,7 +86,7 @@ export default Vue.extend({
|
||||
userId: this.user.id,
|
||||
limit: fetchLimit + 1,
|
||||
includeReplies: this.mode == 'with-replies',
|
||||
withMedia: this.mode == 'with-media',
|
||||
withFiles: this.mode == 'with-media',
|
||||
untilId: (this.$refs.timeline as any).tail().id
|
||||
});
|
||||
|
||||
|
@ -7,45 +7,130 @@
|
||||
|
||||
<mk-forkit class="forkit"/>
|
||||
|
||||
<div class="body">
|
||||
<div class="main block">
|
||||
<h1 v-if="name != 'Misskey'">{{ name }}</h1>
|
||||
<h1 v-else><img :src="$store.state.device.darkmode ? 'assets/title.dark.svg' : 'assets/title.light.svg'" :alt="name"></h1>
|
||||
<main>
|
||||
<div class="body">
|
||||
<div class="main block">
|
||||
<div>
|
||||
<h1 v-if="name != 'Misskey'">{{ name }}</h1>
|
||||
<h1 v-else><img :src="$store.state.device.darkmode ? 'assets/title.dark.svg' : 'assets/title.light.svg'" :alt="name"></h1>
|
||||
|
||||
<div class="info">
|
||||
<span><b>{{ host }}</b> - <span v-html="'%i18n:@powered-by-misskey%'"></span></span>
|
||||
<span class="stats" v-if="stats">
|
||||
<span>%fa:user% {{ stats.originalUsersCount | number }}</span>
|
||||
<span>%fa:pencil-alt% {{ stats.originalNotesCount | number }}</span>
|
||||
</span>
|
||||
<div class="info">
|
||||
<span><b>{{ host }}</b> - <span v-html="'%i18n:@powered-by-misskey%'"></span></span>
|
||||
<span class="stats" v-if="stats">
|
||||
<span>%fa:user% {{ stats.originalUsersCount | number }}</span>
|
||||
<span>%fa:pencil-alt% {{ stats.originalNotesCount | number }}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="desc">
|
||||
<span class="desc" v-html="description || '%i18n:common.about%'"></span>
|
||||
<a class="about" @click="about">%i18n:@about%</a>
|
||||
</div>
|
||||
|
||||
<p class="sign">
|
||||
<span class="signup" @click="signup">%i18n:@signup%</span>
|
||||
<span class="divider">|</span>
|
||||
<span class="signin" @click="signin">%i18n:@signin%</span>
|
||||
</p>
|
||||
|
||||
<img src="/assets/ai.png" alt="" title="藍" class="char">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="desc" v-html="description || '%i18n:common.about%'"></p>
|
||||
<div class="announcements block">
|
||||
<header>%fa:broadcast-tower% %i18n:@announcements%</header>
|
||||
<div v-if="announcements && announcements.length > 0">
|
||||
<div v-for="announcement in announcements">
|
||||
<h1 v-html="announcement.title"></h1>
|
||||
<div v-html="announcement.text"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="sign">
|
||||
<span class="signup" @click="signup">%i18n:@signup%</span>
|
||||
<span class="divider">|</span>
|
||||
<span class="signin" @click="signin">%i18n:@signin%</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="photos block">
|
||||
<header>%fa:images% %i18n:@photos%</header>
|
||||
<div>
|
||||
<div v-for="photo in photos" :style="`background-image: url(${photo.thumbnailUrl})`"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="broadcasts block">
|
||||
<div v-for="broadcast in broadcasts">
|
||||
<h1 v-html="broadcast.title"></h1>
|
||||
<div v-html="broadcast.text"></div>
|
||||
<div class="tag-cloud block">
|
||||
<div>
|
||||
<mk-tag-cloud/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="nav block">
|
||||
<div>
|
||||
<mk-nav class="nav"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="side">
|
||||
<div class="trends block">
|
||||
<div>
|
||||
<mk-trends/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tl block">
|
||||
<header>%fa:comment-alt R% %i18n:@timeline%</header>
|
||||
<div>
|
||||
<mk-welcome-timeline class="tl" :max="20"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info block">
|
||||
<header>%fa:info-circle% %i18n:@info%</header>
|
||||
<div>
|
||||
<div v-if="meta" class="body">
|
||||
<p>Version: <b>{{ meta.version }}</b></p>
|
||||
<p>Maintainer: <b><a :href="meta.maintainer.url" target="_blank">{{ meta.maintainer.name }}</a></b></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div class="nav block">
|
||||
<mk-nav class="nav"/>
|
||||
</div>
|
||||
|
||||
<div class="side">
|
||||
<mk-trends class="trends block"/>
|
||||
|
||||
<mk-welcome-timeline class="tl block" :max="20"/>
|
||||
</div>
|
||||
</div>
|
||||
<modal name="about" :class="$store.state.device.darkmode ? ['about', 'modal-dark'] : ['about', 'modal-light']" width="800px" height="auto" scrollable>
|
||||
<article class="fpdezooorhntlzyeszemrsqdlgbysvxq">
|
||||
<h1>%i18n:common.intro.title%</h1>
|
||||
<p v-html="'%i18n:common.intro.about%'"></p>
|
||||
<section>
|
||||
<h2>%i18n:common.intro.features%</h2>
|
||||
<section>
|
||||
<div class="body">
|
||||
<h3>%i18n:common.intro.rich-contents%</h3>
|
||||
<p v-html="'%i18n:common.intro.rich-contents-desc%'"></p>
|
||||
</div>
|
||||
<div class="image"><img src="/assets/about/post.png" alt=""></div>
|
||||
</section>
|
||||
<section>
|
||||
<div class="body">
|
||||
<h3>%i18n:common.intro.reaction%</h3>
|
||||
<p v-html="'%i18n:common.intro.reaction-desc%'"></p>
|
||||
</div>
|
||||
<div class="image"><img src="/assets/about/reaction.png" alt=""></div>
|
||||
</section>
|
||||
<section>
|
||||
<div class="body">
|
||||
<h3>%i18n:common.intro.ui%</h3>
|
||||
<p v-html="'%i18n:common.intro.ui-desc%'"></p>
|
||||
</div>
|
||||
<div class="image"><img src="/assets/about/ui.png" alt=""></div>
|
||||
</section>
|
||||
<section>
|
||||
<div class="body">
|
||||
<h3>%i18n:common.intro.drive%</h3>
|
||||
<p v-html="'%i18n:common.intro.drive-desc%'"></p>
|
||||
</div>
|
||||
<div class="image"><img src="/assets/about/drive.png" alt=""></div>
|
||||
</section>
|
||||
</section>
|
||||
<p v-html="'%i18n:common.intro.outro%'"></p>
|
||||
</article>
|
||||
</modal>
|
||||
|
||||
<modal name="signup" :class="$store.state.device.darkmode ? 'modal-dark' : 'modal-light'" width="450px" height="auto" scrollable>
|
||||
<header class="formHeader">%i18n:@signup%</header>
|
||||
@ -62,37 +147,62 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { host, copyright } from '../../../config';
|
||||
import { concat } from '../../../../../prelude/array';
|
||||
|
||||
export default Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
meta: null,
|
||||
stats: null,
|
||||
copyright,
|
||||
host,
|
||||
name: 'Misskey',
|
||||
description: '',
|
||||
broadcasts: []
|
||||
announcements: [],
|
||||
photos: []
|
||||
};
|
||||
},
|
||||
|
||||
created() {
|
||||
(this as any).os.getMeta().then(meta => {
|
||||
this.meta = meta;
|
||||
this.name = meta.name;
|
||||
this.description = meta.description;
|
||||
this.broadcasts = meta.broadcasts;
|
||||
this.announcements = meta.broadcasts;
|
||||
});
|
||||
|
||||
(this as any).api('stats').then(stats => {
|
||||
this.stats = stats;
|
||||
});
|
||||
|
||||
const image = [
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/gif'
|
||||
];
|
||||
|
||||
(this as any).api('notes/local-timeline', {
|
||||
fileType: image,
|
||||
limit: 6
|
||||
}).then((notes: any[]) => {
|
||||
const files = concat(notes.map((n: any): any[] => n.files));
|
||||
this.photos = files.filter(f => image.includes(f.type)).slice(0, 6);
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
about() {
|
||||
this.$modal.show('about');
|
||||
},
|
||||
|
||||
signup() {
|
||||
this.$modal.show('signup');
|
||||
},
|
||||
|
||||
signin() {
|
||||
this.$modal.show('signin');
|
||||
},
|
||||
|
||||
dark() {
|
||||
this.$store.commit('device/set', {
|
||||
key: 'darkmode',
|
||||
@ -137,6 +247,54 @@ export default Vue.extend({
|
||||
margin 0 48px
|
||||
font-size 1.5em
|
||||
|
||||
.v--modal-overlay.about
|
||||
.v--modal-box.v--modal
|
||||
margin 32px 0
|
||||
|
||||
.fpdezooorhntlzyeszemrsqdlgbysvxq
|
||||
padding 64px
|
||||
|
||||
> p:last-child
|
||||
margin-bottom 0
|
||||
|
||||
> h1
|
||||
margin-top 0
|
||||
|
||||
> section
|
||||
> h2
|
||||
border-bottom 1px solid isDark ? rgba(#000, 0.2) : rgba(#000, 0.05)
|
||||
|
||||
> section
|
||||
display grid
|
||||
grid-template-rows 1fr
|
||||
grid-template-columns 180px 1fr
|
||||
gap 32px
|
||||
margin-bottom 32px
|
||||
padding-bottom 32px
|
||||
border-bottom 1px solid isDark ? rgba(#000, 0.2) : rgba(#000, 0.05)
|
||||
|
||||
&:nth-child(odd)
|
||||
grid-template-columns 1fr 180px
|
||||
|
||||
> .body
|
||||
grid-column 1
|
||||
|
||||
> .image
|
||||
grid-column 2
|
||||
|
||||
> .body
|
||||
grid-row 1
|
||||
grid-column 2
|
||||
|
||||
> .image
|
||||
grid-row 1
|
||||
grid-column 1
|
||||
|
||||
> img
|
||||
display block
|
||||
width 100%
|
||||
height 100%
|
||||
object-fit cover
|
||||
</style>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
@ -164,116 +322,176 @@ root(isDark)
|
||||
font-size 18px
|
||||
color isDark ? #fff : #444
|
||||
|
||||
> .body
|
||||
display grid
|
||||
grid-template-rows 0.5fr 0.5fr 64px
|
||||
grid-template-columns 1fr 350px
|
||||
gap 16px
|
||||
width 100%
|
||||
max-width 1200px
|
||||
height 100vh
|
||||
min-height 800px
|
||||
> main
|
||||
margin 0 auto
|
||||
padding 64px
|
||||
width 100%
|
||||
max-width 1200px
|
||||
|
||||
.block
|
||||
color isDark ? #fff : #444
|
||||
background isDark ? #313543 : #fff
|
||||
background isDark ? #282C37 : #fff
|
||||
box-shadow 0 3px 8px rgba(0, 0, 0, 0.2)
|
||||
//border-radius 8px
|
||||
overflow auto
|
||||
|
||||
> .main
|
||||
grid-row 1
|
||||
grid-column 1
|
||||
padding 32px
|
||||
border-top solid 5px $theme-color
|
||||
> header
|
||||
z-index 1
|
||||
padding 0 16px
|
||||
line-height 48px
|
||||
background isDark ? #313543 : #fff
|
||||
|
||||
> h1
|
||||
margin 0
|
||||
if !isDark
|
||||
box-shadow 0 1px 0px rgba(0, 0, 0, 0.1)
|
||||
|
||||
> img
|
||||
margin -8px 0 0 -16px
|
||||
max-width 280px
|
||||
|
||||
> .info
|
||||
margin 0 auto 16px auto
|
||||
width $width
|
||||
font-size 14px
|
||||
|
||||
> .stats
|
||||
margin-left 16px
|
||||
padding-left 16px
|
||||
border-left solid 1px isDark ? #fff : #444
|
||||
|
||||
> *
|
||||
margin-right 16px
|
||||
|
||||
> .sign
|
||||
font-size 120%
|
||||
|
||||
> .divider
|
||||
margin 0 16px
|
||||
|
||||
> .signin
|
||||
> .signup
|
||||
cursor pointer
|
||||
|
||||
&:hover
|
||||
color $theme-color
|
||||
|
||||
> .hashtags
|
||||
margin 16px auto
|
||||
width $width
|
||||
font-size 14px
|
||||
background rgba(#000, 0.3)
|
||||
border-radius 8px
|
||||
|
||||
> *
|
||||
display inline-block
|
||||
margin 14px
|
||||
|
||||
> .broadcasts
|
||||
grid-row 2
|
||||
grid-column 1
|
||||
padding 32px
|
||||
& + div
|
||||
max-height calc(100% - 48px)
|
||||
|
||||
> div
|
||||
padding 0 0 16px 0
|
||||
margin 0 0 16px 0
|
||||
border-bottom 1px solid isDark ? rgba(#000, 0.2) : rgba(#000, 0.05)
|
||||
|
||||
> h1
|
||||
margin 0
|
||||
font-size 1.5em
|
||||
|
||||
> .nav
|
||||
display flex
|
||||
justify-content center
|
||||
align-items center
|
||||
grid-row 3
|
||||
grid-column 1
|
||||
font-size 14px
|
||||
|
||||
> .side
|
||||
display grid
|
||||
grid-row 1 / 4
|
||||
grid-column 2
|
||||
grid-template-rows 1fr 350px
|
||||
grid-template-columns 1fr
|
||||
gap 16px
|
||||
|
||||
> .tl
|
||||
grid-row 1
|
||||
grid-column 1
|
||||
text-align left
|
||||
max-height 100%
|
||||
overflow auto
|
||||
|
||||
> .trends
|
||||
> .body
|
||||
display grid
|
||||
grid-template-rows 390px 1fr 256px 64px
|
||||
grid-template-columns 1fr 1fr 350px
|
||||
gap 16px
|
||||
height 1150px
|
||||
|
||||
> .main
|
||||
grid-row 1
|
||||
grid-column 1 / 3
|
||||
border-top solid 5px $theme-color
|
||||
|
||||
> div
|
||||
padding 32px
|
||||
min-height 100%
|
||||
|
||||
> h1
|
||||
margin 0
|
||||
|
||||
> img
|
||||
margin -8px 0 0 -16px
|
||||
max-width 280px
|
||||
|
||||
> .info
|
||||
margin 0 auto 16px auto
|
||||
width $width
|
||||
font-size 14px
|
||||
|
||||
> .stats
|
||||
margin-left 16px
|
||||
padding-left 16px
|
||||
border-left solid 1px isDark ? #fff : #444
|
||||
|
||||
> *
|
||||
margin-right 16px
|
||||
|
||||
> .desc
|
||||
max-width calc(100% - 150px)
|
||||
|
||||
> .sign
|
||||
font-size 120%
|
||||
margin-bottom 0
|
||||
|
||||
> .divider
|
||||
margin 0 16px
|
||||
|
||||
> .signin
|
||||
> .signup
|
||||
cursor pointer
|
||||
|
||||
&:hover
|
||||
color $theme-color
|
||||
|
||||
> .char
|
||||
display block
|
||||
position absolute
|
||||
right 16px
|
||||
bottom 0
|
||||
height 320px
|
||||
opacity 0.7
|
||||
|
||||
> *:not(.char)
|
||||
z-index 1
|
||||
|
||||
> .announcements
|
||||
grid-row 2
|
||||
grid-column 1
|
||||
padding 8px
|
||||
|
||||
> div
|
||||
padding 32px
|
||||
|
||||
> div
|
||||
padding 0 0 16px 0
|
||||
margin 0 0 16px 0
|
||||
border-bottom 1px solid isDark ? rgba(#000, 0.2) : rgba(#000, 0.05)
|
||||
|
||||
> h1
|
||||
margin 0
|
||||
font-size 1.25em
|
||||
|
||||
> .photos
|
||||
grid-row 2
|
||||
grid-column 2
|
||||
|
||||
> div
|
||||
display grid
|
||||
grid-template-rows 1fr 1fr 1fr
|
||||
grid-template-columns 1fr 1fr
|
||||
gap 8px
|
||||
height 100%
|
||||
padding 16px
|
||||
|
||||
> div
|
||||
//border-radius 4px
|
||||
background-position center center
|
||||
background-size cover
|
||||
|
||||
> .tag-cloud
|
||||
grid-row 3
|
||||
grid-column 1 / 3
|
||||
|
||||
> div
|
||||
height 256px
|
||||
padding 32px
|
||||
|
||||
> .nav
|
||||
display flex
|
||||
justify-content center
|
||||
align-items center
|
||||
grid-row 4
|
||||
grid-column 1 / 3
|
||||
font-size 14px
|
||||
|
||||
> .side
|
||||
display grid
|
||||
grid-row 1 / 5
|
||||
grid-column 3
|
||||
grid-template-rows 1fr 350px
|
||||
grid-template-columns 1fr
|
||||
gap 16px
|
||||
|
||||
> .tl
|
||||
grid-row 1
|
||||
grid-column 1
|
||||
overflow auto
|
||||
|
||||
> .trends
|
||||
grid-row 2
|
||||
grid-column 1
|
||||
padding 8px
|
||||
|
||||
> .info
|
||||
grid-row 3
|
||||
grid-column 1
|
||||
|
||||
> div
|
||||
padding 16px
|
||||
|
||||
> .body
|
||||
> p
|
||||
display block
|
||||
margin 0
|
||||
|
||||
.mk-welcome[data-darkmode]
|
||||
root(true)
|
||||
|
@ -49,7 +49,7 @@ export default define({
|
||||
offset: this.offset,
|
||||
renote: false,
|
||||
reply: false,
|
||||
media: false,
|
||||
file: false,
|
||||
poll: false
|
||||
}).then(notes => {
|
||||
const note = notes ? notes[0] : null;
|
||||
|
@ -7,9 +7,6 @@ import Vuex from 'vuex';
|
||||
import VueRouter from 'vue-router';
|
||||
import * as TreeView from 'vue-json-tree-view';
|
||||
import VAnimateCss from 'v-animate-css';
|
||||
import Element from 'element-ui';
|
||||
import ElementLocaleEn from 'element-ui/lib/locale/lang/en';
|
||||
import ElementLocaleJa from 'element-ui/lib/locale/lang/ja';
|
||||
import VModal from 'vue-js-modal';
|
||||
|
||||
import App from './app.vue';
|
||||
@ -17,18 +14,10 @@ import checkForUpdate from './common/scripts/check-for-update';
|
||||
import MiOS, { API } from './mios';
|
||||
import { version, codename, lang } from './config';
|
||||
|
||||
let elementLocale;
|
||||
switch (lang) {
|
||||
case 'ja-JP': elementLocale = ElementLocaleJa; break;
|
||||
case 'en-US': elementLocale = ElementLocaleEn; break;
|
||||
default: elementLocale = ElementLocaleEn; break;
|
||||
}
|
||||
|
||||
Vue.use(Vuex);
|
||||
Vue.use(VueRouter);
|
||||
Vue.use(TreeView);
|
||||
Vue.use(VAnimateCss);
|
||||
Vue.use(Element, { locale: elementLocale });
|
||||
Vue.use(VModal);
|
||||
|
||||
// Register global directives
|
||||
@ -42,9 +31,13 @@ require('./common/views/widgets');
|
||||
require('./common/views/filters');
|
||||
|
||||
Vue.mixin({
|
||||
destroyed(this: any) {
|
||||
if (this.$el.parentNode) {
|
||||
this.$el.parentNode.removeChild(this.$el);
|
||||
methods: {
|
||||
destroyDom() {
|
||||
this.$destroy();
|
||||
|
||||
if (this.$el.parentNode) {
|
||||
this.$el.parentNode.removeChild(this.$el);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -17,6 +17,7 @@ import Err from './common/views/components/connect-failed.vue';
|
||||
import { LocalTimelineStreamManager } from './common/scripts/streaming/local-timeline';
|
||||
import { HybridTimelineStreamManager } from './common/scripts/streaming/hybrid-timeline';
|
||||
import { GlobalTimelineStreamManager } from './common/scripts/streaming/global-timeline';
|
||||
import { erase } from '../../prelude/array';
|
||||
|
||||
//#region api requests
|
||||
let spinner = null;
|
||||
@ -537,7 +538,7 @@ export default class MiOS extends EventEmitter {
|
||||
}
|
||||
|
||||
public unregisterStreamConnection(connection: Connection) {
|
||||
this.connections = this.connections.filter(c => c != connection);
|
||||
this.connections = erase(connection, this.connections);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,6 @@ import VueRouter from 'vue-router';
|
||||
|
||||
// Style
|
||||
import './style.styl';
|
||||
import '../../element.scss';
|
||||
|
||||
import init from '../init';
|
||||
|
||||
|
@ -78,7 +78,7 @@ export default Vue.extend({
|
||||
scale: 0.8,
|
||||
duration: 300,
|
||||
easing: [ 0.5, -0.5, 1, 0.5 ],
|
||||
complete: () => this.$destroy()
|
||||
complete: () => this.destroyDom()
|
||||
});
|
||||
},
|
||||
onBgClick() {
|
||||
|
@ -31,15 +31,15 @@ export default Vue.extend({
|
||||
},
|
||||
onSelected(file) {
|
||||
this.$emit('selected', file);
|
||||
this.$destroy();
|
||||
this.destroyDom();
|
||||
},
|
||||
cancel() {
|
||||
this.$emit('canceled');
|
||||
this.$destroy();
|
||||
this.destroyDom();
|
||||
},
|
||||
ok() {
|
||||
this.$emit('selected', this.files);
|
||||
this.$destroy();
|
||||
this.destroyDom();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -19,11 +19,11 @@ export default Vue.extend({
|
||||
methods: {
|
||||
cancel() {
|
||||
this.$emit('canceled');
|
||||
this.$destroy();
|
||||
this.destroyDom();
|
||||
},
|
||||
ok() {
|
||||
this.$emit('selected', (this.$refs.browser as any).folder);
|
||||
this.$destroy();
|
||||
this.destroyDom();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -67,7 +67,7 @@
|
||||
import Vue from 'vue';
|
||||
import * as EXIF from 'exif-js';
|
||||
import * as hljs from 'highlight.js';
|
||||
import gcd from '../../../common/scripts/gcd';
|
||||
import { gcd } from '../../../../../prelude/math';
|
||||
|
||||
export default Vue.extend({
|
||||
props: ['file'],
|
||||
|
@ -47,7 +47,7 @@ export default Vue.extend({
|
||||
this.fetch();
|
||||
},
|
||||
close() {
|
||||
this.$destroy();
|
||||
this.destroyDom();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="qjewsnkgzzxlxtzncydssfbgjibiehcy" v-if="image.isSensitive && hide" @click="hide = false">
|
||||
<div class="qjewsnkgzzxlxtzncydssfbgjibiehcy" v-if="image.isSensitive && hide && !$store.state.device.alwaysShowNsfw" @click="hide = false">
|
||||
<div>
|
||||
<b>%fa:exclamation-triangle% %i18n:@sensitive%</b>
|
||||
<span>%i18n:@click-to-show%</span>
|
||||
|
@ -35,20 +35,26 @@
|
||||
</div>
|
||||
</header>
|
||||
<div class="body">
|
||||
<div class="text">
|
||||
<span v-if="p.isHidden" style="opacity: 0.5">(%i18n:@private%)</span>
|
||||
<span v-if="p.deletedAt" style="opacity: 0.5">(%i18n:@deleted%)</span>
|
||||
<misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i"/>
|
||||
</div>
|
||||
<div class="media" v-if="p.media.length > 0">
|
||||
<mk-media-list :media-list="p.media" :raw="true"/>
|
||||
</div>
|
||||
<mk-poll v-if="p.poll" :note="p"/>
|
||||
<mk-url-preview v-for="url in urls" :url="url" :key="url" :detail="true"/>
|
||||
<a class="location" v-if="p.geo" :href="`https://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% %i18n:@location%</a>
|
||||
<div class="map" v-if="p.geo" ref="map"></div>
|
||||
<div class="renote" v-if="p.renote">
|
||||
<mk-note-preview :note="p.renote"/>
|
||||
<p v-if="p.cw != null" class="cw">
|
||||
<span class="text" v-if="p.cw != ''">{{ p.cw }}</span>
|
||||
<mk-cw-button v-model="showContent"/>
|
||||
</p>
|
||||
<div class="content" v-show="p.cw == null || showContent">
|
||||
<div class="text">
|
||||
<span v-if="p.isHidden" style="opacity: 0.5">(%i18n:@private%)</span>
|
||||
<span v-if="p.deletedAt" style="opacity: 0.5">(%i18n:@deleted%)</span>
|
||||
<misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i"/>
|
||||
</div>
|
||||
<div class="files" v-if="p.files.length > 0">
|
||||
<mk-media-list :media-list="p.files" :raw="true"/>
|
||||
</div>
|
||||
<mk-poll v-if="p.poll" :note="p"/>
|
||||
<mk-url-preview v-for="url in urls" :url="url" :key="url" :detail="true"/>
|
||||
<a class="location" v-if="p.geo" :href="`https://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% %i18n:@location%</a>
|
||||
<div class="map" v-if="p.geo" ref="map"></div>
|
||||
<div class="renote" v-if="p.renote">
|
||||
<mk-note-preview :note="p.renote"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<router-link class="time" :to="p | notePage">
|
||||
@ -85,6 +91,7 @@ import parse from '../../../../../mfm/parse';
|
||||
import MkNoteMenu from '../../../common/views/components/note-menu.vue';
|
||||
import MkReactionPicker from '../../../common/views/components/reaction-picker.vue';
|
||||
import XSub from './note.sub.vue';
|
||||
import { sum } from '../../../../../prelude/array';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
@ -103,6 +110,7 @@ export default Vue.extend({
|
||||
|
||||
data() {
|
||||
return {
|
||||
showContent: false,
|
||||
conversation: [],
|
||||
conversationFetching: false,
|
||||
replies: []
|
||||
@ -113,7 +121,7 @@ export default Vue.extend({
|
||||
isRenote(): boolean {
|
||||
return (this.note.renote &&
|
||||
this.note.text == null &&
|
||||
this.note.mediaIds.length == 0 &&
|
||||
this.note.fileIds.length == 0 &&
|
||||
this.note.poll == null);
|
||||
},
|
||||
|
||||
@ -123,9 +131,7 @@ export default Vue.extend({
|
||||
|
||||
reactionsCount(): number {
|
||||
return this.p.reactionCounts
|
||||
? Object.keys(this.p.reactionCounts)
|
||||
.map(key => this.p.reactionCounts[key])
|
||||
.reduce((a, b) => a + b)
|
||||
? sum(Object.values(this.p.reactionCounts))
|
||||
: 0;
|
||||
},
|
||||
|
||||
@ -335,44 +341,57 @@ root(isDark)
|
||||
> .body
|
||||
padding 8px 0
|
||||
|
||||
> .text
|
||||
> .cw
|
||||
cursor default
|
||||
display block
|
||||
margin 0
|
||||
padding 0
|
||||
overflow-wrap break-word
|
||||
font-size 16px
|
||||
color isDark ? #fff : #717171
|
||||
|
||||
@media (min-width 500px)
|
||||
font-size 24px
|
||||
> .text
|
||||
margin-right 8px
|
||||
|
||||
> .renote
|
||||
margin 8px 0
|
||||
> .content
|
||||
|
||||
> .mk-note-preview
|
||||
padding 16px
|
||||
border dashed 1px #c0dac6
|
||||
border-radius 8px
|
||||
|
||||
> .location
|
||||
margin 4px 0
|
||||
font-size 12px
|
||||
color #ccc
|
||||
|
||||
> .map
|
||||
width 100%
|
||||
height 200px
|
||||
|
||||
&:empty
|
||||
display none
|
||||
|
||||
> .mk-url-preview
|
||||
margin-top 8px
|
||||
|
||||
> .media
|
||||
> img
|
||||
> .text
|
||||
display block
|
||||
max-width 100%
|
||||
margin 0
|
||||
padding 0
|
||||
overflow-wrap break-word
|
||||
font-size 16px
|
||||
color isDark ? #fff : #717171
|
||||
|
||||
@media (min-width 500px)
|
||||
font-size 24px
|
||||
|
||||
> .renote
|
||||
margin 8px 0
|
||||
|
||||
> *
|
||||
padding 16px
|
||||
border dashed 1px #c0dac6
|
||||
border-radius 8px
|
||||
|
||||
> .location
|
||||
margin 4px 0
|
||||
font-size 12px
|
||||
color #ccc
|
||||
|
||||
> .map
|
||||
width 100%
|
||||
height 200px
|
||||
|
||||
&:empty
|
||||
display none
|
||||
|
||||
> .mk-url-preview
|
||||
margin-top 8px
|
||||
|
||||
> .files
|
||||
> img
|
||||
display block
|
||||
max-width 100%
|
||||
|
||||
> .time
|
||||
font-size 16px
|
||||
|
@ -1,10 +1,16 @@
|
||||
<template>
|
||||
<div class="mk-note-preview" :class="{ smart: $store.state.device.postStyle == 'smart' }">
|
||||
<div class="yohlumlkhizgfkvvscwfcrcggkotpvry" :class="{ smart: $store.state.device.postStyle == 'smart' }">
|
||||
<mk-avatar class="avatar" :user="note.user" v-if="$store.state.device.postStyle != 'smart'"/>
|
||||
<div class="main">
|
||||
<mk-note-header class="header" :note="note" :mini="true"/>
|
||||
<div class="body">
|
||||
<mk-sub-note-content class="text" :note="note"/>
|
||||
<p v-if="note.cw != null" class="cw">
|
||||
<span class="text" v-if="note.cw != ''">{{ note.cw }}</span>
|
||||
<mk-cw-button v-model="showContent"/>
|
||||
</p>
|
||||
<div class="content" v-show="note.cw == null || showContent">
|
||||
<mk-sub-note-content class="text" :note="note"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -14,7 +20,18 @@
|
||||
import Vue from 'vue';
|
||||
|
||||
export default Vue.extend({
|
||||
props: ['note']
|
||||
props: {
|
||||
note: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
showContent: false
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -65,16 +82,28 @@ root(isDark)
|
||||
|
||||
> .body
|
||||
|
||||
> .text
|
||||
> .cw
|
||||
cursor default
|
||||
display block
|
||||
margin 0
|
||||
padding 0
|
||||
color isDark ? #959ba7 : #717171
|
||||
overflow-wrap break-word
|
||||
color isDark ? #fff : #717171
|
||||
|
||||
.mk-note-preview[data-darkmode]
|
||||
> .text
|
||||
margin-right 8px
|
||||
|
||||
> .content
|
||||
> .text
|
||||
cursor default
|
||||
margin 0
|
||||
padding 0
|
||||
color isDark ? #959ba7 : #717171
|
||||
|
||||
.yohlumlkhizgfkvvscwfcrcggkotpvry[data-darkmode]
|
||||
root(true)
|
||||
|
||||
.mk-note-preview:not([data-darkmode])
|
||||
.yohlumlkhizgfkvvscwfcrcggkotpvry:not([data-darkmode])
|
||||
root(false)
|
||||
|
||||
</style>
|
||||
|
@ -1,10 +1,16 @@
|
||||
<template>
|
||||
<div class="sub" :class="{ smart: $store.state.device.postStyle == 'smart' }">
|
||||
<div class="zlrxdaqttccpwhpaagdmkawtzklsccam" :class="{ smart: $store.state.device.postStyle == 'smart' }">
|
||||
<mk-avatar class="avatar" :user="note.user" v-if="$store.state.device.postStyle != 'smart'"/>
|
||||
<div class="main">
|
||||
<mk-note-header class="header" :note="note" :mini="true"/>
|
||||
<div class="body">
|
||||
<mk-sub-note-content class="text" :note="note"/>
|
||||
<p v-if="note.cw != null" class="cw">
|
||||
<span class="text" v-if="note.cw != ''">{{ note.cw }}</span>
|
||||
<mk-cw-button v-model="showContent"/>
|
||||
</p>
|
||||
<div class="content" v-show="note.cw == null || showContent">
|
||||
<mk-sub-note-content class="text" :note="note"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -24,6 +30,12 @@ export default Vue.extend({
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
showContent: false
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@ -77,20 +89,31 @@ root(isDark)
|
||||
margin-bottom 2px
|
||||
|
||||
> .body
|
||||
|
||||
> .text
|
||||
> .cw
|
||||
cursor default
|
||||
display block
|
||||
margin 0
|
||||
padding 0
|
||||
color isDark ? #959ba7 : #717171
|
||||
overflow-wrap break-word
|
||||
color isDark ? #fff : #717171
|
||||
|
||||
pre
|
||||
max-height 120px
|
||||
font-size 80%
|
||||
> .text
|
||||
margin-right 8px
|
||||
|
||||
.sub[data-darkmode]
|
||||
> .content
|
||||
> .text
|
||||
margin 0
|
||||
padding 0
|
||||
color isDark ? #959ba7 : #717171
|
||||
|
||||
pre
|
||||
max-height 120px
|
||||
font-size 80%
|
||||
|
||||
.zlrxdaqttccpwhpaagdmkawtzklsccam[data-darkmode]
|
||||
root(true)
|
||||
|
||||
.sub:not([data-darkmode])
|
||||
.zlrxdaqttccpwhpaagdmkawtzklsccam:not([data-darkmode])
|
||||
root(false)
|
||||
|
||||
</style>
|
||||
|
@ -18,7 +18,7 @@
|
||||
<div class="body">
|
||||
<p v-if="p.cw != null" class="cw">
|
||||
<span class="text" v-if="p.cw != ''">{{ p.cw }}</span>
|
||||
<span class="toggle" @click="showContent = !showContent">{{ showContent ? '%i18n:@less%' : '%i18n:@more%' }}</span>
|
||||
<mk-cw-button v-model="showContent"/>
|
||||
</p>
|
||||
<div class="content" v-show="p.cw == null || showContent">
|
||||
<div class="text">
|
||||
@ -28,16 +28,14 @@
|
||||
<misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i" :class="$style.text"/>
|
||||
<a class="rp" v-if="p.renote != null">RP:</a>
|
||||
</div>
|
||||
<div class="media" v-if="p.media.length > 0">
|
||||
<mk-media-list :media-list="p.media"/>
|
||||
<div class="files" v-if="p.files.length > 0">
|
||||
<mk-media-list :media-list="p.files"/>
|
||||
</div>
|
||||
<mk-poll v-if="p.poll" :note="p" ref="pollViewer"/>
|
||||
<mk-url-preview v-for="url in urls" :url="url" :key="url"/>
|
||||
<a class="location" v-if="p.geo" :href="`https://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% %i18n:@location%</a>
|
||||
<div class="map" v-if="p.geo" ref="map"></div>
|
||||
<div class="renote" v-if="p.renote">
|
||||
<mk-note-preview :note="p.renote"/>
|
||||
</div>
|
||||
<div class="renote" v-if="p.renote"><mk-note-preview :note="p.renote"/></div>
|
||||
</div>
|
||||
<span class="app" v-if="p.app">via <b>{{ p.app.name }}</b></span>
|
||||
</div>
|
||||
@ -70,6 +68,7 @@ import parse from '../../../../../mfm/parse';
|
||||
import MkNoteMenu from '../../../common/views/components/note-menu.vue';
|
||||
import MkReactionPicker from '../../../common/views/components/reaction-picker.vue';
|
||||
import XSub from './note.sub.vue';
|
||||
import { sum } from '../../../../../prelude/array';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
@ -90,7 +89,7 @@ export default Vue.extend({
|
||||
isRenote(): boolean {
|
||||
return (this.note.renote &&
|
||||
this.note.text == null &&
|
||||
this.note.mediaIds.length == 0 &&
|
||||
this.note.fileIds.length == 0 &&
|
||||
this.note.poll == null);
|
||||
},
|
||||
|
||||
@ -100,9 +99,7 @@ export default Vue.extend({
|
||||
|
||||
reactionsCount(): number {
|
||||
return this.p.reactionCounts
|
||||
? Object.keys(this.p.reactionCounts)
|
||||
.map(key => this.p.reactionCounts[key])
|
||||
.reduce((a, b) => a + b)
|
||||
? sum(Object.values(this.p.reactionCounts))
|
||||
: 0;
|
||||
},
|
||||
|
||||
@ -353,19 +350,6 @@ root(isDark)
|
||||
> .text
|
||||
margin-right 8px
|
||||
|
||||
> .toggle
|
||||
display inline-block
|
||||
padding 4px 8px
|
||||
font-size 0.7em
|
||||
color isDark ? #393f4f : #fff
|
||||
background isDark ? #687390 : #b1b9c1
|
||||
border-radius 2px
|
||||
cursor pointer
|
||||
user-select none
|
||||
|
||||
&:hover
|
||||
background isDark ? #707b97 : #bbc4ce
|
||||
|
||||
> .content
|
||||
|
||||
> .text
|
||||
@ -414,7 +398,7 @@ root(isDark)
|
||||
.mk-url-preview
|
||||
margin-top 8px
|
||||
|
||||
> .media
|
||||
> .files
|
||||
> img
|
||||
display block
|
||||
max-width 100%
|
||||
@ -437,7 +421,7 @@ root(isDark)
|
||||
> .renote
|
||||
margin 8px 0
|
||||
|
||||
> .mk-note-preview
|
||||
> *
|
||||
padding 16px
|
||||
border dashed 1px isDark ? #4e945e : #c0dac6
|
||||
border-radius 8px
|
||||
|
@ -14,8 +14,7 @@
|
||||
</div>
|
||||
|
||||
<!-- トランジションを有効にするとなぜかメモリリークする -->
|
||||
<!-- <transition-group name="mk-notes" class="transition"> -->
|
||||
<div class="transition">
|
||||
<transition-group name="mk-notes" class="transition" tag="div">
|
||||
<template v-for="(note, i) in _notes">
|
||||
<mk-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)"/>
|
||||
<p class="date" :key="note.id + '_date'" v-if="i != notes.length - 1 && note._date != _notes[i + 1]._date">
|
||||
@ -23,8 +22,7 @@
|
||||
<span>%fa:angle-down%{{ _notes[i + 1]._datetext }}</span>
|
||||
</p>
|
||||
</template>
|
||||
</div>
|
||||
<!-- </transition-group> -->
|
||||
</transition-group>
|
||||
|
||||
<footer v-if="more">
|
||||
<button @click="loadMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }">
|
||||
@ -125,7 +123,7 @@ export default Vue.extend({
|
||||
prepend(note, silent = false) {
|
||||
//#region 弾く
|
||||
const isMyNote = note.userId == this.$store.state.i.id;
|
||||
const isPureRenote = note.renoteId != null && note.text == null && note.mediaIds.length == 0 && note.poll == null;
|
||||
const isPureRenote = note.renoteId != null && note.text == null && note.fileIds.length == 0 && note.poll == null;
|
||||
|
||||
if (this.$store.state.settings.showMyRenotes === false) {
|
||||
if (isMyNote && isPureRenote) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="mk-notify">
|
||||
<div class="mk-notify" :class="pos">
|
||||
<div>
|
||||
<mk-notification-preview :notification="notification"/>
|
||||
</div>
|
||||
@ -12,11 +12,16 @@ import * as anime from 'animejs';
|
||||
|
||||
export default Vue.extend({
|
||||
props: ['notification'],
|
||||
computed: {
|
||||
pos() {
|
||||
return this.$store.state.device.mobileNotificationPosition;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
anime({
|
||||
targets: this.$el,
|
||||
bottom: '0px',
|
||||
[this.pos]: '0px',
|
||||
duration: 500,
|
||||
easing: 'easeOutQuad'
|
||||
});
|
||||
@ -24,10 +29,10 @@ export default Vue.extend({
|
||||
setTimeout(() => {
|
||||
anime({
|
||||
targets: this.$el,
|
||||
bottom: `-${this.$el.offsetHeight}px`,
|
||||
[this.pos]: `-${this.$el.offsetHeight}px`,
|
||||
duration: 500,
|
||||
easing: 'easeOutQuad',
|
||||
complete: () => this.$destroy()
|
||||
complete: () => this.destroyDom()
|
||||
});
|
||||
}, 6000);
|
||||
});
|
||||
@ -40,8 +45,7 @@ export default Vue.extend({
|
||||
$height = 78px
|
||||
|
||||
position fixed
|
||||
z-index 1024
|
||||
bottom -($height)
|
||||
z-index 10000
|
||||
left 0
|
||||
right 0
|
||||
width 100%
|
||||
@ -52,6 +56,12 @@ export default Vue.extend({
|
||||
pointer-events none
|
||||
font-size 80%
|
||||
|
||||
&.bottom
|
||||
bottom -($height)
|
||||
|
||||
&.top
|
||||
top -($height)
|
||||
|
||||
> div
|
||||
height 100%
|
||||
-webkit-backdrop-filter blur(2px)
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="ulveipglmagnxfgvitaxyszerjwiqmwl">
|
||||
<div class="bg" ref="bg" @click="onBgClick"></div>
|
||||
<div class="main" ref="main" @click.self="onBgClick">
|
||||
<div class="bg" ref="bg"></div>
|
||||
<div class="main" ref="main">
|
||||
<mk-post-form ref="form"
|
||||
:reply="reply"
|
||||
:renote="renote"
|
||||
@ -79,15 +79,10 @@ export default Vue.extend({
|
||||
translateY: 16,
|
||||
duration: 300,
|
||||
easing: 'easeOutQuad',
|
||||
complete: () => this.$destroy()
|
||||
complete: () => this.destroyDom()
|
||||
});
|
||||
},
|
||||
|
||||
onBgClick() {
|
||||
this.$emit('cancel');
|
||||
this.close();
|
||||
},
|
||||
|
||||
onPosted() {
|
||||
this.$emit('posted');
|
||||
this.close();
|
||||
|
@ -4,14 +4,14 @@
|
||||
<header>
|
||||
<button class="cancel" @click="cancel">%fa:times%</button>
|
||||
<div>
|
||||
<span class="text-count" :class="{ over: text.length > 1000 }">{{ 1000 - text.length }}</span>
|
||||
<span class="text-count" :class="{ over: trimmedLength(text) > 1000 }">{{ 1000 - trimmedLength(text) }}</span>
|
||||
<span class="geo" v-if="geo">%fa:map-marker-alt%</span>
|
||||
<button class="submit" :disabled="!canPost" @click="post">{{ submitText }}</button>
|
||||
</div>
|
||||
</header>
|
||||
<div class="form">
|
||||
<mk-note-preview v-if="reply" :note="reply"/>
|
||||
<mk-note-preview v-if="renote" :note="renote"/>
|
||||
<mk-note-preview class="preview" v-if="reply" :note="reply"/>
|
||||
<mk-note-preview class="preview" v-if="renote" :note="renote"/>
|
||||
<div v-if="visibility == 'specified'" class="visibleUsers">
|
||||
<span v-for="u in visibleUsers">{{ u | userName }}<a @click="removeVisibleUser(u)">[x]</a></span>
|
||||
<a @click="addVisibleUser">+%i18n:@add-visible-user%</a>
|
||||
@ -59,6 +59,9 @@ import MkVisibilityChooser from '../../../common/views/components/visibility-cho
|
||||
import getFace from '../../../common/scripts/get-face';
|
||||
import parse from '../../../../../mfm/parse';
|
||||
import { host } from '../../../config';
|
||||
import { erase } from '../../../../../prelude/array';
|
||||
import { length } from 'stringz';
|
||||
import parseAcct from '../../../../../misc/acct/parse';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
@ -94,7 +97,7 @@ export default Vue.extend({
|
||||
files: [],
|
||||
poll: false,
|
||||
geo: null,
|
||||
visibility: this.$store.state.device.visibility || 'public',
|
||||
visibility: this.$store.state.settings.rememberNoteVisibility ? (this.$store.state.device.visibility || this.$store.state.settings.defaultNoteVisibility) : this.$store.state.settings.defaultNoteVisibility,
|
||||
visibleUsers: [],
|
||||
useCw: false,
|
||||
cw: null,
|
||||
@ -178,6 +181,10 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
methods: {
|
||||
trimmedLength(text: string) {
|
||||
return length(text.trim());
|
||||
},
|
||||
|
||||
addTag(tag: string) {
|
||||
insertTextAtCursor(this.$refs.text, ` #${tag} `);
|
||||
},
|
||||
@ -200,12 +207,12 @@ export default Vue.extend({
|
||||
|
||||
attachMedia(driveFile) {
|
||||
this.files.push(driveFile);
|
||||
this.$emit('change-attached-media', this.files);
|
||||
this.$emit('change-attached-files', this.files);
|
||||
},
|
||||
|
||||
detachMedia(file) {
|
||||
this.files = this.files.filter(x => x.id != file.id);
|
||||
this.$emit('change-attached-media', this.files);
|
||||
this.$emit('change-attached-files', this.files);
|
||||
},
|
||||
|
||||
onChangeFile() {
|
||||
@ -252,24 +259,23 @@ export default Vue.extend({
|
||||
addVisibleUser() {
|
||||
(this as any).apis.input({
|
||||
title: '%i18n:@username-prompt%'
|
||||
}).then(username => {
|
||||
(this as any).api('users/show', {
|
||||
username
|
||||
}).then(user => {
|
||||
}).then(acct => {
|
||||
if (acct.startsWith('@')) acct = acct.substr(1);
|
||||
(this as any).api('users/show', parseAcct(acct)).then(user => {
|
||||
this.visibleUsers.push(user);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
removeVisibleUser(user) {
|
||||
this.visibleUsers = this.visibleUsers.filter(u => u != user);
|
||||
this.visibleUsers = erase(user, this.visibleUsers);
|
||||
},
|
||||
|
||||
clear() {
|
||||
this.text = '';
|
||||
this.files = [];
|
||||
this.poll = false;
|
||||
this.$emit('change-attached-media');
|
||||
this.$emit('change-attached-files');
|
||||
},
|
||||
|
||||
post() {
|
||||
@ -277,7 +283,7 @@ export default Vue.extend({
|
||||
const viaMobile = this.$store.state.settings.disableViaMobile !== true;
|
||||
(this as any).api('notes/create', {
|
||||
text: this.text == '' ? undefined : this.text,
|
||||
mediaIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined,
|
||||
fileIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined,
|
||||
replyId: this.reply ? this.reply.id : undefined,
|
||||
renoteId: this.renote ? this.renote.id : undefined,
|
||||
poll: this.poll ? (this.$refs.poll as any).get() : undefined,
|
||||
@ -302,7 +308,7 @@ export default Vue.extend({
|
||||
if (this.text && this.text != '') {
|
||||
const hashtags = parse(this.text).filter(x => x.type == 'hashtag').map(x => x.hashtag);
|
||||
const history = JSON.parse(localStorage.getItem('hashtags') || '[]') as string[];
|
||||
localStorage.setItem('hashtags', JSON.stringify(hashtags.concat(history).reduce((a, c) => a.includes(c) ? a : [...a, c], [])));
|
||||
localStorage.setItem('hashtags', JSON.stringify(unique(hashtags.concat(history))));
|
||||
}
|
||||
},
|
||||
|
||||
@ -381,7 +387,7 @@ root(isDark)
|
||||
max-width 500px
|
||||
margin 0 auto
|
||||
|
||||
> .mk-note-preview
|
||||
> .preview
|
||||
padding 16px
|
||||
|
||||
> .visibleUsers
|
||||
|
@ -7,9 +7,9 @@
|
||||
<misskey-flavored-markdown v-if="note.text" :text="note.text" :i="$store.state.i"/>
|
||||
<a class="rp" v-if="note.renoteId">RP: ...</a>
|
||||
</div>
|
||||
<details v-if="note.media.length > 0">
|
||||
<summary>({{ '%i18n:@media-count%'.replace('{}', note.media.length) }})</summary>
|
||||
<mk-media-list :media-list="note.media"/>
|
||||
<details v-if="note.files.length > 0">
|
||||
<summary>({{ '%i18n:@media-count%'.replace('{}', note.files.length) }})</summary>
|
||||
<mk-media-list :media-list="note.files"/>
|
||||
</details>
|
||||
<details v-if="note.poll">
|
||||
<summary>%i18n:@poll%</summary>
|
||||
|
@ -34,6 +34,12 @@
|
||||
<li @click="dark"><p><template v-if="$store.state.device.darkmode">%fa:moon%</template><template v-else>%fa:R moon%</template><span>%i18n:@darkmode%</span></p></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="announcements" v-if="announcements && announcements.length > 0">
|
||||
<article v-for="announcement in announcements">
|
||||
<span v-html="announcement.title" class="title"></span>
|
||||
<div v-html="announcement.text"></div>
|
||||
</article>
|
||||
</div>
|
||||
<a :href="aboutUrl"><p class="about">%i18n:@about%</p></a>
|
||||
</div>
|
||||
</transition>
|
||||
@ -46,23 +52,32 @@ import { lang } from '../../../config';
|
||||
|
||||
export default Vue.extend({
|
||||
props: ['isOpen'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
hasGameInvitation: false,
|
||||
connection: null,
|
||||
connectionId: null,
|
||||
aboutUrl: `/docs/${lang}/about`
|
||||
aboutUrl: `/docs/${lang}/about`,
|
||||
announcements: []
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
hasUnreadNotification(): boolean {
|
||||
return this.$store.getters.isSignedIn && this.$store.state.i.hasUnreadNotification;
|
||||
},
|
||||
|
||||
hasUnreadMessagingMessage(): boolean {
|
||||
return this.$store.getters.isSignedIn && this.$store.state.i.hasUnreadMessagingMessage;
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
(this as any).os.getMeta().then(meta => {
|
||||
this.announcements = meta.broadcasts;
|
||||
});
|
||||
|
||||
if (this.$store.getters.isSignedIn) {
|
||||
this.connection = (this as any).os.stream.getConnection();
|
||||
this.connectionId = (this as any).os.stream.use();
|
||||
@ -71,6 +86,7 @@ export default Vue.extend({
|
||||
this.connection.on('reversi_no_invites', this.onReversiNoInvites);
|
||||
}
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
if (this.$store.getters.isSignedIn) {
|
||||
this.connection.off('reversi_invited', this.onReversiInvited);
|
||||
@ -78,18 +94,22 @@ export default Vue.extend({
|
||||
(this as any).os.stream.dispose(this.connectionId);
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
search() {
|
||||
const query = window.prompt('%i18n:@search%');
|
||||
if (query == null || query == '') return;
|
||||
this.$router.push(`/search?q=${encodeURIComponent(query)}`);
|
||||
},
|
||||
|
||||
onReversiInvited() {
|
||||
this.hasGameInvitation = true;
|
||||
},
|
||||
|
||||
onReversiNoInvites() {
|
||||
this.hasGameInvitation = false;
|
||||
},
|
||||
|
||||
dark() {
|
||||
this.$store.commit('device/set', {
|
||||
key: 'darkmode',
|
||||
@ -204,6 +224,17 @@ root(isDark)
|
||||
color $color
|
||||
opacity 0.5
|
||||
|
||||
.announcements
|
||||
> article
|
||||
background isDark ? rgba(30, 129, 216, 0.2) : rgba(155, 196, 232, 0.2)
|
||||
color isDark ? #fff : #3f4967
|
||||
padding 16px
|
||||
margin 8px 0
|
||||
font-size 12px
|
||||
|
||||
> .title
|
||||
font-weight bold
|
||||
|
||||
.about
|
||||
margin 0 0 8px 0
|
||||
padding 1em 0
|
||||
|
@ -41,7 +41,7 @@ export default Vue.extend({
|
||||
(this.$refs.timeline as any).init(() => new Promise((res, rej) => {
|
||||
(this as any).api('users/notes', {
|
||||
userId: this.user.id,
|
||||
withMedia: this.withMedia,
|
||||
withFiles: this.withMedia,
|
||||
limit: fetchLimit + 1
|
||||
}).then(notes => {
|
||||
if (notes.length == fetchLimit + 1) {
|
||||
@ -62,7 +62,7 @@ export default Vue.extend({
|
||||
|
||||
const promise = (this as any).api('users/notes', {
|
||||
userId: this.user.id,
|
||||
withMedia: this.withMedia,
|
||||
withFiles: this.withMedia,
|
||||
limit: fetchLimit + 1,
|
||||
untilId: (this.$refs.timeline as any).tail().id
|
||||
});
|
||||
|
@ -24,8 +24,8 @@
|
||||
<div class="body">
|
||||
<div>
|
||||
<span :data-active="src == 'home'" @click="src = 'home'">%fa:home% %i18n:@home%</span>
|
||||
<span :data-active="src == 'local'" @click="src = 'local'">%fa:R comments% %i18n:@local%</span>
|
||||
<span :data-active="src == 'hybrid'" @click="src = 'hybrid'">%fa:share-alt% %i18n:@hybrid%</span>
|
||||
<span :data-active="src == 'local'" @click="src = 'local'" v-if="enableLocalTimeline">%fa:R comments% %i18n:@local%</span>
|
||||
<span :data-active="src == 'hybrid'" @click="src = 'hybrid'" v-if="enableLocalTimeline">%fa:share-alt% %i18n:@hybrid%</span>
|
||||
<span :data-active="src == 'global'" @click="src = 'global'">%fa:globe% %i18n:@global%</span>
|
||||
<template v-if="lists">
|
||||
<span v-for="l in lists" :data-active="src == 'list' && list == l" @click="src = 'list'; list = l" :key="l.id">%fa:list% {{ l.title }}</span>
|
||||
@ -60,7 +60,8 @@ export default Vue.extend({
|
||||
src: 'home',
|
||||
list: null,
|
||||
lists: null,
|
||||
showNav: false
|
||||
showNav: false,
|
||||
enableLocalTimeline: false
|
||||
};
|
||||
},
|
||||
|
||||
@ -85,6 +86,10 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
created() {
|
||||
(this as any).os.getMeta().then(meta => {
|
||||
this.enableLocalTimeline = !meta.disableLocalTimeline;
|
||||
});
|
||||
|
||||
if (this.$store.state.device.tl) {
|
||||
this.src = this.$store.state.device.tl.src;
|
||||
if (this.src == 'list') {
|
||||
|
@ -10,80 +10,119 @@
|
||||
<ui-card>
|
||||
<div slot="title">%fa:palette% %i18n:@design%</div>
|
||||
|
||||
<ui-switch v-model="darkmode">%i18n:@dark-mode%</ui-switch>
|
||||
<ui-switch v-model="$store.state.settings.circleIcons" @change="onChangeCircleIcons">%i18n:@circle-icons%</ui-switch>
|
||||
<ui-switch v-model="$store.state.settings.iLikeSushi" @change="onChangeILikeSushi">%i18n:common.i-like-sushi%</ui-switch>
|
||||
<ui-switch v-model="$store.state.settings.disableAnimatedMfm" @change="onChangeDisableAnimatedMfm">%i18n:common.disable-animated-mfm%</ui-switch>
|
||||
<ui-switch v-model="$store.state.settings.games.reversi.showBoardLabels" @change="onChangeReversiBoardLabels">%i18n:common.show-reversi-board-labels%</ui-switch>
|
||||
<ui-switch v-model="$store.state.settings.games.reversi.useContrastStones" @change="onChangeUseContrastReversiStones">%i18n:common.use-contrast-reversi-stones%</ui-switch>
|
||||
<section>
|
||||
<ui-switch v-model="darkmode">%i18n:@dark-mode%</ui-switch>
|
||||
<ui-switch v-model="circleIcons">%i18n:@circle-icons%</ui-switch>
|
||||
<ui-switch v-model="contrastedAcct">%i18n:@contrasted-acct%</ui-switch>
|
||||
<ui-switch v-model="showFullAcct">%i18n:common.show-full-acct%</ui-switch>
|
||||
<ui-switch v-model="iLikeSushi">%i18n:common.i-like-sushi%</ui-switch>
|
||||
<ui-switch v-model="disableAnimatedMfm">%i18n:common.disable-animated-mfm%</ui-switch>
|
||||
<ui-switch v-model="alwaysShowNsfw">%i18n:common.always-show-nsfw% (%i18n:common.this-setting-is-this-device-only%)</ui-switch>
|
||||
<ui-switch v-model="games_reversi_showBoardLabels">%i18n:common.show-reversi-board-labels%</ui-switch>
|
||||
<ui-switch v-model="games_reversi_useContrastStones">%i18n:common.use-contrast-reversi-stones%</ui-switch>
|
||||
</section>
|
||||
|
||||
<div>
|
||||
<div>%i18n:@timeline%</div>
|
||||
<ui-switch v-model="$store.state.settings.showReplyTarget" @change="onChangeShowReplyTarget">%i18n:@show-reply-target%</ui-switch>
|
||||
<ui-switch v-model="$store.state.settings.showMyRenotes" @change="onChangeShowMyRenotes">%i18n:@show-my-renotes%</ui-switch>
|
||||
<ui-switch v-model="$store.state.settings.showRenotedMyNotes" @change="onChangeShowRenotedMyNotes">%i18n:@show-renoted-my-notes%</ui-switch>
|
||||
<ui-switch v-model="$store.state.settings.showLocalRenotes" @change="onChangeShowLocalRenotes">%i18n:@show-local-renotes%</ui-switch>
|
||||
</div>
|
||||
<section>
|
||||
<header>%i18n:@timeline%</header>
|
||||
<div>
|
||||
<ui-switch v-model="showReplyTarget">%i18n:@show-reply-target%</ui-switch>
|
||||
<ui-switch v-model="showMyRenotes">%i18n:@show-my-renotes%</ui-switch>
|
||||
<ui-switch v-model="showRenotedMyNotes">%i18n:@show-renoted-my-notes%</ui-switch>
|
||||
<ui-switch v-model="showLocalRenotes">%i18n:@show-local-renotes%</ui-switch>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div>
|
||||
<div>%i18n:@post-style%</div>
|
||||
<section>
|
||||
<header>%i18n:@post-style%</header>
|
||||
<ui-radio v-model="postStyle" value="standard">%i18n:@post-style-standard%</ui-radio>
|
||||
<ui-radio v-model="postStyle" value="smart">%i18n:@post-style-smart%</ui-radio>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<header>%i18n:@notification-position%</header>
|
||||
<ui-radio v-model="mobileNotificationPosition" value="bottom">%i18n:@notification-position-bottom%</ui-radio>
|
||||
<ui-radio v-model="mobileNotificationPosition" value="top">%i18n:@notification-position-top%</ui-radio>
|
||||
</section>
|
||||
</ui-card>
|
||||
|
||||
<ui-card>
|
||||
<div slot="title">%fa:cog% %i18n:@behavior%</div>
|
||||
<ui-switch v-model="$store.state.settings.fetchOnScroll" @change="onChangeFetchOnScroll">%i18n:@fetch-on-scroll%</ui-switch>
|
||||
<ui-switch v-model="$store.state.settings.disableViaMobile" @change="onChangeDisableViaMobile">%i18n:@disable-via-mobile%</ui-switch>
|
||||
<ui-switch v-model="loadRawImages">%i18n:@load-raw-images%</ui-switch>
|
||||
<ui-switch v-model="$store.state.settings.loadRemoteMedia" @change="onChangeLoadRemoteMedia">%i18n:@load-remote-media%</ui-switch>
|
||||
<ui-switch v-model="lightmode">%i18n:@i-am-under-limited-internet%</ui-switch>
|
||||
|
||||
<section>
|
||||
<ui-switch v-model="fetchOnScroll">%i18n:@fetch-on-scroll%</ui-switch>
|
||||
<ui-switch v-model="disableViaMobile">%i18n:@disable-via-mobile%</ui-switch>
|
||||
<ui-switch v-model="loadRawImages">%i18n:@load-raw-images%</ui-switch>
|
||||
<ui-switch v-model="loadRemoteMedia">%i18n:@load-remote-media%</ui-switch>
|
||||
<ui-switch v-model="lightmode">%i18n:@i-am-under-limited-internet%</ui-switch>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<header>%i18n:@note-visibility%</header>
|
||||
<ui-switch v-model="rememberNoteVisibility">%i18n:@remember-note-visibility%</ui-switch>
|
||||
<section>
|
||||
<header>%i18n:@default-note-visibility%</header>
|
||||
<ui-select v-model="defaultNoteVisibility">
|
||||
<option value="public">%i18n:common.note-visibility.public%</option>
|
||||
<option value="home">%i18n:common.note-visibility.home%</option>
|
||||
<option value="followers">%i18n:common.note-visibility.followers%</option>
|
||||
<option value="specified">%i18n:common.note-visibility.specified%</option>
|
||||
<option value="private">%i18n:common.note-visibility.private%</option>
|
||||
</ui-select>
|
||||
</section>
|
||||
</section>
|
||||
</ui-card>
|
||||
|
||||
<ui-card>
|
||||
<div slot="title">%fa:volume-up% %i18n:@sound%</div>
|
||||
|
||||
<ui-switch v-model="enableSounds">%i18n:@enable-sounds%</ui-switch>
|
||||
<section>
|
||||
<ui-switch v-model="enableSounds">%i18n:@enable-sounds%</ui-switch>
|
||||
</section>
|
||||
</ui-card>
|
||||
|
||||
<ui-card>
|
||||
<div slot="title">%fa:language% %i18n:@lang%</div>
|
||||
|
||||
<ui-select v-model="lang" placeholder="%i18n:@auto%">
|
||||
<optgroup label="%i18n:@recommended%">
|
||||
<option value="">%i18n:@auto%</option>
|
||||
</optgroup>
|
||||
<section class="fit-top">
|
||||
<ui-select v-model="lang" placeholder="%i18n:@auto%">
|
||||
<optgroup label="%i18n:@recommended%">
|
||||
<option value="">%i18n:@auto%</option>
|
||||
</optgroup>
|
||||
|
||||
<optgroup label="%i18n:@specify-language%">
|
||||
<option v-for="x in langs" :value="x[0]" :key="x[0]">{{ x[1] }}</option>
|
||||
</optgroup>
|
||||
</ui-select>
|
||||
<span>%fa:info-circle% %i18n:@lang-tip%</span>
|
||||
<optgroup label="%i18n:@specify-language%">
|
||||
<option v-for="x in langs" :value="x[0]" :key="x[0]">{{ x[1] }}</option>
|
||||
</optgroup>
|
||||
</ui-select>
|
||||
<span>%fa:info-circle% %i18n:@lang-tip%</span>
|
||||
</section>
|
||||
</ui-card>
|
||||
|
||||
<ui-card>
|
||||
<div slot="title">%fa:B twitter% %i18n:@twitter%</div>
|
||||
|
||||
<p class="account" v-if="$store.state.i.twitter"><a :href="`https://twitter.com/${$store.state.i.twitter.screenName}`" target="_blank">@{{ $store.state.i.twitter.screenName }}</a></p>
|
||||
<p>
|
||||
<a :href="`${apiUrl}/connect/twitter`" target="_blank">{{ $store.state.i.twitter ? '%i18n:@twitter-reconnect%' : '%i18n:@twitter-connect%' }}</a>
|
||||
<span v-if="$store.state.i.twitter"> or </span>
|
||||
<a :href="`${apiUrl}/disconnect/twitter`" target="_blank" v-if="$store.state.i.twitter">%i18n:@twitter-disconnect%</a>
|
||||
</p>
|
||||
<section>
|
||||
<p class="account" v-if="$store.state.i.twitter"><a :href="`https://twitter.com/${$store.state.i.twitter.screenName}`" target="_blank">@{{ $store.state.i.twitter.screenName }}</a></p>
|
||||
<p>
|
||||
<a :href="`${apiUrl}/connect/twitter`" target="_blank">{{ $store.state.i.twitter ? '%i18n:@twitter-reconnect%' : '%i18n:@twitter-connect%' }}</a>
|
||||
<span v-if="$store.state.i.twitter"> or </span>
|
||||
<a :href="`${apiUrl}/disconnect/twitter`" target="_blank" v-if="$store.state.i.twitter">%i18n:@twitter-disconnect%</a>
|
||||
</p>
|
||||
</section>
|
||||
</ui-card>
|
||||
|
||||
<ui-card>
|
||||
<div slot="title">%fa:sync-alt% %i18n:@update%</div>
|
||||
|
||||
<div>%i18n:@version% <i>{{ version }}</i></div>
|
||||
<template v-if="latestVersion !== undefined">
|
||||
<div>%i18n:@latest-version% <i>{{ latestVersion ? latestVersion : version }}</i></div>
|
||||
</template>
|
||||
<ui-button @click="checkForUpdate" :disabled="checkingForUpdate">
|
||||
<template v-if="checkingForUpdate">%i18n:@update-checking%<mk-ellipsis/></template>
|
||||
<template v-else>%i18n:@check-for-updates%</template>
|
||||
</ui-button>
|
||||
<section>
|
||||
<div>%i18n:@version% <i>{{ version }}</i></div>
|
||||
<template v-if="latestVersion !== undefined">
|
||||
<div>%i18n:@latest-version% <i>{{ latestVersion ? latestVersion : version }}</i></div>
|
||||
</template>
|
||||
<ui-button @click="checkForUpdate" :disabled="checkingForUpdate">
|
||||
<template v-if="checkingForUpdate">%i18n:@update-checking%<mk-ellipsis/></template>
|
||||
<template v-else>%i18n:@check-for-updates%</template>
|
||||
</ui-button>
|
||||
</section>
|
||||
</ui-card>
|
||||
</div>
|
||||
|
||||
@ -129,11 +168,21 @@ export default Vue.extend({
|
||||
set(value) { this.$store.commit('device/set', { key: 'darkmode', value }); }
|
||||
},
|
||||
|
||||
alwaysShowNsfw: {
|
||||
get() { return this.$store.state.device.alwaysShowNsfw; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'alwaysShowNsfw', value }); }
|
||||
},
|
||||
|
||||
postStyle: {
|
||||
get() { return this.$store.state.device.postStyle; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'postStyle', value }); }
|
||||
},
|
||||
|
||||
mobileNotificationPosition: {
|
||||
get() { return this.$store.state.device.mobileNotificationPosition; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'mobileNotificationPosition', value }); }
|
||||
},
|
||||
|
||||
lightmode: {
|
||||
get() { return this.$store.state.device.lightmode; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'lightmode', value }); }
|
||||
@ -153,6 +202,86 @@ export default Vue.extend({
|
||||
get() { return this.$store.state.device.enableSounds; },
|
||||
set(value) { this.$store.commit('device/set', { key: 'enableSounds', value }); }
|
||||
},
|
||||
|
||||
fetchOnScroll: {
|
||||
get() { return this.$store.state.settings.fetchOnScroll; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'fetchOnScroll', value }); }
|
||||
},
|
||||
|
||||
rememberNoteVisibility: {
|
||||
get() { return this.$store.state.settings.rememberNoteVisibility; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'rememberNoteVisibility', value }); }
|
||||
},
|
||||
|
||||
disableViaMobile: {
|
||||
get() { return this.$store.state.settings.disableViaMobile; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'disableViaMobile', value }); }
|
||||
},
|
||||
|
||||
loadRemoteMedia: {
|
||||
get() { return this.$store.state.settings.loadRemoteMedia; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'loadRemoteMedia', value }); }
|
||||
},
|
||||
|
||||
circleIcons: {
|
||||
get() { return this.$store.state.settings.circleIcons; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'circleIcons', value }); }
|
||||
},
|
||||
|
||||
contrastedAcct: {
|
||||
get() { return this.$store.state.settings.contrastedAcct; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'contrastedAcct', value }); }
|
||||
},
|
||||
|
||||
showFullAcct: {
|
||||
get() { return this.$store.state.settings.showFullAcct; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'showFullAcct', value }); }
|
||||
},
|
||||
|
||||
iLikeSushi: {
|
||||
get() { return this.$store.state.settings.iLikeSushi; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'iLikeSushi', value }); }
|
||||
},
|
||||
|
||||
games_reversi_showBoardLabels: {
|
||||
get() { return this.$store.state.settings.games.reversi.showBoardLabels; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'games.reversi.showBoardLabels', value }); }
|
||||
},
|
||||
|
||||
games_reversi_useContrastStones: {
|
||||
get() { return this.$store.state.settings.games.reversi.useContrastStones; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'games.reversi.useContrastStones', value }); }
|
||||
},
|
||||
|
||||
disableAnimatedMfm: {
|
||||
get() { return this.$store.state.settings.disableAnimatedMfm; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'disableAnimatedMfm', value }); }
|
||||
},
|
||||
|
||||
showReplyTarget: {
|
||||
get() { return this.$store.state.settings.showReplyTarget; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'showReplyTarget', value }); }
|
||||
},
|
||||
|
||||
showMyRenotes: {
|
||||
get() { return this.$store.state.settings.showMyRenotes; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'showMyRenotes', value }); }
|
||||
},
|
||||
|
||||
showRenotedMyNotes: {
|
||||
get() { return this.$store.state.settings.showRenotedMyNotes; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'showRenotedMyNotes', value }); }
|
||||
},
|
||||
|
||||
showLocalRenotes: {
|
||||
get() { return this.$store.state.settings.showLocalRenotes; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'showLocalRenotes', value }); }
|
||||
},
|
||||
|
||||
defaultNoteVisibility: {
|
||||
get() { return this.$store.state.settings.defaultNoteVisibility; },
|
||||
set(value) { this.$store.dispatch('settings/set', { key: 'defaultNoteVisibility', value }); }
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
@ -164,90 +293,6 @@ export default Vue.extend({
|
||||
(this as any).os.signout();
|
||||
},
|
||||
|
||||
onChangeFetchOnScroll(v) {
|
||||
this.$store.dispatch('settings/set', {
|
||||
key: 'fetchOnScroll',
|
||||
value: v
|
||||
});
|
||||
},
|
||||
|
||||
onChangeDisableViaMobile(v) {
|
||||
this.$store.dispatch('settings/set', {
|
||||
key: 'disableViaMobile',
|
||||
value: v
|
||||
});
|
||||
},
|
||||
|
||||
onChangeLoadRemoteMedia(v) {
|
||||
this.$store.dispatch('settings/set', {
|
||||
key: 'loadRemoteMedia',
|
||||
value: v
|
||||
});
|
||||
},
|
||||
|
||||
onChangeCircleIcons(v) {
|
||||
this.$store.dispatch('settings/set', {
|
||||
key: 'circleIcons',
|
||||
value: v
|
||||
});
|
||||
},
|
||||
|
||||
onChangeILikeSushi(v) {
|
||||
this.$store.dispatch('settings/set', {
|
||||
key: 'iLikeSushi',
|
||||
value: v
|
||||
});
|
||||
},
|
||||
|
||||
onChangeReversiBoardLabels(v) {
|
||||
this.$store.dispatch('settings/set', {
|
||||
key: 'games.reversi.showBoardLabels',
|
||||
value: v
|
||||
});
|
||||
},
|
||||
|
||||
onChangeUseContrastReversiStones(v) {
|
||||
this.$store.dispatch('settings/set', {
|
||||
key: 'games.reversi.useContrastStones',
|
||||
value: v
|
||||
});
|
||||
},
|
||||
|
||||
onChangeDisableAnimatedMfm(v) {
|
||||
this.$store.dispatch('settings/set', {
|
||||
key: 'disableAnimatedMfm',
|
||||
value: v
|
||||
});
|
||||
},
|
||||
|
||||
onChangeShowReplyTarget(v) {
|
||||
this.$store.dispatch('settings/set', {
|
||||
key: 'showReplyTarget',
|
||||
value: v
|
||||
});
|
||||
},
|
||||
|
||||
onChangeShowMyRenotes(v) {
|
||||
this.$store.dispatch('settings/set', {
|
||||
key: 'showMyRenotes',
|
||||
value: v
|
||||
});
|
||||
},
|
||||
|
||||
onChangeShowRenotedMyNotes(v) {
|
||||
this.$store.dispatch('settings/set', {
|
||||
key: 'showRenotedMyNotes',
|
||||
value: v
|
||||
});
|
||||
},
|
||||
|
||||
onChangeShowLocalRenotes(v) {
|
||||
this.$store.dispatch('settings/set', {
|
||||
key: 'showLocalRenotes',
|
||||
value: v
|
||||
});
|
||||
},
|
||||
|
||||
checkForUpdate() {
|
||||
this.checkingForUpdate = true;
|
||||
checkForUpdate((this as any).os, true, true).then(newer => {
|
||||
@ -273,7 +318,7 @@ export default Vue.extend({
|
||||
<style lang="stylus" scoped>
|
||||
root(isDark)
|
||||
margin 0 auto
|
||||
max-width 500px
|
||||
max-width 600px
|
||||
width 100%
|
||||
|
||||
> .signin-as
|
||||
|
@ -2,47 +2,64 @@
|
||||
<ui-card>
|
||||
<div slot="title">%fa:user% %i18n:@title%</div>
|
||||
|
||||
<ui-form :disabled="saving">
|
||||
<ui-input v-model="name" :max="30">
|
||||
<span>%i18n:@name%</span>
|
||||
</ui-input>
|
||||
<section class="fit-top">
|
||||
<ui-form :disabled="saving">
|
||||
<ui-input v-model="name" :max="30">
|
||||
<span>%i18n:@name%</span>
|
||||
</ui-input>
|
||||
|
||||
<ui-input v-model="username" readonly>
|
||||
<span>%i18n:@account%</span>
|
||||
<span slot="prefix">@</span>
|
||||
<span slot="suffix">@{{ host }}</span>
|
||||
</ui-input>
|
||||
<ui-input v-model="username" readonly>
|
||||
<span>%i18n:@account%</span>
|
||||
<span slot="prefix">@</span>
|
||||
<span slot="suffix">@{{ host }}</span>
|
||||
</ui-input>
|
||||
|
||||
<ui-input v-model="location">
|
||||
<span>%i18n:@location%</span>
|
||||
<span slot="prefix">%fa:map-marker-alt%</span>
|
||||
</ui-input>
|
||||
<ui-input v-model="location">
|
||||
<span>%i18n:@location%</span>
|
||||
<span slot="prefix">%fa:map-marker-alt%</span>
|
||||
</ui-input>
|
||||
|
||||
<ui-input v-model="birthday" type="date">
|
||||
<span>%i18n:@birthday%</span>
|
||||
<span slot="prefix">%fa:birthday-cake%</span>
|
||||
</ui-input>
|
||||
<ui-input v-model="birthday" type="date">
|
||||
<span>%i18n:@birthday%</span>
|
||||
<span slot="prefix">%fa:birthday-cake%</span>
|
||||
</ui-input>
|
||||
|
||||
<ui-textarea v-model="description" :max="500">
|
||||
<span>%i18n:@description%</span>
|
||||
</ui-textarea>
|
||||
<ui-textarea v-model="description" :max="500">
|
||||
<span>%i18n:@description%</span>
|
||||
</ui-textarea>
|
||||
|
||||
<ui-input type="file" @change="onAvatarChange">
|
||||
<span>%i18n:@avatar%</span>
|
||||
<span slot="icon">%fa:image%</span>
|
||||
<span slot="text" v-if="avatarUploading">%i18n:@uploading%<mk-ellipsis/></span>
|
||||
</ui-input>
|
||||
<ui-input type="file" @change="onAvatarChange">
|
||||
<span>%i18n:@avatar%</span>
|
||||
<span slot="icon">%fa:image%</span>
|
||||
<span slot="text" v-if="avatarUploading">%i18n:@uploading%<mk-ellipsis/></span>
|
||||
</ui-input>
|
||||
|
||||
<ui-input type="file" @change="onBannerChange">
|
||||
<span>%i18n:@banner%</span>
|
||||
<span slot="icon">%fa:image%</span>
|
||||
<span slot="text" v-if="bannerUploading">%i18n:@uploading%<mk-ellipsis/></span>
|
||||
</ui-input>
|
||||
<ui-input type="file" @change="onBannerChange">
|
||||
<span>%i18n:@banner%</span>
|
||||
<span slot="icon">%fa:image%</span>
|
||||
<span slot="text" v-if="bannerUploading">%i18n:@uploading%<mk-ellipsis/></span>
|
||||
</ui-input>
|
||||
|
||||
<ui-switch v-model="isCat">%i18n:@is-cat%</ui-switch>
|
||||
<ui-button @click="save(true)">%i18n:@save%</ui-button>
|
||||
</ui-form>
|
||||
</section>
|
||||
|
||||
<ui-button @click="save">%i18n:@save%</ui-button>
|
||||
</ui-form>
|
||||
<section>
|
||||
<header>%i18n:@advanced%</header>
|
||||
|
||||
<div>
|
||||
<ui-switch v-model="isCat" @change="save(false)">%i18n:@is-cat%</ui-switch>
|
||||
<ui-switch v-model="alwaysMarkNsfw">%i18n:common.always-mark-nsfw%</ui-switch>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<header>%i18n:@privacy%</header>
|
||||
|
||||
<div>
|
||||
<ui-switch v-model="isLocked" @change="save(false)">%i18n:@is-locked%</ui-switch>
|
||||
</div>
|
||||
</section>
|
||||
</ui-card>
|
||||
</template>
|
||||
|
||||
@ -62,12 +79,20 @@ export default Vue.extend({
|
||||
avatarId: null,
|
||||
bannerId: null,
|
||||
isCat: false,
|
||||
isLocked: false,
|
||||
saving: false,
|
||||
avatarUploading: false,
|
||||
bannerUploading: false
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
alwaysMarkNsfw: {
|
||||
get() { return this.$store.state.i.settings.alwaysMarkNsfw; },
|
||||
set(value) { (this as any).api('i/update', { alwaysMarkNsfw: value }); }
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
this.name = this.$store.state.i.name || '';
|
||||
this.username = this.$store.state.i.username;
|
||||
@ -77,6 +102,7 @@ export default Vue.extend({
|
||||
this.avatarId = this.$store.state.i.avatarId;
|
||||
this.bannerId = this.$store.state.i.bannerId;
|
||||
this.isCat = this.$store.state.i.isCat;
|
||||
this.isLocked = this.$store.state.i.isLocked;
|
||||
},
|
||||
|
||||
methods: {
|
||||
@ -124,7 +150,7 @@ export default Vue.extend({
|
||||
});
|
||||
},
|
||||
|
||||
save() {
|
||||
save(notify) {
|
||||
this.saving = true;
|
||||
|
||||
(this as any).api('i/update', {
|
||||
@ -134,7 +160,8 @@ export default Vue.extend({
|
||||
birthday: this.birthday || null,
|
||||
avatarId: this.avatarId,
|
||||
bannerId: this.bannerId,
|
||||
isCat: this.isCat
|
||||
isCat: this.isCat,
|
||||
isLocked: this.isLocked
|
||||
}).then(i => {
|
||||
this.saving = false;
|
||||
this.$store.state.i.avatarId = i.avatarId;
|
||||
@ -142,7 +169,9 @@ export default Vue.extend({
|
||||
this.$store.state.i.bannerId = i.bannerId;
|
||||
this.$store.state.i.bannerUrl = i.bannerUrl;
|
||||
|
||||
alert('%i18n:@saved%');
|
||||
if (notify) {
|
||||
alert('%i18n:@saved%');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@
|
||||
</div>
|
||||
<div class="title">
|
||||
<h1>{{ user | userName }}</h1>
|
||||
<span class="username"><mk-acct :user="user"/></span>
|
||||
<span class="username"><mk-acct :user="user" :detail="true" /></span>
|
||||
<span class="followed" v-if="user.isFollowed">%i18n:@follows-you%</span>
|
||||
</div>
|
||||
<div class="description">
|
||||
|
@ -26,7 +26,7 @@ export default Vue.extend({
|
||||
mounted() {
|
||||
(this as any).api('users/notes', {
|
||||
userId: this.user.id,
|
||||
withMedia: true,
|
||||
withFiles: true,
|
||||
limit: 6
|
||||
}).then(notes => {
|
||||
notes.forEach(note => {
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="welcome">
|
||||
<div class="wgwfgvvimdjvhjfwxropcwksnzftjqes">
|
||||
<div>
|
||||
<img :src="$store.state.device.darkmode ? 'assets/title.dark.svg' : 'assets/title.light.svg'" :alt="name">
|
||||
<p class="host">{{ host }}</p>
|
||||
@ -15,12 +15,53 @@
|
||||
<mk-welcome-timeline/>
|
||||
</div>
|
||||
<div class="hashtags">
|
||||
<router-link v-for="tag in tags" :key="tag" :to="`/tags/${ tag }`" :title="tag">#{{ tag }}</router-link>
|
||||
<mk-tag-cloud/>
|
||||
</div>
|
||||
<div class="photos">
|
||||
<div v-for="photo in photos" :style="`background-image: url(${photo.thumbnailUrl})`"></div>
|
||||
</div>
|
||||
<div class="stats" v-if="stats">
|
||||
<span>%fa:user% {{ stats.originalUsersCount | number }}</span>
|
||||
<span>%fa:pencil-alt% {{ stats.originalNotesCount | number }}</span>
|
||||
</div>
|
||||
<div class="announcements" v-if="announcements && announcements.length > 0">
|
||||
<article v-for="announcement in announcements">
|
||||
<span class="title" v-html="announcement.title"></span>
|
||||
<div v-html="announcement.text"></div>
|
||||
</article>
|
||||
</div>
|
||||
<article class="about-misskey">
|
||||
<h1>%i18n:common.intro.title%</h1>
|
||||
<p v-html="'%i18n:common.intro.about%'"></p>
|
||||
<section>
|
||||
<h2>%i18n:common.intro.features%</h2>
|
||||
<section>
|
||||
<h3>%i18n:common.intro.rich-contents%</h3>
|
||||
<div class="image"><img src="/assets/about/post.png" alt=""></div>
|
||||
<p v-html="'%i18n:common.intro.rich-contents-desc%'"></p>
|
||||
</section>
|
||||
<section>
|
||||
<h3>%i18n:common.intro.reaction%</h3>
|
||||
<div class="image"><img src="/assets/about/reaction.png" alt=""></div>
|
||||
<p v-html="'%i18n:common.intro.reaction-desc%'"></p>
|
||||
</section>
|
||||
<section>
|
||||
<h3>%i18n:common.intro.ui%</h3>
|
||||
<div class="image"><img src="/assets/about/ui.png" alt=""></div>
|
||||
<p v-html="'%i18n:common.intro.ui-desc%'"></p>
|
||||
</section>
|
||||
<section>
|
||||
<h3>%i18n:common.intro.drive%</h3>
|
||||
<div class="image"><img src="/assets/about/drive.png" alt=""></div>
|
||||
<p v-html="'%i18n:common.intro.drive-desc%'"></p>
|
||||
</section>
|
||||
</section>
|
||||
<p v-html="'%i18n:common.intro.outro%'"></p>
|
||||
</article>
|
||||
<div class="info" v-if="meta">
|
||||
<p>Version: <b>{{ meta.version }}</b></p>
|
||||
<p>Maintainer: <b><a :href="meta.maintainer.url" target="_blank">{{ meta.maintainer.name }}</a></b></p>
|
||||
</div>
|
||||
<footer>
|
||||
<small>{{ copyright }}</small>
|
||||
</footer>
|
||||
@ -30,39 +71,53 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { apiUrl, copyright, host } from '../../../config';
|
||||
import { copyright, host } from '../../../config';
|
||||
import { concat } from '../../../../../prelude/array';
|
||||
|
||||
export default Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
apiUrl,
|
||||
meta: null,
|
||||
copyright,
|
||||
stats: null,
|
||||
host,
|
||||
name: 'Misskey',
|
||||
description: '',
|
||||
tags: []
|
||||
photos: [],
|
||||
announcements: []
|
||||
};
|
||||
},
|
||||
created() {
|
||||
(this as any).os.getMeta().then(meta => {
|
||||
this.meta = meta;
|
||||
this.name = meta.name;
|
||||
this.description = meta.description;
|
||||
this.announcements = meta.broadcasts;
|
||||
});
|
||||
|
||||
(this as any).api('stats').then(stats => {
|
||||
this.stats = stats;
|
||||
});
|
||||
|
||||
(this as any).api('hashtags/trend').then(stats => {
|
||||
this.tags = stats.map(x => x.tag);
|
||||
const image = [
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/gif'
|
||||
];
|
||||
|
||||
(this as any).api('notes/local-timeline', {
|
||||
fileType: image,
|
||||
limit: 6
|
||||
}).then((notes: any[]) => {
|
||||
const files = concat(notes.map((n: any): any[] => n.files));
|
||||
this.photos = files.filter(f => image.includes(f.type)).slice(0, 6);
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.welcome
|
||||
root(isDark)
|
||||
text-align center
|
||||
//background #fff
|
||||
|
||||
@ -138,12 +193,21 @@ export default Vue.extend({
|
||||
-webkit-overflow-scrolling touch
|
||||
|
||||
> .hashtags
|
||||
padding 16px 0
|
||||
border solid 2px #ddd
|
||||
border-radius 8px
|
||||
padding 0 8px
|
||||
height 200px
|
||||
|
||||
> *
|
||||
margin 0 16px
|
||||
> .photos
|
||||
display grid
|
||||
grid-template-rows 1fr 1fr 1fr
|
||||
grid-template-columns 1fr 1fr
|
||||
gap 8px
|
||||
height 300px
|
||||
margin-top 16px
|
||||
|
||||
> div
|
||||
border-radius 4px
|
||||
background-position center center
|
||||
background-size cover
|
||||
|
||||
> .stats
|
||||
margin 16px 0
|
||||
@ -156,6 +220,68 @@ export default Vue.extend({
|
||||
> *
|
||||
margin 0 8px
|
||||
|
||||
> .announcements
|
||||
margin 16px 0
|
||||
|
||||
> article
|
||||
background isDark ? rgba(30, 129, 216, 0.2) : rgba(155, 196, 232, 0.2)
|
||||
border-radius 6px
|
||||
color isDark ? #fff : #3f4967
|
||||
padding 16px
|
||||
margin 8px 0
|
||||
font-size 12px
|
||||
|
||||
> .title
|
||||
font-weight bold
|
||||
|
||||
> .about-misskey
|
||||
margin 16px 0
|
||||
padding 32px
|
||||
font-size 14px
|
||||
background #fff
|
||||
border-radius 6px
|
||||
overflow hidden
|
||||
color #3a3e46
|
||||
|
||||
> h1
|
||||
margin 0
|
||||
|
||||
& + p
|
||||
margin-top 8px
|
||||
|
||||
> p:last-child
|
||||
margin-bottom 0
|
||||
|
||||
> section
|
||||
> h2
|
||||
border-bottom 1px solid isDark ? rgba(#000, 0.2) : rgba(#000, 0.05)
|
||||
|
||||
> section
|
||||
margin-bottom 16px
|
||||
padding-bottom 16px
|
||||
border-bottom 1px solid isDark ? rgba(#000, 0.2) : rgba(#000, 0.05)
|
||||
|
||||
> h3
|
||||
margin-bottom 8px
|
||||
|
||||
> p
|
||||
margin-bottom 0
|
||||
|
||||
> .image
|
||||
> img
|
||||
display block
|
||||
width 100%
|
||||
height 120px
|
||||
object-fit cover
|
||||
|
||||
> .info
|
||||
padding 16px 0
|
||||
border solid 2px #ddd
|
||||
border-radius 8px
|
||||
|
||||
> *
|
||||
margin 0 16px
|
||||
|
||||
> footer
|
||||
text-align center
|
||||
color #444
|
||||
@ -165,4 +291,10 @@ export default Vue.extend({
|
||||
margin 16px 0 0 0
|
||||
opacity 0.7
|
||||
|
||||
.wgwfgvvimdjvhjfwxropcwksnzftjqes[data-darkmode]
|
||||
root(true)
|
||||
|
||||
.wgwfgvvimdjvhjfwxropcwksnzftjqes:not([data-darkmode])
|
||||
root(false)
|
||||
|
||||
</style>
|
||||
|
@ -4,6 +4,7 @@ import * as nestedProperty from 'nested-property';
|
||||
|
||||
import MiOS from './mios';
|
||||
import { hostname } from './config';
|
||||
import { erase } from '../../prelude/array';
|
||||
|
||||
const defaultSettings = {
|
||||
home: null,
|
||||
@ -15,6 +16,8 @@ const defaultSettings = {
|
||||
suggestRecentHashtags: true,
|
||||
showClockOnHeader: true,
|
||||
circleIcons: true,
|
||||
contrastedAcct: true,
|
||||
showFullAcct: false,
|
||||
gradientWindowHeader: false,
|
||||
showReplyTarget: true,
|
||||
showMyRenotes: true,
|
||||
@ -24,6 +27,8 @@ const defaultSettings = {
|
||||
disableViaMobile: false,
|
||||
memo: null,
|
||||
iLikeSushi: false,
|
||||
rememberNoteVisibility: false,
|
||||
defaultNoteVisibility: 'public',
|
||||
games: {
|
||||
reversi: {
|
||||
showBoardLabels: false,
|
||||
@ -43,7 +48,9 @@ const defaultDeviceSettings = {
|
||||
debug: false,
|
||||
lightmode: false,
|
||||
loadRawImages: false,
|
||||
postStyle: 'standard'
|
||||
alwaysShowNsfw: false,
|
||||
postStyle: 'standard',
|
||||
mobileNotificationPosition: 'bottom'
|
||||
};
|
||||
|
||||
export default (os: MiOS) => new Vuex.Store({
|
||||
@ -194,7 +201,7 @@ export default (os: MiOS) => new Vuex.Store({
|
||||
|
||||
removeDeckColumn(state, id) {
|
||||
state.deck.columns = state.deck.columns.filter(c => c.id != id);
|
||||
state.deck.layout = state.deck.layout.map(ids => ids.filter(x => x != id));
|
||||
state.deck.layout = state.deck.layout.map(ids => erase(id, ids));
|
||||
state.deck.layout = state.deck.layout.filter(ids => ids.length > 0);
|
||||
},
|
||||
|
||||
@ -265,7 +272,7 @@ export default (os: MiOS) => new Vuex.Store({
|
||||
|
||||
stackLeftDeckColumn(state, id) {
|
||||
const i = state.deck.layout.findIndex(ids => ids.indexOf(id) != -1);
|
||||
state.deck.layout = state.deck.layout.map(ids => ids.filter(x => x != id));
|
||||
state.deck.layout = state.deck.layout.map(ids => erase(id, ids));
|
||||
const left = state.deck.layout[i - 1];
|
||||
if (left) state.deck.layout[i - 1].push(id);
|
||||
state.deck.layout = state.deck.layout.filter(ids => ids.length > 0);
|
||||
@ -273,7 +280,7 @@ export default (os: MiOS) => new Vuex.Store({
|
||||
|
||||
popRightDeckColumn(state, id) {
|
||||
const i = state.deck.layout.findIndex(ids => ids.indexOf(id) != -1);
|
||||
state.deck.layout = state.deck.layout.map(ids => ids.filter(x => x != id));
|
||||
state.deck.layout = state.deck.layout.map(ids => erase(id, ids));
|
||||
state.deck.layout.splice(i + 1, 0, [id]);
|
||||
state.deck.layout = state.deck.layout.filter(ids => ids.length > 0);
|
||||
},
|
||||
|
@ -3,6 +3,7 @@
|
||||
*/
|
||||
|
||||
import composeNotification from './common/scripts/compose-notification';
|
||||
import { erase } from '../../prelude/array';
|
||||
|
||||
// キャッシュするリソース
|
||||
const cachee = [
|
||||
@ -24,8 +25,7 @@ self.addEventListener('activate', ev => {
|
||||
// Clean up old caches
|
||||
ev.waitUntil(
|
||||
caches.keys().then(keys => Promise.all(
|
||||
keys
|
||||
.filter(key => key != _VERSION_)
|
||||
erase(_VERSION_, keys)
|
||||
.map(key => caches.delete(key))
|
||||
))
|
||||
);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user