Compare commits

...

28 Commits

Author SHA1 Message Date
syuilo
468ff7037f 10.49.2 2018-11-14 03:07:55 +09:00
syuilo
df23504ccf [Client] Fix #3227 2018-11-14 03:05:13 +09:00
syuilo
66e3cb8eda Update src/client/app/init.ts 2018-11-14 02:04:24 +09:00
syuilo
6ddd2389dc [Client] Add missing icons 2018-11-14 01:45:15 +09:00
syuilo
402efb8c50 [Client] Fix imports 2018-11-14 01:40:29 +09:00
syuilo
7b6eae0ce4 Fix error 2018-11-14 01:32:38 +09:00
syuilo
26ce9725ce [Client] Fix #3009 2018-11-14 01:31:36 +09:00
ButterflyOfFire
ebfaa18f12 Create setup.fr.md (#3225) 2018-11-14 00:15:33 +09:00
syuilo
cc81d41a05 [Client] Some optimizations 2018-11-13 23:33:30 +09:00
syuilo
212176ee5c Use terser instead of uglifyjs 2018-11-13 23:10:51 +09:00
syuilo
a63ec05e41 [Client] Some optimizations 2018-11-13 23:06:31 +09:00
syuilo
0dcb527bf3 [Client] Fix bug 2018-11-13 22:45:28 +09:00
syuilo
54710f17fc [Client] Some performance optimizations 2018-11-13 22:43:09 +09:00
syuilo
e58a6593c0 [Client] Fix bug 2018-11-13 22:42:31 +09:00
syuilo
62132570e1 [Client] Split components to reduce bundle size 2018-11-13 20:21:52 +09:00
MeiMei
9f0b8ba2f8 Fix: notes/create hangs when rejected (#3221) 2018-11-13 19:34:09 +09:00
syuilo
adbe0fbcd1 10.49.1 2018-11-13 16:33:21 +09:00
syuilo
7896242f57 [Client] Fix #3213 2018-11-13 16:19:46 +09:00
syuilo
4a6722b9e9 Merge branch 'master' into develop 2018-11-13 15:01:52 +09:00
syuilo
7c9fb5228b Improve usability 2018-11-13 15:01:05 +09:00
MeiMei
81805b01cc sort user timeline by date (#3210) 2018-11-13 01:17:59 +09:00
syuilo
50824a7245 10.49.0 2018-11-13 01:12:27 +09:00
syuilo
6f2953f3a7 [Client] Clear cached locale data when shouldFlush is true 2018-11-13 01:11:36 +09:00
syuilo
dd3f007582 [Client] Improve post-form widget 2018-11-13 01:04:15 +09:00
syuilo
a4b2b093fc 🎨 2018-11-13 00:21:49 +09:00
syuilo
0fbf56219f [Client] Emoji picker
Closes #3130
2018-11-13 00:12:55 +09:00
syuilo
0acacf7a8e 10.48.1 2018-11-12 06:12:45 +09:00
syuilo
c84500d914 Clean up 2018-11-12 06:12:22 +09:00
82 changed files with 1323 additions and 710 deletions

126
docs/setup.fr.md Normal file
View File

@@ -0,0 +1,126 @@
Guide d'installation et de configuration de Misskey
================================================================
Nous vous remerçions de l'intrêt que vous manifestez pour l'installation de votre propre instance Misskey !
Ce guide décrit les étapes à suivre afin d'installer et de configurer une instance Misskey.
[La version en japonnais est également disponible sur - 日本語版もあります](./setup.ja.md)
----------------------------------------------------------------
*1.* Création de l'utilisateur Misskey
----------------------------------------------------------------
Lancer misskey en tant qu'utilisateur est une mauvaise idée, nous avons besoin de créer un utilisateur dédié.
Sur Debian, à titre d'exemple :
```
adduser --disabled-password --disabled-login misskey
```
*2.* Installation des dépendances
----------------------------------------------------------------
Installez les paquets suivants :
#### Dépendences :package:
* **[Node.js](https://nodejs.org/en/)** >= 10.0.0
* **[MongoDB](https://www.mongodb.com/)** >= 3.6
##### Optionnels
* [Redis](https://redis.io/)
* Redis est optionnel mais nous vous recommandons vivement de l'installer
* [Elasticsearch](https://www.elastic.co/) - requis pour pouvoir activer la fonctionnalité de recherche
*3.* Paramètrage de MongoDB
----------------------------------------------------------------
En mode root :
1. `mongo` Accédez au shell de mango
2. `use misskey` Utilisez la base de données misskey
3. `db.users.save( {dummy:"dummy"} )` Write dummy data to initialize the db.
4. `db.createUser( { user: "misskey", pwd: "<password>", roles: [ { role: "readWrite", db: "misskey" } ] } )` Créez l'utilisateur misskey.
5. `exit` Vous avez terminé !
*4.* Installation de Misskey
----------------------------------------------------------------
1. `su - misskey` Basculez vers l'utilisateur misskey.
2. `git clone -b master git://github.com/syuilo/misskey.git` Clonez la branche master du dépôt misskey.
3. `cd misskey` Accédez au dossier misskey.
4. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)` Télécharge la [version la plus récente](https://github.com/syuilo/misskey/releases/latest)
5. `npm install` Installez les dépendances de misskey.
*(optionnel)* Génération des clés VAPID
----------------------------------------------------------------
Si vous désirez activer ServiceWorker, vous devez générer les clés VAPID :
Unless you have set your global node_modules location elsewhere, vous devez lancer ceci en mode root.
``` shell
npm install web-push -g
web-push generate-vapid-keys
```
*5.* Création du fichier de configuration
----------------------------------------------------------------
1. `cp .config/example.yml .config/default.yml` Copiez le fichier `.config/example.yml` et renommez-le `default.yml`.
2. Editez le fichier `default.yml`
*6.* Construction de Misskey
----------------------------------------------------------------
Construisez Misskey comme ceci :
`npm run build`
Si vous êtes sous Debian, vous serez amené à installer les paquets `build-essential`, `python`.
Si vous rencontrez des erreurs concernant certains modules, utilisez node-gyp:
1. `npm install -g node-gyp`
2. `node-gyp configure`
3. `node-gyp build`
4. `npm run build`
*7.* C'est tout.
----------------------------------------------------------------
Excellent ! Maintenant, vous avez un environnement prêt pour lancer Misskey
### Lancement conventionnel
Lancez tout simplement `npm start`. Bonne chance et amusez-vous bien !
### Démarrage avec systemd
1. Créez une service systemd sur : `/etc/systemd/system/misskey.service`
2. Editez-le puis copiez et coller ceci dans le fichier :
```
[Unit]
Description=Misskey daemon
[Service]
Type=simple
User=misskey
ExecStart=/usr/bin/npm start
WorkingDirectory=/home/misskey/misskey
TimeoutSec=60
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=misskey
Restart=always
[Install]
WantedBy=multi-user.target
```
3. `systemctl daemon-reload ; systemctl enable misskey` Redémarre systemd et active le service misskey.
4. `systemctl start misskey` Démarre le service misskey.
Vous pouvez vérifier si le service a démarré en utilisant la commande `systemctl status misskey`.
### Méthode de mise à jour vers la plus récente version de Misskey
1. `git fetch`
2. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)`
3. `npm install`
4. `npm run build`
5. Consultez [ChangeLog](../CHANGELOG.md) pour les information de migration.
----------------------------------------------------------------
Si vous rencontrez des difficultés ou avez d'autres questions, n'hésitez pas à nous contacter !

View File

@@ -379,6 +379,17 @@ common/views/components/poll-editor.vue:
common/views/components/reaction-picker.vue: common/views/components/reaction-picker.vue:
choose-reaction: "リアクションを選択" choose-reaction: "リアクションを選択"
common/views/components/emoji-picker.vue:
custom-emoji: "カスタム絵文字"
people: "人"
animals-and-nature: "動物&自然"
food-and-drink: "食べ物&飲み物"
activity: "アクティビティ"
travel-and-places: "場所"
objects: "物"
symbols: "記号"
flags: "旗"
common/views/components/signin.vue: common/views/components/signin.vue:
username: "ユーザー名" username: "ユーザー名"
password: "パスワード" password: "パスワード"
@@ -657,13 +668,6 @@ desktop/views/components/media-video.vue:
sensitive: "閲覧注意" sensitive: "閲覧注意"
click-to-show: "クリックして表示" click-to-show: "クリックして表示"
desktop/views/components/follow-button.vue:
following: "フォロー中"
follow: "フォロー"
request-pending: "フォロー許可待ち"
follow-processing: "フォロー処理中"
follow-request: "フォロー申請"
desktop/views/components/followers-window.vue: desktop/views/components/followers-window.vue:
followers: "{} のフォロワー" followers: "{} のフォロワー"
@@ -1325,7 +1329,7 @@ mobile/views/components/media-video.vue:
sensitive: "閲覧注意" sensitive: "閲覧注意"
click-to-show: "クリックして表示" click-to-show: "クリックして表示"
mobile/views/components/follow-button.vue: common/views/components/follow-button.vue:
following: "フォロー中" following: "フォロー中"
follow: "フォロー" follow: "フォロー"
request-pending: "フォロー許可待ち" request-pending: "フォロー許可待ち"

View File

@@ -1,8 +1,8 @@
{ {
"name": "misskey", "name": "misskey",
"author": "syuilo <i@syuilo.com>", "author": "syuilo <i@syuilo.com>",
"version": "10.48.0", "version": "10.49.2",
"clientVersion": "2.0.11733", "clientVersion": "2.0.11761",
"codename": "nighthike", "codename": "nighthike",
"main": "./built/index.js", "main": "./built/index.js",
"private": true, "private": true,
@@ -198,6 +198,7 @@
"summaly": "2.2.0", "summaly": "2.2.0",
"systeminformation": "3.47.0", "systeminformation": "3.47.0",
"syuilo-password-strength": "0.0.1", "syuilo-password-strength": "0.0.1",
"terser-webpack-plugin": "1.1.0",
"textarea-caret": "3.1.0", "textarea-caret": "3.1.0",
"tinycolor2": "1.4.1", "tinycolor2": "1.4.1",
"tmp": "0.0.33", "tmp": "0.0.33",

View File

@@ -66,19 +66,6 @@ export default abstract class Chart<T> {
} else { } else {
this.collection.createIndex({ span: -1, date: -1 }, { unique: true }); this.collection.createIndex({ span: -1, date: -1 }, { unique: true });
} }
//#region 後方互換性のため
this.collection.find({ span: 'day' }, { fields: { _id: true, date: true } }).then(logs => {
logs.forEach(log => {
this.collection.update({ _id: log._id }, { $set: { date: utc(log.date).hour(0).toDate() } });
});
});
this.collection.find({ span: 'hour' }, { fields: { _id: true, date: true } }).then(logs => {
logs.forEach(log => {
this.collection.update({ _id: log._id }, { $set: { date: utc(log.date).toDate() } });
});
});
//#endregion
} }
@autobind @autobind

View File

@@ -9,14 +9,11 @@ import './style.styl';
import init from '../init'; import init from '../init';
import Index from './views/index.vue'; import Index from './views/index.vue';
import * as config from '../config';
/** /**
* init * init
*/ */
init(launch => { init(launch => {
document.title = `${config.name} | %i18n:common.application-authorization%`;
// Init router // Init router
const router = new VueRouter({ const router = new VueRouter({
mode: 'history', mode: 'history',

View File

@@ -146,6 +146,8 @@
function refresh() { function refresh() {
localStorage.setItem('shouldFlush', 'false'); localStorage.setItem('shouldFlush', 'false');
localStorage.removeItem('locale');
// Random // Random
localStorage.setItem('salt', Math.random().toString().substr(2, 8)); localStorage.setItem('salt', Math.random().toString().substr(2, 8));

View File

@@ -66,7 +66,7 @@ export default function<T extends object>(data: {
this.bakeProps(); this.bakeProps();
(this as any).api('i/update_widget', { this.$root.api('i/update_widget', {
id: this.id, id: this.id,
data: this.props data: this.props
}); });

View File

@@ -0,0 +1,202 @@
<template>
<div class="prlncendiewqqkrevzeruhndoakghvtx">
<header>
<button v-for="category in categories"
:title="category.text"
@click="go(category.ref)"
:class="{ active: category.isActive }"
>
<fa :icon="category.icon" fixed-width/>
</button>
</header>
<div class="emojis" ref="emojis" @scroll.passive="onScroll">
<section v-for="category in categories" :ref="category.ref">
<header><fa :icon="category.icon" fixed-width/> {{ category.text }}</header>
<div v-if="category.name">
<button v-for="emoji in Object.entries(lib).filter(([k, v]) => v.category === category.name)"
:title="emoji[0]"
@click="chosen(emoji[1].char)"
>
<mk-emoji :emoji="emoji[1].char"/>
</button>
</div>
<div v-else>
<button v-for="emoji in customEmojis"
:title="emoji.name"
@click="chosen(`:${emoji.name}:`)"
>
<img :src="emoji.url" :alt="emoji.name"/>
</button>
</div>
</section>
</div>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import i18n from '../../../i18n';
import { lib } from 'emojilib';
import { faAsterisk, faLeaf, faUtensils, faFutbol, faCity, faDice } from '@fortawesome/free-solid-svg-icons';
import { faHeart, faFlag } from '@fortawesome/free-regular-svg-icons';
export default Vue.extend({
i18n: i18n('common/views/components/emoji-picker.vue'),
data() {
return {
lib,
customEmojis: [],
categories: [{
ref: 'customEmojiSection',
text: this.$t('custom-emoji'),
icon: faAsterisk,
isActive: true
}, {
name: 'people',
ref: 'peopleSection',
text: this.$t('people'),
icon: ['far', 'laugh'],
isActive: false
}, {
name: 'animals_and_nature',
ref: 'animalsAndNatureSection',
text: this.$t('animals-and-nature'),
icon: faLeaf,
isActive: false
}, {
name: 'food_and_drink',
ref: 'foodAndDrinkSection',
text: this.$t('food-and-drink'),
icon: faUtensils,
isActive: false
}, {
name: 'activity',
ref: 'activitySection',
text: this.$t('activity'),
icon: faFutbol,
isActive: false
}, {
name: 'travel_and_places',
ref: 'travelAndPlacesSection',
text: this.$t('travel-and-places'),
icon: faCity,
isActive: false
}, {
name: 'objects',
ref: 'objectsSection',
text: this.$t('objects'),
icon: faDice,
isActive: false
}, {
name: 'symbols',
ref: 'symbolsSection',
text: this.$t('symbols'),
icon: faHeart,
isActive: false
}, {
name: 'flags',
ref: 'flagsSection',
text: this.$t('flags'),
icon: faFlag,
isActive: false
}]
}
},
created() {
this.customEmojis = (this.$root.getMetaSync() || { emojis: [] }).emojis || [];
},
methods: {
go(ref) {
this.$refs.emojis.scrollTop = this.$refs[ref][0].offsetTop;
},
onScroll(e) {
const section = this.categories.forEach(x => {
const top = e.target.scrollTop;
const el = this.$refs[x.ref][0];
x.isActive = el.offsetTop <= top && el.offsetTop + el.offsetHeight > top;
});
},
chosen(emoji) {
this.$emit('chosen', emoji);
}
}
});
</script>
<style lang="stylus" scoped>
.prlncendiewqqkrevzeruhndoakghvtx
width 350px
background var(--face)
> header
display flex
> button
flex 1
padding 10px 0
font-size 16px
color var(--text)
transition color 0.2s ease
&:hover
color var(--textHighlighted)
transition color 0s
&.active
color var(--primary)
transition color 0s
> .emojis
height 300px
overflow-y auto
overflow-x hidden
> section
> header
position sticky
top 0
left 0
z-index 1
padding 8px
background var(--faceHeader)
color var(--text)
font-size 12px
> div
display grid
grid-template-columns 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr
gap 4px
padding 8px
> button
padding 0
width 100%
&:before
content ''
display block
width 1px
height 0
padding-bottom 100%
&:hover
> *
transform scale(1.2)
transition transform 0s
> *
position absolute
top 0
left 0
width 100%
height 100%
font-size 28px
transition transform 0.2s ease
pointer-events none
</style>

View File

@@ -22,7 +22,7 @@ export default Vue.extend({
}, },
customEmojis: { customEmojis: {
required: false, required: false,
default: [] default: () => []
} }
}, },

View File

@@ -0,0 +1,184 @@
<template>
<button class="wfliddvnhxvyusikowhxozkyxyenqxqr"
:class="{ wait, block, mini, active: isFollowing || hasPendingFollowRequestFromYou }"
@click="onClick"
:disabled="wait"
>
<template v-if="!wait">
<fa :icon="iconAndText[0]"/> <template v-if="!mini">{{ iconAndText[1] }}</template>
</template>
<template v-else><fa icon="spinner" pulse fixed-width/></template>
</button>
</template>
<script lang="ts">
import Vue from 'vue';
import i18n from '../../../i18n';
export default Vue.extend({
i18n: i18n('common/views/components/follow-button.vue'),
props: {
user: {
type: Object,
required: true
},
block: {
type: Boolean,
required: false,
default: false
},
mini: {
type: Boolean,
required: false,
default: false
}
},
data() {
return {
isFollowing: this.user.isFollowing,
hasPendingFollowRequestFromYou: this.user.hasPendingFollowRequestFromYou,
wait: false,
connection: null
};
},
computed: {
iconAndText(): any[] {
return (
(this.hasPendingFollowRequestFromYou && this.user.isLocked) ? ['hourglass-half', this.$t('request-pending')] :
(this.hasPendingFollowRequestFromYou && !this.user.isLocked) ? ['hourglass-start', this.$t('follow-processing')] :
(this.isFollowing) ? ['minus', this.$t('following')] :
(!this.isFollowing && this.user.isLocked) ? ['plus', this.$t('follow-request')] :
(!this.isFollowing && !this.user.isLocked) ? ['plus', this.$t('follow')] :
[]
);
}
},
mounted() {
this.connection = this.$root.stream.useSharedConnection('main');
this.connection.on('follow', this.onFollowChange);
this.connection.on('unfollow', this.onFollowChange);
},
beforeDestroy() {
this.connection.dispose();
},
methods: {
onFollowChange(user) {
if (user.id == this.user.id) {
this.isFollowing = user.isFollowing;
this.hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou;
}
},
async onClick() {
this.wait = true;
try {
if (this.isFollowing) {
await this.$root.api('following/delete', {
userId: this.user.id
});
} else {
if (this.hasPendingFollowRequestFromYou) {
await this.$root.api('following/requests/cancel', {
userId: this.user.id
});
} else if (this.user.isLocked) {
await this.$root.api('following/create', {
userId: this.user.id
});
this.hasPendingFollowRequestFromYou = true;
} else {
await this.$root.api('following/create', {
userId: this.user.id
});
this.hasPendingFollowRequestFromYou = true;
}
}
} catch (e) {
console.error(e);
} finally {
this.wait = false;
}
}
}
});
</script>
<style lang="stylus" scoped>
.wfliddvnhxvyusikowhxozkyxyenqxqr
display block
user-select none
cursor pointer
padding 0 16px
margin 0
min-width 100px
line-height 36px
font-size 14px
font-weight bold
color var(--primary)
background transparent
outline none
border solid 1px var(--primary)
border-radius 36px
&.mini
padding 0
min-width 0
width 32px
height 32px
font-size 16px
border-radius 4px
line-height 32px
&:focus
&:after
border-radius 8px
&.block
width 100%
&:focus
&:after
content ""
pointer-events none
position absolute
top -5px
right -5px
bottom -5px
left -5px
border 2px solid var(--primaryAlpha03)
border-radius 36px
&:hover
background var(--primaryAlpha01)
&:active
background var(--primaryAlpha02)
&.active
color var(--primaryForeground)
background var(--primary)
&:hover
background var(--primaryLighten10)
border-color var(--primaryLighten10)
&:active
background var(--primaryDarken10)
border-color var(--primaryDarken10)
&.wait
cursor wait !important
opacity 0.7
*
pointer-events none
</style>

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="mk-github-setting"> <div class="mk-github-setting">
<p>{{ $t('description') }}<a :href="`${docsUrl}/link-to-github`" target="_blank">{{ $t('detail') }}</a></p> <p>{{ $t('description') }}</p>
<p class="account" v-if="$store.state.i.github" :title="`GitHub ID: ${$store.state.i.github.id}`">{{ $t('connected-to') }}: <a :href="`https://github.com/${$store.state.i.github.login}`" target="_blank">@{{ $store.state.i.github.login }}</a></p> <p class="account" v-if="$store.state.i.github" :title="`GitHub ID: ${$store.state.i.github.id}`">{{ $t('connected-to') }}: <a :href="`https://github.com/${$store.state.i.github.login}`" target="_blank">@{{ $store.state.i.github.login }}</a></p>
<p> <p>
<a :href="`${apiUrl}/connect/github`" target="_blank" @click.prevent="connect">{{ $store.state.i.github ? this.$t('reconnect') : this.$t('connect') }}</a> <a :href="`${apiUrl}/connect/github`" target="_blank" @click.prevent="connect">{{ $store.state.i.github ? this.$t('reconnect') : this.$t('connect') }}</a>
@@ -14,15 +14,14 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import i18n from '../../../i18n'; import i18n from '../../../i18n';
import { apiUrl, docsUrl } from '../../../config'; import { apiUrl } from '../../../config';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('common/views/components/github-setting.vue'), i18n: i18n('common/views/components/github-setting.vue'),
data() { data() {
return { return {
form: null, form: null,
apiUrl, apiUrl
docsUrl
}; };
}, },
mounted() { mounted() {

View File

@@ -1,13 +1,8 @@
import Vue from 'vue'; import Vue from 'vue';
import muteAndBlock from './mute-and-block.vue'; import followButton from './follow-button.vue';
import error from './error.vue'; import error from './error.vue';
import apiSettings from './api-settings.vue';
import passwordSettings from './password-settings.vue';
import driveSettings from './drive-settings.vue';
import profileEditor from './profile-editor.vue';
import noteSkeleton from './note-skeleton.vue'; import noteSkeleton from './note-skeleton.vue';
import theme from './theme.vue';
import instance from './instance.vue'; import instance from './instance.vue';
import cwButton from './cw-button.vue'; import cwButton from './cw-button.vue';
import tagCloud from './tag-cloud.vue'; import tagCloud from './tag-cloud.vue';
@@ -27,7 +22,6 @@ import pollEditor from './poll-editor.vue';
import reactionIcon from './reaction-icon.vue'; import reactionIcon from './reaction-icon.vue';
import reactionsViewer from './reactions-viewer.vue'; import reactionsViewer from './reactions-viewer.vue';
import time from './time.vue'; import time from './time.vue';
import timer from './timer.vue';
import mediaList from './media-list.vue'; import mediaList from './media-list.vue';
import uploader from './uploader.vue'; import uploader from './uploader.vue';
import streamIndicator from './stream-indicator.vue'; import streamIndicator from './stream-indicator.vue';
@@ -51,14 +45,9 @@ import uiInfo from './ui/info.vue';
import formButton from './ui/form/button.vue'; import formButton from './ui/form/button.vue';
import formRadio from './ui/form/radio.vue'; import formRadio from './ui/form/radio.vue';
Vue.component('mk-mute-and-block', muteAndBlock); Vue.component('mk-follow-button', followButton);
Vue.component('mk-error', error); Vue.component('mk-error', error);
Vue.component('mk-api-settings', apiSettings);
Vue.component('mk-password-settings', passwordSettings);
Vue.component('mk-drive-settings', driveSettings);
Vue.component('mk-profile-editor', profileEditor);
Vue.component('mk-note-skeleton', noteSkeleton); Vue.component('mk-note-skeleton', noteSkeleton);
Vue.component('mk-theme', theme);
Vue.component('mk-instance', instance); Vue.component('mk-instance', instance);
Vue.component('mk-cw-button', cwButton); Vue.component('mk-cw-button', cwButton);
Vue.component('mk-tag-cloud', tagCloud); Vue.component('mk-tag-cloud', tagCloud);
@@ -78,7 +67,6 @@ Vue.component('mk-poll-editor', pollEditor);
Vue.component('mk-reaction-icon', reactionIcon); Vue.component('mk-reaction-icon', reactionIcon);
Vue.component('mk-reactions-viewer', reactionsViewer); Vue.component('mk-reactions-viewer', reactionsViewer);
Vue.component('mk-time', time); Vue.component('mk-time', time);
Vue.component('mk-timer', timer);
Vue.component('mk-media-list', mediaList); Vue.component('mk-media-list', mediaList);
Vue.component('mk-uploader', uploader); Vue.component('mk-uploader', uploader);
Vue.component('mk-stream-indicator', streamIndicator); Vue.component('mk-stream-indicator', streamIndicator);

View File

@@ -8,7 +8,7 @@
<p class="empty" v-if="!init && messages.length == 0"><fa icon="info-circle"/>{{ $t('empty') }}</p> <p class="empty" v-if="!init && messages.length == 0"><fa icon="info-circle"/>{{ $t('empty') }}</p>
<p class="no-history" v-if="!init && messages.length > 0 && !existMoreMessages"><fa icon="flag"/>{{ $t('no-history') }}</p> <p class="no-history" v-if="!init && messages.length > 0 && !existMoreMessages"><fa icon="flag"/>{{ $t('no-history') }}</p>
<button class="more" :class="{ fetching: fetchingMoreMessages }" v-if="existMoreMessages" @click="fetchMoreMessages" :disabled="fetchingMoreMessages"> <button class="more" :class="{ fetching: fetchingMoreMessages }" v-if="existMoreMessages" @click="fetchMoreMessages" :disabled="fetchingMoreMessages">
<template v-if="fetchingMoreMessages"><fa icon="spinner .pulse" fixed-width/></template>{{ fetchingMoreMessages ? $t('@.loading') : $t('@.load-more') }} <template v-if="fetchingMoreMessages"><fa icon="spinner" pulse fixed-width/></template>{{ fetchingMoreMessages ? $t('@.loading') : $t('@.load-more') }}
</button> </button>
<template v-for="(message, i) in _messages"> <template v-for="(message, i) in _messages">
<x-message :message="message" :key="message.id"/> <x-message :message="message" :key="message.id"/>

View File

@@ -45,7 +45,7 @@
</template> </template>
</div> </div>
<p class="no-history" v-if="!fetching && messages.length == 0">{{ $t('no-history') }}</p> <p class="no-history" v-if="!fetching && messages.length == 0">{{ $t('no-history') }}</p>
<p class="fetching" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p> <p class="fetching" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p>
</div> </div>
</template> </template>

View File

@@ -10,7 +10,7 @@
<span>{{ $t('username') }}</span> <span>{{ $t('username') }}</span>
<span slot="prefix">@</span> <span slot="prefix">@</span>
<span slot="suffix">@{{ host }}</span> <span slot="suffix">@{{ host }}</span>
<p slot="desc" v-if="usernameState == 'wait'" style="color:#999"><fa icon="spinner .pulse" fixed-width/> {{ $t('checking') }}</p> <p slot="desc" v-if="usernameState == 'wait'" style="color:#999"><fa icon="spinner" pulse fixed-width/> {{ $t('checking') }}</p>
<p slot="desc" v-if="usernameState == 'ok'" style="color:#3CB7B5"><fa icon="check" fixed-width/> {{ $t('available') }}</p> <p slot="desc" v-if="usernameState == 'ok'" style="color:#3CB7B5"><fa icon="check" fixed-width/> {{ $t('available') }}</p>
<p slot="desc" v-if="usernameState == 'unavailable'" style="color:#FF1161"><fa icon="exclamation-triangle" fixed-width/> {{ $t('unavailable') }}</p> <p slot="desc" v-if="usernameState == 'unavailable'" style="color:#FF1161"><fa icon="exclamation-triangle" fixed-width/> {{ $t('unavailable') }}</p>
<p slot="desc" v-if="usernameState == 'error'" style="color:#FF1161"><fa icon="exclamation-triangle" fixed-width/> {{ $t('error') }}</p> <p slot="desc" v-if="usernameState == 'error'" style="color:#FF1161"><fa icon="exclamation-triangle" fixed-width/> {{ $t('error') }}</p>

View File

@@ -1,11 +1,11 @@
<template> <template>
<div class="mk-stream-indicator"> <div class="mk-stream-indicator">
<p v-if="stream.state == 'initializing'"> <p v-if="stream.state == 'initializing'">
<fa icon="spinner .pulse"/> <fa icon="spinner" pulse/>
<span>{{ $t('connecting') }}<mk-ellipsis/></span> <span>{{ $t('connecting') }}<mk-ellipsis/></span>
</p> </p>
<p v-if="stream.state == 'reconnecting'"> <p v-if="stream.state == 'reconnecting'">
<fa icon="spinner .pulse"/> <fa icon="spinner" pulse/>
<span>{{ $t('reconnecting') }}<mk-ellipsis/></span> <span>{{ $t('reconnecting') }}<mk-ellipsis/></span>
</p> </p>
<p v-if="stream.state == 'connected'"> <p v-if="stream.state == 'connected'">

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="jtivnzhfwquxpsfidertopbmwmchmnmo"> <div class="jtivnzhfwquxpsfidertopbmwmchmnmo">
<p class="fetching" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p> <p class="fetching" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p>
<p class="empty" v-else-if="tags.length == 0"><fa icon="exclamation-circle"/>{{ $t('empty') }}</p> <p class="empty" v-else-if="tags.length == 0"><fa icon="exclamation-circle"/>{{ $t('empty') }}</p>
<div v-else> <div v-else>
<vue-word-cloud <vue-word-cloud

View File

@@ -1,49 +0,0 @@
<template>
<time class="mk-time">
{{ hh }}:{{ mm }}:{{ ss }}
</time>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
props: {
time: {
type: [Date, String],
required: true
}
},
data() {
return {
tickId: null,
hh: null,
mm: null,
ss: null
};
},
computed: {
_time(): Date {
return typeof this.time == 'string' ? new Date(this.time) : this.time;
}
},
created() {
this.tick();
this.tickId = setInterval(this.tick, 1000);
},
destroyed() {
clearInterval(this.tickId);
},
methods: {
tick() {
const now = new Date().getTime();
const start = this._time.getTime();
const ago = Math.floor((now - start) / 1000);
this.hh = Math.floor(ago / (60 * 60)).toString().padStart(2, '0');
this.mm = Math.floor(ago / 60).toString().padStart(2, '0');
this.ss = (ago % 60).toString().padStart(2, '0');
}
}
});
</script>

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="csqvmxybqbycalfhkxvyfrgbrdalkaoc"> <div class="csqvmxybqbycalfhkxvyfrgbrdalkaoc">
<p class="fetching" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p> <p class="fetching" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p>
<p class="empty" v-else-if="stats.length == 0"><fa icon="exclamation-circle"/>{{ $t('empty') }}</p> <p class="empty" v-else-if="stats.length == 0"><fa icon="exclamation-circle"/>{{ $t('empty') }}</p>
<!-- トランジションを有効にするとなぜかメモリリークする --> <!-- トランジションを有効にするとなぜかメモリリークする -->
<transition-group v-else tag="div" name="chart"> <transition-group v-else tag="div" name="chart">

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="mk-twitter-setting"> <div class="mk-twitter-setting">
<p>{{ $t('description') }}<a :href="`${docsUrl}/link-to-twitter`" target="_blank">{{ $t('detail') }}</a></p> <p>{{ $t('description') }}</p>
<p class="account" v-if="$store.state.i.twitter" :title="`Twitter ID: ${$store.state.i.twitter.userId}`">{{ $t('connected-to') }}: <a :href="`https://twitter.com/${$store.state.i.twitter.screenName}`" target="_blank">@{{ $store.state.i.twitter.screenName }}</a></p> <p class="account" v-if="$store.state.i.twitter" :title="`Twitter ID: ${$store.state.i.twitter.userId}`">{{ $t('connected-to') }}: <a :href="`https://twitter.com/${$store.state.i.twitter.screenName}`" target="_blank">@{{ $store.state.i.twitter.screenName }}</a></p>
<p> <p>
<a :href="`${apiUrl}/connect/twitter`" target="_blank" @click.prevent="connect">{{ $store.state.i.twitter ? this.$t('reconnect') : this.$t('connect') }}</a> <a :href="`${apiUrl}/connect/twitter`" target="_blank" @click.prevent="connect">{{ $store.state.i.twitter ? this.$t('reconnect') : this.$t('connect') }}</a>
@@ -14,15 +14,14 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import i18n from '../../../i18n'; import i18n from '../../../i18n';
import { apiUrl, docsUrl } from '../../../config'; import { apiUrl } from '../../../config';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('common/views/components/twitter-setting.vue'), i18n: i18n('common/views/components/twitter-setting.vue'),
data() { data() {
return { return {
form: null, form: null,
apiUrl, apiUrl
docsUrl
}; };
}, },
mounted() { mounted() {

View File

@@ -3,7 +3,7 @@
<ol v-if="uploads.length > 0"> <ol v-if="uploads.length > 0">
<li v-for="ctx in uploads" :key="ctx.id"> <li v-for="ctx in uploads" :key="ctx.id">
<div class="img" :style="{ backgroundImage: `url(${ ctx.img })` }"></div> <div class="img" :style="{ backgroundImage: `url(${ ctx.img })` }"></div>
<p class="name"><fa icon="spinner .pulse"/>{{ ctx.name }}</p> <p class="name"><fa icon="spinner" pulse/>{{ ctx.name }}</p>
<p class="status"> <p class="status">
<span class="initing" v-if="ctx.progress == undefined">{{ $t('waiting') }}<mk-ellipsis/></span> <span class="initing" v-if="ctx.progress == undefined">{{ $t('waiting') }}<mk-ellipsis/></span>
<span class="kb" v-if="ctx.progress != undefined">{{ String(Math.floor(ctx.progress.value / 1024)).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,') }}<i>KB</i> / {{ String(Math.floor(ctx.progress.max / 1024)).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,') }}<i>KB</i></span> <span class="kb" v-if="ctx.progress != undefined">{{ String(Math.floor(ctx.progress.value / 1024)).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,') }}<i>KB</i> / {{ String(Math.floor(ctx.progress.max / 1024)).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,') }}<i>KB</i></span>

View File

@@ -25,7 +25,7 @@
<template v-else-if="!user.isFollowing && user.isLocked"><fa icon="plus"/> {{ $t('follow-request') }}</template> <template v-else-if="!user.isFollowing && user.isLocked"><fa icon="plus"/> {{ $t('follow-request') }}</template>
<template v-else-if="!user.isFollowing && !user.isLocked"><fa icon="plus"/> {{ $t('follow') }}</template> <template v-else-if="!user.isFollowing && !user.isLocked"><fa icon="plus"/> {{ $t('follow') }}</template>
</template> </template>
<template v-else><fa icon="spinner .pulse" fixed-width/></template> <template v-else><fa icon="spinner" pulse fixed-width/></template>
</button> </button>
</div> </div>
</template> </template>

View File

@@ -3,9 +3,15 @@
<mk-widget-container :show-header="props.design == 0" :naked="props.design == 2"> <mk-widget-container :show-header="props.design == 0" :naked="props.design == 2">
<template slot="header"><fa icon="camera"/>{{ $t('title') }}</template> <template slot="header"><fa icon="camera"/>{{ $t('title') }}</template>
<p :class="$style.fetching" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p> <p :class="$style.fetching" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p>
<div :class="$style.stream" v-if="!fetching && images.length > 0"> <div :class="$style.stream" v-if="!fetching && images.length > 0">
<div v-for="image in images" :class="$style.img" :style="`background-image: url(${image.thumbnailUrl || image.url})`"></div> <div v-for="image in images"
:class="$style.img"
:style="`background-image: url(${image.thumbnailUrl || image.url})`"
draggable="true"
@dragstart="onDragstart(image, $event)"
@dragend="onDragend"
></div>
</div> </div>
<p :class="$style.empty" v-if="!fetching && images.length == 0">{{ $t('no-photos') }}</p> <p :class="$style.empty" v-if="!fetching && images.length == 0">{{ $t('no-photos') }}</p>
</mk-widget-container> </mk-widget-container>
@@ -31,6 +37,7 @@ export default define({
connection: null connection: null
}; };
}, },
mounted() { mounted() {
this.connection = this.$root.stream.useSharedConnection('main'); this.connection = this.$root.stream.useSharedConnection('main');
@@ -44,9 +51,11 @@ export default define({
this.fetching = false; this.fetching = false;
}); });
}, },
beforeDestroy() { beforeDestroy() {
this.connection.dispose(); this.connection.dispose();
}, },
methods: { methods: {
onDriveFileCreated(file) { onDriveFileCreated(file) {
if (/^image\/.+$/.test(file.type)) { if (/^image\/.+$/.test(file.type)) {
@@ -54,6 +63,7 @@ export default define({
if (this.images.length > 9) this.images.pop(); if (this.images.length > 9) this.images.pop();
} }
}, },
func() { func() {
if (this.props.design == 2) { if (this.props.design == 2) {
this.props.design = 0; this.props.design = 0;
@@ -62,7 +72,16 @@ export default define({
} }
this.save(); this.save();
} },
onDragstart(file, e) {
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('mk_drive_file', JSON.stringify(file));
},
onDragend(e) {
this.browser.isDragSource = false;
},
} }
}); });
</script> </script>

View File

@@ -5,7 +5,7 @@
<button slot="func" title="設定" @click="setting"><fa icon="cog"/></button> <button slot="func" title="設定" @click="setting"><fa icon="cog"/></button>
<div class="mkw-rss--body" :data-mobile="platform == 'mobile'"> <div class="mkw-rss--body" :data-mobile="platform == 'mobile'">
<p class="fetching" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p> <p class="fetching" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p>
<div class="feed" v-else> <div class="feed" v-else>
<a v-for="item in items" :href="item.link" target="_blank">{{ item.title }}</a> <a v-for="item in items" :href="item.link" target="_blank">{{ item.title }}</a>
</div> </div>

View File

@@ -2,7 +2,7 @@
<div class="memory"> <div class="memory">
<x-pie class="pie" :value="usage"/> <x-pie class="pie" :value="usage"/>
<div> <div>
<p><fa icon="flask"/>Memory</p> <p><fa icon="memory"/>Memory</p>
<p>Total: {{ total | bytes(1) }}</p> <p>Total: {{ total | bytes(1) }}</p>
<p>Used: {{ used | bytes(1) }}</p> <p>Used: {{ used | bytes(1) }}</p>
<p>Free: {{ free | bytes(1) }}</p> <p>Free: {{ free | bytes(1) }}</p>

View File

@@ -4,7 +4,7 @@
<template slot="header"><fa icon="server"/>{{ $t('title') }}</template> <template slot="header"><fa icon="server"/>{{ $t('title') }}</template>
<button slot="func" @click="toggle" :title="$t('toggle')"><fa icon="sort"/></button> <button slot="func" @click="toggle" :title="$t('toggle')"><fa icon="sort"/></button>
<p :class="$style.fetching" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p> <p :class="$style.fetching" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p>
<template v-if="!fetching"> <template v-if="!fetching">
<x-cpu-memory v-show="props.view == 0" :connection="connection"/> <x-cpu-memory v-show="props.view == 0" :connection="connection"/>
<x-cpu v-show="props.view == 1" :connection="connection" :meta="meta"/> <x-cpu v-show="props.view == 1" :connection="connection" :meta="meta"/>

View File

@@ -4,7 +4,7 @@
<template slot="header"><fa icon="chart-bar"/>{{ $t('title') }}</template> <template slot="header"><fa icon="chart-bar"/>{{ $t('title') }}</template>
<button slot="func" :title="$t('toggle')" @click="toggle"><fa icon="sort"/></button> <button slot="func" :title="$t('toggle')" @click="toggle"><fa icon="sort"/></button>
<p :class="$style.fetching" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p> <p :class="$style.fetching" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p>
<template v-else> <template v-else>
<x-calendar v-show="view == 0" :data="[].concat(activity)"/> <x-calendar v-show="view == 0" :data="[].concat(activity)"/>
<x-chart v-show="view == 1" :data="[].concat(activity)"/> <x-chart v-show="view == 1" :data="[].concat(activity)"/>

View File

@@ -0,0 +1,84 @@
<template>
<div class="gcafiosrssbtbnbzqupfmglvzgiaipyv">
<x-picker @chosen="chosen"/>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import contains from '../../../common/scripts/contains';
export default Vue.extend({
components: {
XPicker: () => import('../../../common/views/components/emoji-picker.vue').then(m => m.default)
},
props: {
x: {
type: Number,
required: true
},
y: {
type: Number,
required: true
}
},
mounted() {
this.$nextTick(() => {
const width = this.$el.offsetWidth;
const height = this.$el.offsetHeight;
let x = this.x;
let y = this.y;
if (x + width - window.pageXOffset > window.innerWidth) {
x = window.innerWidth - width + window.pageXOffset;
}
if (y + height - window.pageYOffset > window.innerHeight) {
y = window.innerHeight - height + window.pageYOffset;
}
this.$el.style.left = x + 'px';
this.$el.style.top = y + 'px';
Array.from(document.querySelectorAll('body *')).forEach(el => {
el.addEventListener('mousedown', this.onMousedown);
});
});
},
methods: {
onMousedown(e) {
e.preventDefault();
if (!contains(this.$el, e.target) && (this.$el != e.target)) this.close();
return false;
},
chosen(emoji) {
this.$emit('chosen', emoji);
this.close();
},
close() {
Array.from(document.querySelectorAll('body *')).forEach(el => {
el.removeEventListener('mousedown', this.onMousedown);
});
this.$emit('closed');
this.destroyDom();
}
}
});
</script>
<style lang="stylus" scoped>
.gcafiosrssbtbnbzqupfmglvzgiaipyv
position fixed
top 0
left 0
z-index 3000
box-shadow 0 2px 12px 0 rgba(0, 0, 0, 0.3)
</style>

View File

@@ -1,157 +0,0 @@
<template>
<button class="mk-follow-button"
:class="{ wait, active: u.isFollowing || u.hasPendingFollowRequestFromYou, big: size == 'big' }"
@click="onClick"
:disabled="wait"
>
<template v-if="!wait">
<template v-if="u.hasPendingFollowRequestFromYou && u.isLocked"><fa icon="hourglass-half"/><template v-if="size == 'big'"> {{ $t('request-pending') }}</template></template>
<template v-else-if="u.hasPendingFollowRequestFromYou && !u.isLocked"><fa icon="hourglass-start"/><template v-if="size == 'big'"> {{ $t('follow-processing') }}</template></template>
<template v-else-if="u.isFollowing"><fa icon="minus"/><template v-if="size == 'big'"> {{ $t('following') }}</template></template>
<template v-else-if="!u.isFollowing && u.isLocked"><fa icon="plus"/><template v-if="size == 'big'"> {{ $t('follow-request') }}</template></template>
<template v-else-if="!u.isFollowing && !u.isLocked"><fa icon="plus"/><template v-if="size == 'big'"> {{ $t('follow') }}</template></template>
</template>
<template v-else><fa icon="spinner .pulse" fixed-width/></template>
</button>
</template>
<script lang="ts">
import Vue from 'vue';
import i18n from '../../../i18n';
export default Vue.extend({
i18n: i18n('desktop/views/components/follow-button.vue'),
props: {
user: {
type: Object,
required: true
},
size: {
type: String,
default: 'compact'
}
},
data() {
return {
u: this.user,
wait: false,
connection: null
};
},
mounted() {
this.connection = this.$root.stream.useSharedConnection('main');
this.connection.on('follow', this.onFollowChange);
this.connection.on('unfollow', this.onFollowChange);
},
beforeDestroy() {
this.connection.dispose();
},
methods: {
onFollowChange(user) {
if (user.id == this.u.id) {
this.u.isFollowing = user.isFollowing;
this.u.hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou;
this.$forceUpdate();
}
},
async onClick() {
this.wait = true;
try {
if (this.u.isFollowing) {
this.u = await this.$root.api('following/delete', {
userId: this.u.id
});
} else {
if (this.u.hasPendingFollowRequestFromYou) {
this.u = await this.$root.api('following/requests/cancel', {
userId: this.u.id
});
} else if (this.u.isLocked) {
this.u = await this.$root.api('following/create', {
userId: this.u.id
});
} else {
this.u = await this.$root.api('following/create', {
userId: this.user.id
});
}
}
} catch (e) {
console.error(e);
} finally {
this.wait = false;
}
}
}
});
</script>
<style lang="stylus" scoped>
.mk-follow-button
display block
cursor pointer
padding 0
margin 0
width 32px
height 32px
font-size 1em
outline none
border-radius 4px
*
pointer-events none
&:focus
&:after
content ""
pointer-events none
position absolute
top -5px
right -5px
bottom -5px
left -5px
border 2px solid var(--primaryAlpha03)
border-radius 8px
&:not(.active)
color var(--primary)
border solid 1px var(--primary)
&:hover
background var(--primaryAlpha03)
&:active
background var(--primaryAlpha05)
&.active
color var(--primaryForeground)
background var(--primary)
border solid 1px var(--primary)
&:not(:disabled)
font-weight bold
&:hover:not(:disabled)
background var(--primaryLighten5)
border-color var(--primaryLighten5)
&:active:not(:disabled)
background var(--primaryDarken5)
border-color var(--primaryDarken5)
&.wait
cursor wait !important
opacity 0.7
&.big
width 100%
height 38px
line-height 38px
</style>

View File

@@ -11,7 +11,7 @@
</div> </div>
</div> </div>
<p class="empty" v-if="!fetching && users.length == 0">{{ $t('empty') }}</p> <p class="empty" v-if="!fetching && users.length == 0">{{ $t('empty') }}</p>
<p class="fetching" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>{{ $t('fetching') }}<mk-ellipsis/></p> <p class="fetching" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('fetching') }}<mk-ellipsis/></p>
<a class="refresh" @click="refresh">{{ $t('refresh') }}</a> <a class="refresh" @click="refresh">{{ $t('refresh') }}</a>
<button class="close" @click="destroyDom()" :title="$t('title')"><fa icon="times"/></button> <button class="close" @click="destroyDom()" :title="$t('title')"><fa icon="times"/></button>
</div> </div>

View File

@@ -14,10 +14,8 @@ import mediaVideo from './media-video.vue';
import notifications from './notifications.vue'; import notifications from './notifications.vue';
import noteForm from './post-form.vue'; import noteForm from './post-form.vue';
import renoteForm from './renote-form.vue'; import renoteForm from './renote-form.vue';
import followButton from './follow-button.vue';
import notePreview from './note-preview.vue'; import notePreview from './note-preview.vue';
import noteDetail from './note-detail.vue'; import noteDetail from './note-detail.vue';
import settings from './settings.vue';
import calendar from './calendar.vue'; import calendar from './calendar.vue';
import activity from './activity.vue'; import activity from './activity.vue';
import friendsMaker from './friends-maker.vue'; import friendsMaker from './friends-maker.vue';
@@ -39,10 +37,8 @@ Vue.component('mk-media-video', mediaVideo);
Vue.component('mk-notifications', notifications); Vue.component('mk-notifications', notifications);
Vue.component('mk-post-form', noteForm); Vue.component('mk-post-form', noteForm);
Vue.component('mk-renote-form', renoteForm); Vue.component('mk-renote-form', renoteForm);
Vue.component('mk-follow-button', followButton);
Vue.component('mk-note-preview', notePreview); Vue.component('mk-note-preview', notePreview);
Vue.component('mk-note-detail', noteDetail); Vue.component('mk-note-detail', noteDetail);
Vue.component('mk-settings', settings);
Vue.component('mk-calendar', calendar); Vue.component('mk-calendar', calendar);
Vue.component('mk-activity', activity); Vue.component('mk-activity', activity);
Vue.component('mk-friends-maker', friendsMaker); Vue.component('mk-friends-maker', friendsMaker);

View File

@@ -8,7 +8,7 @@
:disabled="conversationFetching" :disabled="conversationFetching"
> >
<template v-if="!conversationFetching"><fa icon="ellipsis-v"/></template> <template v-if="!conversationFetching"><fa icon="ellipsis-v"/></template>
<template v-if="conversationFetching"><fa icon="spinner .pulse"/></template> <template v-if="conversationFetching"><fa icon="spinner" pulse/></template>
</button> </button>
<div class="conversation"> <div class="conversation">
<x-sub v-for="note in conversation" :key="note.id" :note="note"/> <x-sub v-for="note in conversation" :key="note.id" :note="note"/>

View File

@@ -26,7 +26,7 @@
<footer v-if="more"> <footer v-if="more">
<button @click="loadMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }"> <button @click="loadMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }">
<template v-if="!moreFetching">{{ $t('@.load-more') }}</template> <template v-if="!moreFetching">{{ $t('@.load-more') }}</template>
<template v-if="moreFetching"><fa icon="spinner .pulse" fixed-width/></template> <template v-if="moreFetching"><fa icon="spinner" pulse fixed-width/></template>
</button> </button>
</footer> </footer>
</div> </div>

View File

@@ -105,7 +105,7 @@
</component> </component>
</div> </div>
<button class="more" :class="{ fetching: fetchingMoreNotifications }" v-if="moreNotifications" @click="fetchMoreNotifications" :disabled="fetchingMoreNotifications"> <button class="more" :class="{ fetching: fetchingMoreNotifications }" v-if="moreNotifications" @click="fetchMoreNotifications" :disabled="fetchingMoreNotifications">
<template v-if="fetchingMoreNotifications"><fa icon="spinner .pulse" fixed-width/></template>{{ fetchingMoreNotifications ? $t('@.loading') : $t('@.load-more') }} <template v-if="fetchingMoreNotifications"><fa icon="spinner" pulse fixed-width/></template>{{ fetchingMoreNotifications ? $t('@.loading') : $t('@.load-more') }}
</button> </button>
<p class="empty" v-if="notifications.length == 0 && !fetching">{{ $t('empty') }}</p> <p class="empty" v-if="notifications.length == 0 && !fetching">{{ $t('empty') }}</p>
</div> </div>

View File

@@ -15,11 +15,15 @@
<a v-for="tag in recentHashtags.slice(0, 5)" @click="addTag(tag)" :title="$t('click-to-tagging')">#{{ tag }}</a> <a v-for="tag in recentHashtags.slice(0, 5)" @click="addTag(tag)" :title="$t('click-to-tagging')">#{{ tag }}</a>
</div> </div>
<input v-show="useCw" v-model="cw" :placeholder="$t('annotations')"> <input v-show="useCw" v-model="cw" :placeholder="$t('annotations')">
<div class="textarea">
<textarea :class="{ with: (files.length != 0 || poll) }" <textarea :class="{ with: (files.length != 0 || poll) }"
ref="text" v-model="text" :disabled="posting" ref="text" v-model="text" :disabled="posting"
@keydown="onKeydown" @paste="onPaste" :placeholder="placeholder" @keydown="onKeydown" @paste="onPaste" :placeholder="placeholder"
v-autocomplete="'text'" v-autocomplete="'text'"
></textarea> ></textarea>
<button class="emoji" @click="emoji" ref="emoji">
<fa :icon="['far', 'laugh']"/>
</button>
<div class="files" :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 }"> <x-draggable :list="files" :options="{ animation: 150 }">
<div v-for="file in files" :key="file.id"> <div v-for="file in files" :key="file.id">
@@ -31,12 +35,13 @@
</div> </div>
<mk-poll-editor v-if="poll" ref="poll" @destroyed="poll = false" @updated="saveDraft()"/> <mk-poll-editor v-if="poll" ref="poll" @destroyed="poll = false" @updated="saveDraft()"/>
</div> </div>
</div>
<mk-uploader ref="uploader" @uploaded="attachMedia" @change="onChangeUploadings"/> <mk-uploader ref="uploader" @uploaded="attachMedia" @change="onChangeUploadings"/>
<button class="upload" :title="$t('attach-media-from-local')" @click="chooseFile"><fa icon="upload"/></button> <button class="upload" :title="$t('attach-media-from-local')" @click="chooseFile"><fa icon="upload"/></button>
<button class="drive" :title="$t('attach-media-from-drive')" @click="chooseFileFromDrive"><fa icon="cloud"/></button> <button class="drive" :title="$t('attach-media-from-drive')" @click="chooseFileFromDrive"><fa icon="cloud"/></button>
<button class="kao" :title="$t('insert-a-kao')" @click="kao"><fa :icon="['far', 'smile']"/></button> <button class="kao" :title="$t('insert-a-kao')" @click="kao"><fa :icon="['far', 'smile']"/></button>
<button class="poll" :title="$t('create-poll')" @click="poll = !poll"><fa icon="chart-pie"/></button> <button class="poll" :title="$t('create-poll')" @click="poll = !poll"><fa icon="chart-pie"/></button>
<button class="cw" :title="$t('hide-contents')" @click="useCw = !useCw"><fa icon="eye-slash"/></button> <button class="cw" :title="$t('hide-contents')" @click="useCw = !useCw"><fa :icon="['far', 'eye-slash']"/></button>
<button class="geo" :title="$t('attach-location-information')" @click="geo ? removeGeo() : setGeo()"><fa icon="map-marker-alt"/></button> <button class="geo" :title="$t('attach-location-information')" @click="geo ? removeGeo() : setGeo()"><fa icon="map-marker-alt"/></button>
<button class="visibility" :title="$t('visibility')" @click="setVisibility" ref="visibilityButton"> <button class="visibility" :title="$t('visibility')" @click="setVisibility" ref="visibilityButton">
<span v-if="visibility === 'public'"><fa icon="globe"/></span> <span v-if="visibility === 'public'"><fa icon="globe"/></span>
@@ -377,6 +382,19 @@ export default Vue.extend({
this.visibleUsers = erase(user, this.visibleUsers); this.visibleUsers = erase(user, this.visibleUsers);
}, },
async emoji() {
const Picker = await import('./emoji-picker-dialog.vue').then(m => m.default);
const button = this.$refs.emoji;
const rect = button.getBoundingClientRect();
const vm = this.$root.new(Picker, {
x: button.offsetWidth + rect.left + window.pageXOffset,
y: rect.top + window.pageYOffset
});
vm.$once('chosen', emoji => {
insertTextAtCursor(this.$refs.text, emoji);
});
},
post() { post() {
this.posting = true; this.posting = true;
@@ -469,7 +487,7 @@ export default Vue.extend({
> .content > .content
> input > input
> textarea > .textarea > textarea
display block display block
width 100% width 100%
padding 12px padding 12px
@@ -498,6 +516,24 @@ export default Vue.extend({
> input > input
margin-bottom 8px margin-bottom 8px
> .textarea
> .emoji
position absolute
top 0
right 0
padding 10px
font-size 18px
color var(--text)
opacity 0.5
&:hover
color var(--textHighlighted)
opacity 1
&:active
color var(--primary)
opacity 1
> textarea > textarea
margin 0 margin 0
max-width 100% max-width 100%
@@ -505,42 +541,24 @@ export default Vue.extend({
min-height 84px min-height 84px
&:hover &:hover
& + *
& + * + * & + * + *
& + * + * + *
border-color var(--primaryAlpha02) border-color var(--primaryAlpha02)
transition border-color .1s ease transition border-color .1s ease
&:focus &:focus
& + *
& + * + * & + * + *
& + * + * + *
border-color var(--primaryAlpha05) border-color var(--primaryAlpha05)
transition border-color 0s ease transition border-color 0s ease
& + .emoji
opacity 0.7
&.with &.with
border-bottom solid 1px var(--primaryAlpha01) !important border-bottom solid 1px var(--primaryAlpha01) !important
border-radius 4px 4px 0 0 border-radius 4px 4px 0 0
> .visibleUsers
margin-bottom 8px
font-size 14px
> span
margin-right 16px
color var(--primary)
> .hashtags
margin 0 0 8px 0
overflow hidden
white-space nowrap
font-size 14px
> b
color var(--primary)
> *
margin-right 8px
white-space nowrap
> .files > .files
margin 0 margin 0
padding 0 padding 0
@@ -601,6 +619,27 @@ export default Vue.extend({
border-radius 0 0 4px 4px border-radius 0 0 4px 4px
transition border-color .3s ease transition border-color .3s ease
> .visibleUsers
margin-bottom 8px
font-size 14px
> span
margin-right 16px
color var(--primary)
> .hashtags
margin 0 0 8px 0
overflow hidden
white-space nowrap
font-size 14px
> b
color var(--primary)
> *
margin-right 8px
white-space nowrap
> .mk-uploader > .mk-uploader
margin 8px 0 0 0 margin 8px 0 0 0
padding 8px padding 8px

View File

@@ -1,15 +1,21 @@
<template> <template>
<mk-window ref="window" is-modal width="700px" height="550px" @closed="destroyDom"> <mk-window ref="window" is-modal width="700px" height="550px" @closed="destroyDom">
<span slot="header" :class="$style.header"><fa icon="cog"/>{{ $t('settings') }}</span> <span slot="header" :class="$style.header"><fa icon="cog"/>{{ $t('settings') }}</span>
<mk-settings :initial-page="initialPage" @done="close"/> <x-settings :initial-page="initialPage" @done="close"/>
</mk-window> </mk-window>
</template> </template>
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import i18n from '../../../i18n'; import i18n from '../../../i18n';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('desktop/views/components/settings-window.vue'), i18n: i18n('desktop/views/components/settings-window.vue'),
components: {
XSettings: () => import('./settings.vue').then(m => m.default)
},
props: { props: {
initialPage: { initialPage: {
type: String, type: String,

View File

@@ -15,7 +15,7 @@
</div> </div>
<div class="pages"> <div class="pages">
<div class="profile" v-show="page == 'profile'"> <div class="profile" v-show="page == 'profile'">
<mk-profile-editor/> <x-profile-editor/>
<ui-card> <ui-card>
<div slot="title"><fa :icon="['fab', 'twitter']"/> {{ $t('twitter') }}</div> <div slot="title"><fa :icon="['fab', 'twitter']"/> {{ $t('twitter') }}</div>
@@ -36,7 +36,7 @@
<div slot="title"><fa icon="palette"/> {{ $t('theme') }}</div> <div slot="title"><fa icon="palette"/> {{ $t('theme') }}</div>
<section> <section>
<mk-theme/> <x-theme/>
</section> </section>
</ui-card> </ui-card>
@@ -194,7 +194,7 @@
</ui-card> </ui-card>
<div class="drive" v-if="page == 'drive'"> <div class="drive" v-if="page == 'drive'">
<mk-drive-settings/> <x-drive-settings/>
</div> </div>
<ui-card class="hashtags" v-show="page == 'hashtags'"> <ui-card class="hashtags" v-show="page == 'hashtags'">
@@ -205,7 +205,7 @@
</ui-card> </ui-card>
<div class="muteAndBlock" v-show="page == 'muteAndBlock'"> <div class="muteAndBlock" v-show="page == 'muteAndBlock'">
<mk-mute-and-block/> <x-mute-and-block/>
</div> </div>
<ui-card class="apps" v-show="page == 'apps'"> <ui-card class="apps" v-show="page == 'apps'">
@@ -218,7 +218,7 @@
<ui-card class="password" v-show="page == 'security'"> <ui-card class="password" v-show="page == 'security'">
<div slot="title"><fa icon="unlock-alt"/> {{ $t('password') }}</div> <div slot="title"><fa icon="unlock-alt"/> {{ $t('password') }}</div>
<section> <section>
<mk-password-settings/> <x-password-settings/>
</section> </section>
</ui-card> </ui-card>
@@ -237,7 +237,7 @@
</ui-card> </ui-card>
<div class="api" v-show="page == 'api'"> <div class="api" v-show="page == 'api'">
<mk-api-settings/> <x-api-settings/>
</div> </div>
<ui-card class="other" v-show="page == 'other'"> <ui-card class="other" v-show="page == 'other'">
@@ -301,7 +301,13 @@ export default Vue.extend({
X2fa, X2fa,
XApps, XApps,
XSignins, XSignins,
XTags XTags,
XTheme: () => import('../../../common/views/components/theme.vue').then(m => m.default),
XDriveSettings: () => import('../../../common/views/components/drive-settings.vue').then(m => m.default),
XMuteAndBlock: () => import('../../../common/views/components/mute-and-block.vue').then(m => m.default),
XPasswordSettings: () => import('../../../common/views/components/password-settings.vue').then(m => m.default),
XProfileEditor: () => import('../../../common/views/components/profile-editor.vue').then(m => m.default),
XApiSettings: () => import('../../../common/views/components/api-settings.vue').then(m => m.default),
}, },
props: { props: {
initialPage: { initialPage: {

View File

@@ -2,7 +2,7 @@
<div class="zvdbznxvfixtmujpsigoccczftvpiwqh"> <div class="zvdbznxvfixtmujpsigoccczftvpiwqh">
<div class="banner" :style="bannerStyle"></div> <div class="banner" :style="bannerStyle"></div>
<mk-avatar class="avatar" :user="user" :disable-preview="true"/> <mk-avatar class="avatar" :user="user" :disable-preview="true"/>
<mk-follow-button :user="user" class="follow"/> <mk-follow-button :user="user" class="follow" mini/>
<div class="body"> <div class="body">
<router-link :to="user | userPage" class="name">{{ user | userName }}</router-link> <router-link :to="user | userPage" class="name">{{ user | userName }}</router-link>
<span class="username">@{{ user | acct }}</span> <span class="username">@{{ user | acct }}</span>

View File

@@ -19,7 +19,7 @@
<p>{{ $t('followers') }}</p><span>{{ u.followersCount }}</span> <p>{{ $t('followers') }}</p><span>{{ u.followersCount }}</span>
</div> </div>
</div> </div>
<mk-follow-button v-if="$store.getters.isSignedIn && u.id != $store.state.i.id" :user="u"/> <mk-follow-button class="follow-button" v-if="$store.getters.isSignedIn && u.id != $store.state.i.id" :user="u" mini/>
</template> </template>
</div> </div>
</template> </template>
@@ -154,7 +154,7 @@ export default Vue.extend({
font-size 1em font-size 1em
color var(--primary) color var(--primary)
> .mk-follow-button > .follow-button
position absolute position absolute
top 92px top 92px
right 8px right 8px

View File

@@ -31,7 +31,7 @@
<footer v-if="more"> <footer v-if="more">
<button @click="loadMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }"> <button @click="loadMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }">
<template v-if="!moreFetching">{{ $t('@.load-more') }}</template> <template v-if="!moreFetching">{{ $t('@.load-more') }}</template>
<template v-if="moreFetching"><fa icon="spinner .pulse" fixed-width/></template> <template v-if="moreFetching"><fa icon="spinner" pulse fixed-width/></template>
</button> </button>
</footer> </footer>
</div> </div>

View File

@@ -17,7 +17,7 @@
</template> </template>
</component> </component>
<button class="more" :class="{ fetching: fetchingMoreNotifications }" v-if="moreNotifications" @click="fetchMoreNotifications" :disabled="fetchingMoreNotifications"> <button class="more" :class="{ fetching: fetchingMoreNotifications }" v-if="moreNotifications" @click="fetchMoreNotifications" :disabled="fetchingMoreNotifications">
<template v-if="fetchingMoreNotifications"><fa icon="spinner .pulse" fixed-width/></template>{{ fetchingMoreNotifications ? this.$t('@.loading') : this.$t('@.load-more') }} <template v-if="fetchingMoreNotifications"><fa icon="spinner" pulse fixed-width/></template>{{ fetchingMoreNotifications ? this.$t('@.loading') : this.$t('@.load-more') }}
</button> </button>
<p class="empty" v-if="notifications.length == 0 && !fetching">{{ $t('empty') }}</p> <p class="empty" v-if="notifications.length == 0 && !fetching">{{ $t('empty') }}</p>
</div> </div>

View File

@@ -14,7 +14,7 @@
<header :style="bannerStyle"> <header :style="bannerStyle">
<div> <div>
<button class="menu" @click="menu" ref="menu"><fa icon="ellipsis-h"/></button> <button class="menu" @click="menu" ref="menu"><fa icon="ellipsis-h"/></button>
<mk-follow-button v-if="$store.getters.isSignedIn && user.id != $store.state.i.id" :user="user" class="follow"/> <mk-follow-button v-if="$store.getters.isSignedIn && user.id != $store.state.i.id" :user="user" class="follow" mini/>
<mk-avatar class="avatar" :user="user" :disable-preview="true"/> <mk-avatar class="avatar" :user="user" :disable-preview="true"/>
<span class="name">{{ user | userName }}</span> <span class="name">{{ user | userName }}</span>
<span class="acct">@{{ user | acct }}</span> <span class="acct">@{{ user | acct }}</span>
@@ -155,7 +155,8 @@ export default Vue.extend({
this.$root.api('users/notes', { this.$root.api('users/notes', {
userId: this.user.id, userId: this.user.id,
fileType: image, fileType: image,
limit: 9 limit: 9,
untilDate: new Date().getTime() + 1000 * 86400 * 365
}).then(notes => { }).then(notes => {
notes.forEach(note => { notes.forEach(note => {
note.files.forEach(file => { note.files.forEach(file => {
@@ -254,6 +255,7 @@ export default Vue.extend({
this.$root.api('users/notes', { this.$root.api('users/notes', {
userId: this.user.id, userId: this.user.id,
limit: fetchLimit + 1, limit: fetchLimit + 1,
untilDate: new Date().getTime() + 1000 * 86400 * 365,
withFiles: this.withFiles, withFiles: this.withFiles,
includeMyRenotes: this.$store.state.settings.showMyRenotes, includeMyRenotes: this.$store.state.settings.showMyRenotes,
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes, includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
@@ -274,7 +276,7 @@ export default Vue.extend({
const promise = this.$root.api('users/notes', { const promise = this.$root.api('users/notes', {
userId: this.user.id, userId: this.user.id,
limit: fetchLimit + 1, limit: fetchLimit + 1,
untilId: (this.$refs.timeline as any).tail().id, untilDate: new Date((this.$refs.timeline as any).tail().createdAt).getTime(),
withFiles: this.withFiles, withFiles: this.withFiles,
includeMyRenotes: this.$store.state.settings.showMyRenotes, includeMyRenotes: this.$store.state.settings.showMyRenotes,
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes, includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,

View File

@@ -1,7 +1,7 @@
<template> <template>
<div class="vahgrswmbzfdlmomxnqftuueyvwaafth"> <div class="vahgrswmbzfdlmomxnqftuueyvwaafth">
<p class="title"><fa icon="users"/>{{ $t('title') }}</p> <p class="title"><fa icon="users"/>{{ $t('title') }}</p>
<p class="initializing" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>{{ $t('loading') }}<mk-ellipsis/></p> <p class="initializing" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('loading') }}<mk-ellipsis/></p>
<div v-if="!fetching && users.length > 0"> <div v-if="!fetching && users.length > 0">
<router-link v-for="user in users" :to="user | userPage" :key="user.id"> <router-link v-for="user in users" :to="user | userPage" :key="user.id">
<img :src="user.avatarUrl" :alt="user | userName" v-user-preview="user.id"/> <img :src="user.avatarUrl" :alt="user | userName" v-user-preview="user.id"/>

View File

@@ -1,7 +1,7 @@
<template> <template>
<div class="hozptpaliadatkehcmcayizwzwwctpbc"> <div class="hozptpaliadatkehcmcayizwzwwctpbc">
<p class="title"><fa icon="users"/>{{ $t('title') }}</p> <p class="title"><fa icon="users"/>{{ $t('title') }}</p>
<p class="initializing" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>{{ $t('loading') }}<mk-ellipsis/></p> <p class="initializing" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('loading') }}<mk-ellipsis/></p>
<template v-if="!fetching && users.length != 0"> <template v-if="!fetching && users.length != 0">
<div class="user" v-for="friend in users"> <div class="user" v-for="friend in users">
<mk-avatar class="avatar" :user="friend"/> <mk-avatar class="avatar" :user="friend"/>
@@ -9,7 +9,7 @@
<router-link class="name" :to="friend | userPage" v-user-preview="friend.id">{{ friend.name }}</router-link> <router-link class="name" :to="friend | userPage" v-user-preview="friend.id">{{ friend.name }}</router-link>
<p class="username">@{{ friend | acct }}</p> <p class="username">@{{ friend | acct }}</p>
</div> </div>
<mk-follow-button :user="friend"/> <mk-follow-button class="follow-button" :user="friend"/>
</div> </div>
</template> </template>
<p class="empty" v-if="!fetching && users.length == 0">{{ $t('no-users') }}</p> <p class="empty" v-if="!fetching && users.length == 0">{{ $t('no-users') }}</p>
@@ -110,7 +110,7 @@ export default Vue.extend({
color var(--text) color var(--text)
opacity 0.7 opacity 0.7
> .mk-follow-button > .follow-button
position absolute position absolute
top 16px top 16px
right 16px right 16px

View File

@@ -1,7 +1,7 @@
<template> <template>
<div class="dzsuvbsrrrwobdxifudxuefculdfiaxd"> <div class="dzsuvbsrrrwobdxifudxuefculdfiaxd">
<p class="title"><fa icon="camera"/>{{ $t('title') }}</p> <p class="title"><fa icon="camera"/>{{ $t('title') }}</p>
<p class="initializing" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>{{ $t('loading') }}<mk-ellipsis/></p> <p class="initializing" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('loading') }}<mk-ellipsis/></p>
<div class="stream" v-if="!fetching && images.length > 0"> <div class="stream" v-if="!fetching && images.length > 0">
<div v-for="image in images" class="img" <div v-for="image in images" class="img"
:style="`background-image: url(${image.thumbnailUrl})`" :style="`background-image: url(${image.thumbnailUrl})`"
@@ -27,7 +27,8 @@ export default Vue.extend({
this.$root.api('users/notes', { this.$root.api('users/notes', {
userId: this.user.id, userId: this.user.id,
withFiles: true, withFiles: true,
limit: 9 limit: 9,
untilDate: new Date().getTime() + 1000 * 86400 * 365
}).then(notes => { }).then(notes => {
notes.forEach(note => { notes.forEach(note => {
note.files.forEach(file => { note.files.forEach(file => {

View File

@@ -1,7 +1,7 @@
<template> <template>
<div class="profile" v-if="$store.getters.isSignedIn"> <div class="profile" v-if="$store.getters.isSignedIn">
<div class="friend-form" v-if="$store.state.i.id != user.id"> <div class="friend-form" v-if="$store.state.i.id != user.id">
<mk-follow-button :user="user" size="big"/> <mk-follow-button :user="user" block/>
<p class="followed" v-if="user.isFollowed">{{ $t('follows-you') }}</p> <p class="followed" v-if="user.isFollowed">{{ $t('follows-you') }}</p>
<p class="stalk" v-if="user.isFollowing"> <p class="stalk" v-if="user.isFollowing">
<span v-if="user.isStalking">{{ $t('stalking') }} <a @click="unstalk"><fa icon="meh"/> {{ $t('unstalk') }}</a></span> <span v-if="user.isStalking">{{ $t('stalking') }} <a @click="unstalk"><fa icon="meh"/> {{ $t('unstalk') }}</a></span>
@@ -11,7 +11,7 @@
<div class="action-form"> <div class="action-form">
<ui-button @click="user.isMuted ? unmute() : mute()" v-if="$store.state.i.id != user.id"> <ui-button @click="user.isMuted ? unmute() : mute()" v-if="$store.state.i.id != user.id">
<span v-if="user.isMuted"><fa icon="eye"/> {{ $t('unmute') }}</span> <span v-if="user.isMuted"><fa icon="eye"/> {{ $t('unmute') }}</span>
<span v-else><fa icon="eye-slash"/> {{ $t('mute') }}</span> <span v-else><fa :icon="['far', 'eye-slash']"/> {{ $t('mute') }}</span>
</ui-button> </ui-button>
<ui-button @click="user.isBlocking ? unblock() : block()" v-if="$store.state.i.id != user.id"> <ui-button @click="user.isBlocking ? unblock() : block()" v-if="$store.state.i.id != user.id">
<span v-if="user.isBlocking"><fa icon="user"/> {{ $t('unblock') }}</span> <span v-if="user.isBlocking"><fa icon="user"/> {{ $t('unblock') }}</span>

View File

@@ -3,7 +3,7 @@
<header> <header>
<span :data-active="mode == 'default'" @click="mode = 'default'"><fa :icon="['far', 'comment-alt']"/> {{ $t('default') }}</span> <span :data-active="mode == 'default'" @click="mode = 'default'"><fa :icon="['far', 'comment-alt']"/> {{ $t('default') }}</span>
<span :data-active="mode == 'with-replies'" @click="mode = 'with-replies'"><fa icon="comments"/> {{ $t('with-replies') }}</span> <span :data-active="mode == 'with-replies'" @click="mode = 'with-replies'"><fa icon="comments"/> {{ $t('with-replies') }}</span>
<span :data-active="mode == 'with-media'" @click="mode = 'with-media'"><fa icon="images"/> {{ $t('with-media') }}</span> <span :data-active="mode == 'with-media'" @click="mode = 'with-media'"><fa :icon="['far', 'images']"/> {{ $t('with-media') }}</span>
</header> </header>
<mk-notes ref="timeline" :more="existMore ? more : null"> <mk-notes ref="timeline" :more="existMore ? more : null">
<p class="empty" slot="empty"><fa :icon="['far', 'comments']"/>{{ $t('empty') }}</p> <p class="empty" slot="empty"><fa :icon="['far', 'comments']"/>{{ $t('empty') }}</p>
@@ -63,7 +63,7 @@ export default Vue.extend({
this.$root.api('users/notes', { this.$root.api('users/notes', {
userId: this.user.id, userId: this.user.id,
limit: fetchLimit + 1, limit: fetchLimit + 1,
untilDate: this.date ? this.date.getTime() : undefined, untilDate: this.date ? this.date.getTime() : new Date().getTime() + 1000 * 86400 * 365,
includeReplies: this.mode == 'with-replies', includeReplies: this.mode == 'with-replies',
withFiles: this.mode == 'with-media' withFiles: this.mode == 'with-media'
}).then(notes => { }).then(notes => {
@@ -86,7 +86,7 @@ export default Vue.extend({
limit: fetchLimit + 1, limit: fetchLimit + 1,
includeReplies: this.mode == 'with-replies', includeReplies: this.mode == 'with-replies',
withFiles: this.mode == 'with-media', withFiles: this.mode == 'with-media',
untilId: (this.$refs.timeline as any).tail().id untilDate: new Date((this.$refs.timeline as any).tail().createdAt).getTime()
}); });
promise.then(notes => { promise.then(notes => {

View File

@@ -50,7 +50,7 @@
</div> </div>
<div class="photos block"> <div class="photos block">
<header><fa icon="images"/> {{ $t('photos') }}</header> <header><fa :icon="['far', 'images']"/> {{ $t('photos') }}</header>
<div> <div>
<div v-for="photo in photos" :style="`background-image: url(${photo.thumbnailUrl})`"></div> <div v-for="photo in photos" :style="`background-image: url(${photo.thumbnailUrl})`"></div>
</div> </div>

View File

@@ -11,7 +11,7 @@
<mk-poll :note="poll"/> <mk-poll :note="poll"/>
</div> </div>
<p class="empty" v-if="!fetching && poll == null">{{ $t('nothing') }}</p> <p class="empty" v-if="!fetching && poll == null">{{ $t('nothing') }}</p>
<p class="fetching" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p> <p class="fetching" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p>
</div> </div>
</mk-widget-container> </mk-widget-container>
</div> </div>

View File

@@ -1,16 +1,51 @@
<template> <template>
<div class="mkw-post-form"> <div>
<template v-if="props.design == 0"> <mk-widget-container :show-header="props.design == 0">
<p class="title"><fa icon="pencil-alt"/>{{ $t('title') }}</p> <template slot="header"><fa icon="pencil-alt"/>{{ $t('title') }}</template>
</template>
<textarea :disabled="posting" v-model="text" @keydown="onKeydown" :placeholder="placeholder"></textarea> <div class="lhcuptdmcdkfwmipgazeawoiuxpzaclc-body"
<button @click="post" :disabled="posting">{{ $t('note') }}</button> @dragover.stop="onDragover"
@drop.stop="onDrop"
>
<div class="textarea">
<textarea
:disabled="posting"
v-model="text"
@keydown="onKeydown"
@paste="onPaste"
:placeholder="placeholder"
ref="text"
v-autocomplete="'text'"
></textarea>
<button class="emoji" @click="emoji" ref="emoji">
<fa :icon="['far', 'laugh']"/>
</button>
</div>
<div class="files" 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>
<img class="remove" @click="detachMedia(file.id)" src="/assets/desktop/remove.png" :title="$t('attach-cancel')" alt=""/>
</div>
</x-draggable>
</div>
<input ref="file" type="file" multiple="multiple" tabindex="-1" @change="onChangeFile"/>
<mk-uploader ref="uploader" @uploaded="attachMedia"/>
<footer>
<button @click="chooseFile"><fa icon="upload"/></button>
<button @click="chooseFileFromDrive"><fa icon="cloud"/></button>
<button @click="post" :disabled="posting" class="post">{{ $t('note') }}</button>
</footer>
</div>
</mk-widget-container>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import define from '../../../common/define-widget'; import define from '../../../common/define-widget';
import i18n from '../../../i18n'; import i18n from '../../../i18n';
import insertTextAtCursor from 'insert-text-at-cursor';
import * as XDraggable from 'vuedraggable';
export default define({ export default define({
name: 'post-form', name: 'post-form',
@@ -19,12 +54,19 @@ export default define({
}) })
}).extend({ }).extend({
i18n: i18n('desktop/views/widgets/post-form.vue'), i18n: i18n('desktop/views/widgets/post-form.vue'),
components: {
XDraggable
},
data() { data() {
return { return {
posting: false, posting: false,
text: '' text: '',
files: [],
}; };
}, },
computed: { computed: {
placeholder(): string { placeholder(): string {
const xs = [ const xs = [
@@ -38,6 +80,7 @@ export default define({
return xs[Math.floor(Math.random() * xs.length)]; return xs[Math.floor(Math.random() * xs.length)];
} }
}, },
methods: { methods: {
func() { func() {
if (this.props.design == 1) { if (this.props.design == 1) {
@@ -47,14 +90,95 @@ export default define({
} }
this.save(); this.save();
}, },
chooseFile() {
(this.$refs.file as any).click();
},
chooseFileFromDrive() {
this.$chooseDriveFile({
multiple: true
}).then(files => {
files.forEach(this.attachMedia);
});
},
attachMedia(driveFile) {
this.files.push(driveFile);
this.$emit('change-attached-files', this.files);
},
detachMedia(id) {
this.files = this.files.filter(x => x.id != id);
this.$emit('change-attached-files', this.files);
},
onKeydown(e) { onKeydown(e) {
if ((e.which == 10 || e.which == 13) && (e.ctrlKey || e.metaKey) && !this.posting && this.text) this.post(); if ((e.which == 10 || e.which == 13) && (e.ctrlKey || e.metaKey) && !this.posting && this.text) this.post();
}, },
onPaste(e) {
Array.from(e.clipboardData.items).forEach((item: any) => {
if (item.kind == 'file') {
this.upload(item.getAsFile());
}
});
},
onChangeFile() {
Array.from((this.$refs.file as any).files).forEach(this.upload);
},
upload(file) {
(this.$refs.uploader as any).upload(file);
},
onDragover(e) {
const isFile = e.dataTransfer.items[0].kind == 'file';
const isDriveFile = e.dataTransfer.types[0] == 'mk_drive_file';
if (isFile || isDriveFile) {
e.preventDefault();
e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move';
}
},
onDrop(e): void {
// ファイルだったら
if (e.dataTransfer.files.length > 0) {
e.preventDefault();
Array.from(e.dataTransfer.files).forEach(this.upload);
return;
}
//#region ドライブのファイル
const driveFile = e.dataTransfer.getData('mk_drive_file');
if (driveFile != null && driveFile != '') {
const file = JSON.parse(driveFile);
this.files.push(file);
e.preventDefault();
}
//#endregion
},
async emoji() {
const Picker = await import('../components/emoji-picker-dialog.vue').then(m => m.default);
const button = this.$refs.emoji;
const rect = button.getBoundingClientRect();
const vm = this.$root.new(Picker, {
x: button.offsetWidth + rect.left + window.pageXOffset,
y: rect.top + window.pageYOffset
});
vm.$once('chosen', emoji => {
insertTextAtCursor(this.$refs.text, emoji);
});
},
post() { post() {
this.posting = true; this.posting = true;
this.$root.api('notes/create', { this.$root.api('notes/create', {
text: this.text text: this.text == '' ? undefined : this.text,
fileIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined,
}).then(data => { }).then(data => {
this.clear(); this.clear();
}).catch(err => { }).catch(err => {
@@ -63,34 +187,34 @@ export default define({
this.posting = false; this.posting = false;
}); });
}, },
clear() { clear() {
this.text = ''; this.text = '';
this.files = [];
} }
} }
}); });
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
.lhcuptdmcdkfwmipgazeawoiuxpzaclc-body
> .textarea
> .emoji
position absolute
top 0
right 0
padding 10px
font-size 18px
color var(--text)
opacity 0.5
&:hover
color var(--textHighlighted)
opacity 1
.mkw-post-form &:active
background #fff color var(--primary)
overflow hidden opacity 1
border solid 1px rgba(#000, 0.075)
border-radius 6px
> .title
z-index 1
margin 0
padding 0 16px
line-height 42px
font-size 0.9em
font-weight bold
color #888
box-shadow 0 1px rgba(#000, 0.07)
> [data-icon]
margin-right 4px
> textarea > textarea
display block display block
@@ -98,16 +222,64 @@ export default define({
max-width 100% max-width 100%
min-width 100% min-width 100%
padding 16px padding 16px
margin-bottom 28px + 16px color var(--desktopPostFormTextareaFg)
outline none
background var(--desktopPostFormTextareaBg)
border none border none
border-bottom solid 1px #eee border-bottom solid 1px var(--faceDivider)
> button &:focus
& + .emoji
opacity 0.7
> .files
> div
padding 4px
&:after
content ""
display block display block
clear both
> div
float left
border solid 4px transparent
cursor move
&:hover > .remove
display block
> .img
width 64px
height 64px
background-size cover
background-position center center
> .remove
display none
position absolute position absolute
bottom 8px top -6px
right 8px right -6px
margin 0 width 16px
height 16px
cursor pointer
> input[type=file]
display none
> footer
display flex
padding 8px
> button:not(.post)
color var(--text)
&:hover
color var(--textHighlighted)
> .post
display block
margin 0 0 0 auto
padding 0 10px padding 0 10px
height 28px height 28px
color var(--primaryForeground) color var(--primaryForeground)

View File

@@ -5,7 +5,7 @@
<button slot="func" :title="$t('title')" @click="fetch"><fa icon="sync"/></button> <button slot="func" :title="$t('title')" @click="fetch"><fa icon="sync"/></button>
<div class="mkw-trends--body"> <div class="mkw-trends--body">
<p class="fetching" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p> <p class="fetching" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p>
<div class="note" v-else-if="note != null"> <div class="note" v-else-if="note != null">
<p class="text"><router-link :to="note | notePage">{{ note.text }}</router-link></p> <p class="text"><router-link :to="note | notePage">{{ note.text }}</router-link></p>
<p class="author"><router-link :to="note.user | userPage">@{{ note.user | acct }}</router-link></p> <p class="author"><router-link :to="note.user | userPage">@{{ note.user | acct }}</router-link></p>

View File

@@ -5,7 +5,7 @@
<button slot="func" :title="$t('title')" @click="refresh"><fa icon="sync"/></button> <button slot="func" :title="$t('title')" @click="refresh"><fa icon="sync"/></button>
<div class="mkw-users--body"> <div class="mkw-users--body">
<p class="fetching" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p> <p class="fetching" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p>
<template v-else-if="users.length != 0"> <template v-else-if="users.length != 0">
<div class="user" v-for="_user in users"> <div class="user" v-for="_user in users">
<mk-avatar class="avatar" :user="_user"/> <mk-avatar class="avatar" :user="_user"/>
@@ -114,11 +114,6 @@ export default define({
color var(--text) color var(--text)
opacity 0.7 opacity 0.7
> .mk-follow-button
position absolute
top 16px
right 16px
> .empty > .empty
margin 0 margin 0
padding 16px padding 16px

View File

@@ -25,47 +25,120 @@ if (localStorage.getItem('theme') == null) {
import { library } from '@fortawesome/fontawesome-svg-core'; import { library } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
/* なぜか動かない import {
import faRetweet from '@fortawesome/free-solid-svg-icons/faRetweet'; faRetweet,
import faPlus from '@fortawesome/free-solid-svg-icons/faPlus'; faPlus,
import faUser from '@fortawesome/free-solid-svg-icons/faUser'; faUser,
import faCog from '@fortawesome/free-solid-svg-icons/faCog'; faCog,
import faCheck from '@fortawesome/free-solid-svg-icons/faCheck'; faCheck,
import faStar from '@fortawesome/free-solid-svg-icons/faStar'; faStar,
import faReply from '@fortawesome/free-solid-svg-icons/faReply'; faReply,
import faEllipsisH from '@fortawesome/free-solid-svg-icons/faEllipsisH'; faEllipsisH,
import faQuoteLeft from '@fortawesome/free-solid-svg-icons/faQuoteLeft'; faQuoteLeft,
import faQuoteRight from '@fortawesome/free-solid-svg-icons/faQuoteRight'; faQuoteRight,
import faAngleUp from '@fortawesome/free-solid-svg-icons/faAngleUp'; faAngleUp,
import faAngleDown from '@fortawesome/free-solid-svg-icons/faAngleDown'; faAngleDown,
import faAt from '@fortawesome/free-solid-svg-icons/faAt'; faAt,
import faHashtag from '@fortawesome/free-solid-svg-icons/faHashtag'; faHashtag,
import faHome from '@fortawesome/free-solid-svg-icons/faHome'; faHome,
import faGlobe from '@fortawesome/free-solid-svg-icons/faGlobe'; faGlobe,
import faCircle from '@fortawesome/free-solid-svg-icons/faCircle'; faCircle,
import faList from '@fortawesome/free-solid-svg-icons/faList'; faList,
import faHeart from '@fortawesome/free-solid-svg-icons/faHeart'; faHeart,
import faUnlock from '@fortawesome/free-solid-svg-icons/faUnlock'; faUnlock,
import faRssSquare from '@fortawesome/free-solid-svg-icons/faRssSquare'; faRssSquare,
import faSort from '@fortawesome/free-solid-svg-icons/faSort'; faSort,
import faChartPie from '@fortawesome/free-solid-svg-icons/faChartPie'; faChartPie,
import faChartBar from '@fortawesome/free-solid-svg-icons/faChartBar'; faChartBar,
import faPencilAlt from '@fortawesome/free-solid-svg-icons/faPencilAlt'; faPencilAlt,
import faColumns from '@fortawesome/free-solid-svg-icons/faColumns'; faColumns,
import faComments from '@fortawesome/free-solid-svg-icons/faComments'; faComments,
import faGamepad from '@fortawesome/free-solid-svg-icons/faGamepad'; faGamepad,
import faCloud from '@fortawesome/free-solid-svg-icons/faCloud'; faCloud,
import faPowerOff from '@fortawesome/free-solid-svg-icons/faPowerOff'; faPowerOff,
import faChevronCircleLeft from '@fortawesome/free-solid-svg-icons/faChevronCircleLeft'; faChevronCircleLeft,
import faChevronCircleRight from '@fortawesome/free-solid-svg-icons/faChevronCircleRight'; faChevronCircleRight,
import faShareAlt from '@fortawesome/free-solid-svg-icons/faShareAlt'; faShareAlt,
import faTimes from '@fortawesome/free-solid-svg-icons/faTimes'; faTimes,
import faThumbtack from '@fortawesome/free-solid-svg-icons/faThumbtack'; faThumbtack,
import faSearch from '@fortawesome/free-solid-svg-icons/faSearch'; faSearch,
faAngleRight,
faWrench,
faTerminal,
faMoon,
faPalette,
faSlidersH,
faDesktop,
faVolumeUp,
faLanguage,
faInfoCircle,
faExclamationTriangle,
faKey,
faBan,
faCogs,
faUnlockAlt,
faPuzzlePiece,
faMobileAlt,
faSignInAlt,
faSyncAlt,
faPaperPlane,
faUpload,
faMapMarkerAlt,
faEnvelope,
faLock,
faFolderOpen,
faBirthdayCake,
faImage,
faEye,
faDownload,
faFileImport,
faLink,
faArrowRight,
faICursor,
faCaretRight,
faReplyAll,
faCamera,
faMinus,
faCaretDown,
faCalculator,
faUsers,
faBars,
faFileImage,
faPollH,
faFolder,
faMicrochip,
faMemory,
faServer,
faExclamationCircle,
faSpinner,
faBroadcastTower
} from '@fortawesome/free-solid-svg-icons';
import farBell from '@fortawesome/free-regular-svg-icons/faBell'; import {
import farEnvelope from '@fortawesome/free-regular-svg-icons/faEnvelope'; faBell as farBell,
import farComments from '@fortawesome/free-regular-svg-icons/faComments'; faEnvelope as farEnvelope,
faComments as farComments,
faTrashAlt as farTrashAlt,
faWindowRestore as farWindowRestore,
faFolder as farFolder,
faLaugh as farLaugh,
faSmile as farSmile,
faEyeSlash as farEyeSlash,
faFolderOpen as farFolderOpen,
faSave as farSave,
faImages as farImages,
faChartBar as farChartBar,
faCommentAlt as farCommentAlt,
faClock as farClock,
faCalendarAlt as farCalendarAlt,
faHdd as farHdd,
} from '@fortawesome/free-regular-svg-icons';
import {
faTwitter as fabTwitter,
faGithub as fabGithub,
} from '@fortawesome/free-brands-svg-icons';
import i18n from './i18n';
library.add( library.add(
faRetweet, faRetweet,
@@ -104,16 +177,78 @@ library.add(
faTimes, faTimes,
faThumbtack, faThumbtack,
faSearch, faSearch,
faAngleRight,
faWrench,
faTerminal,
faMoon,
faPalette,
faSlidersH,
faDesktop,
faVolumeUp,
faLanguage,
faInfoCircle,
faExclamationTriangle,
faKey,
faBan,
faCogs,
faUnlockAlt,
faPuzzlePiece,
faMobileAlt,
faSignInAlt,
faSyncAlt,
faPaperPlane,
faUpload,
faMapMarkerAlt,
faEnvelope,
faLock,
faFolderOpen,
faBirthdayCake,
faImage,
faEye,
faDownload,
faFileImport,
faLink,
faArrowRight,
faICursor,
faCaretRight,
faReplyAll,
faCamera,
faMinus,
faCaretDown,
faCalculator,
faUsers,
faBars,
faFileImage,
faPollH,
faFolder,
faMicrochip,
faMemory,
faServer,
faExclamationCircle,
faSpinner,
faBroadcastTower,
farBell, farBell,
farEnvelope, farEnvelope,
farComments, farComments,
farTrashAlt,
farWindowRestore,
farFolder,
farLaugh,
farSmile,
farEyeSlash,
farFolderOpen,
farSave,
farImages,
farChartBar,
farCommentAlt,
farClock,
farCalendarAlt,
farHdd,
fabTwitter,
fabGithub
); );
*/
import { fas } from '@fortawesome/free-solid-svg-icons';
import { far } from '@fortawesome/free-regular-svg-icons';
library.add(fas, far);
//#endregion //#endregion
Vue.use(Vuex); Vue.use(Vuex);
@@ -269,13 +404,7 @@ export default (callback: (launch: (router: VueRouter) => [Vue, MiOS]) => void,
}, { passive: true }); }, { passive: true });
const app = new Vue({ const app = new Vue({
i18n: new VueI18n({ i18n: i18n(),
sync: false,
locale: lang,
messages: {
[lang]: {}
}
}),
store: os.store, store: os.store,
data() { data() {
return { return {

View File

@@ -27,7 +27,6 @@ import MkFollowing from './views/pages/following.vue';
import MkFavorites from './views/pages/favorites.vue'; import MkFavorites from './views/pages/favorites.vue';
import MkUserLists from './views/pages/user-lists.vue'; import MkUserLists from './views/pages/user-lists.vue';
import MkUserList from './views/pages/user-list.vue'; import MkUserList from './views/pages/user-list.vue';
import MkSettings from './views/pages/settings.vue';
import MkReversi from './views/pages/games/reversi.vue'; import MkReversi from './views/pages/games/reversi.vue';
import MkTag from './views/pages/tag.vue'; import MkTag from './views/pages/tag.vue';
import MkShare from './views/pages/share.vue'; import MkShare from './views/pages/share.vue';
@@ -137,7 +136,7 @@ init((launch) => {
routes: [ routes: [
{ path: '/', name: 'index', component: MkIndex }, { path: '/', name: 'index', component: MkIndex },
{ path: '/signup', name: 'signup', component: MkSignup }, { path: '/signup', name: 'signup', component: MkSignup },
{ path: '/i/settings', name: 'settings', component: MkSettings }, { path: '/i/settings', name: 'settings', component: () => import('./views/pages/settings.vue').then(m => m.default) },
{ path: '/i/notifications', name: 'notifications', component: MkNotifications }, { path: '/i/notifications', name: 'notifications', component: MkNotifications },
{ path: '/i/favorites', name: 'favorites', component: MkFavorites }, { path: '/i/favorites', name: 'favorites', component: MkFavorites },
{ path: '/i/lists', name: 'user-lists', component: MkUserLists }, { path: '/i/lists', name: 'user-lists', component: MkUserLists },
@@ -154,7 +153,7 @@ init((launch) => {
{ path: '/tags/:tag', component: MkTag }, { path: '/tags/:tag', component: MkTag },
{ path: '/share', component: MkShare }, { path: '/share', component: MkShare },
{ path: '/reversi/:game?', name: 'reversi', component: MkReversi }, { path: '/reversi/:game?', name: 'reversi', component: MkReversi },
{ path: '/@:user', component: MkUser }, { path: '/@:user', component: () => import('./views/pages/user.vue').then(m => m.default) },
{ path: '/@:user/followers', component: MkFollowers }, { path: '/@:user/followers', component: MkFollowers },
{ path: '/@:user/following', component: MkFollowing }, { path: '/@:user/following', component: MkFollowing },
{ path: '/notes/:note', component: MkNote }, { path: '/notes/:note', component: MkNote },

View File

@@ -31,7 +31,7 @@
<span class="created-at" @click="showCreatedAt"><fa :icon="['far', 'clock']"/><mk-time :time="file.createdAt"/></span> <span class="created-at" @click="showCreatedAt"><fa :icon="['far', 'clock']"/><mk-time :time="file.createdAt"/></span>
<template v-if="file.isSensitive"> <template v-if="file.isSensitive">
<span class="separator"></span> <span class="separator"></span>
<span class="nsfw"><fa icon="eye-slash"/> {{ $t('nsfw') }}</span> <span class="nsfw"><fa :icon="['far', 'eye-slash']"/> {{ $t('nsfw') }}</span>
</template> </template>
</div> </div>
</div> </div>

View File

@@ -15,7 +15,7 @@
<span class="created-at"><fa :icon="['far', 'clock']"/><mk-time :time="file.createdAt"/></span> <span class="created-at"><fa :icon="['far', 'clock']"/><mk-time :time="file.createdAt"/></span>
<template v-if="file.isSensitive"> <template v-if="file.isSensitive">
<span class="separator"></span> <span class="separator"></span>
<span class="nsfw"><fa icon="eye-slash"/> {{ $t('nsfw') }}</span> <span class="nsfw"><fa :icon="['far', 'eye-slash']"/> {{ $t('nsfw') }}</span>
</template> </template>
</footer> </footer>
</div> </div>

View File

@@ -51,8 +51,6 @@ export default Vue.extend({
top 0 top 0
bottom 0 bottom 0
right 20px right 20px
> *
height 100% height 100%
</style> </style>

View File

@@ -1,134 +0,0 @@
<template>
<button class="mk-follow-button"
:class="{ wait: wait, active: u.isFollowing || u.hasPendingFollowRequestFromYou }"
@click="onClick"
:disabled="wait"
>
<template v-if="!wait">
<template v-if="u.hasPendingFollowRequestFromYou && u.isLocked"><fa icon="hourglass-half"/> {{ $t('request-pending') }}</template>
<template v-else-if="u.hasPendingFollowRequestFromYou && !u.isLocked"><fa icon="hourglass-start"/> {{ $t('follow-processing') }}</template>
<template v-else-if="u.isFollowing"><fa icon="minus"/> {{ $t('following') }}</template>
<template v-else-if="!u.isFollowing && u.isLocked"><fa icon="plus"/> {{ $t('follow-request') }}</template>
<template v-else-if="!u.isFollowing && !u.isLocked"><fa icon="plus"/> {{ $t('follow') }}</template>
</template>
<template v-else><fa icon="spinner .pulse" fixed-width/></template>
</button>
</template>
<script lang="ts">
import Vue from 'vue';
import i18n from '../../../i18n';
export default Vue.extend({
i18n: i18n('mobile/views/components/follow-button.vue'),
props: {
user: {
type: Object,
required: true
}
},
data() {
return {
u: this.user,
wait: false,
connection: null
};
},
mounted() {
this.connection = this.$root.stream.useSharedConnection('main');
this.connection.on('follow', this.onFollowChange);
this.connection.on('unfollow', this.onFollowChange);
},
beforeDestroy() {
this.connection.dispose();
},
methods: {
onFollowChange(user) {
if (user.id == this.u.id) {
this.u.isFollowing = user.isFollowing;
this.u.hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou;
this.$forceUpdate();
}
},
async onClick() {
this.wait = true;
try {
if (this.u.isFollowing) {
this.u = await this.$root.api('following/delete', {
userId: this.u.id
});
} else {
if (this.u.hasPendingFollowRequestFromYou) {
this.u = await this.$root.api('following/requests/cancel', {
userId: this.u.id
});
} else if (this.u.isLocked) {
this.u = await this.$root.api('following/create', {
userId: this.u.id
});
} else {
this.u = await this.$root.api('following/create', {
userId: this.user.id
});
}
}
} catch (e) {
console.error(e);
} finally {
this.wait = false;
}
}
}
});
</script>
<style lang="stylus" scoped>
.mk-follow-button
display block
user-select none
cursor pointer
padding 0 16px
margin 0
min-width 100px
line-height 36px
font-size 14px
font-weight bold
color var(--primary)
background transparent
outline none
border solid 1px var(--primary)
border-radius 36px
&:hover
background var(--primaryAlpha01)
&:active
background var(--primaryAlpha02)
&.active
color var(--primaryForeground)
background var(--primary)
&:hover
background var(--primaryLighten10)
border-color var(--primaryLighten10)
&:active
background var(--primaryDarken10)
border-color var(--primaryDarken10)
&.wait
cursor wait !important
opacity 0.7
*
pointer-events none
</style>

View File

@@ -5,7 +5,7 @@
<mk-user-card v-for="user in users" :key="user.id" :user="user"/> <mk-user-card v-for="user in users" :key="user.id" :user="user"/>
</div> </div>
<p class="empty" v-if="!fetching && users.length == 0">{{ $t('empty') }}</p> <p class="empty" v-if="!fetching && users.length == 0">{{ $t('empty') }}</p>
<p class="fetching" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>{{ $t('fetching') }}<mk-ellipsis/></p> <p class="fetching" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('fetching') }}<mk-ellipsis/></p>
<a class="refresh" @click="refresh">{{ $t('refresh') }}</a> <a class="refresh" @click="refresh">{{ $t('refresh') }}</a>
<button class="close" @click="close" :title="$t('title')"><fa icon="times"/></button> <button class="close" @click="close" :title="$t('title')"><fa icon="times"/></button>
</div> </div>

View File

@@ -10,7 +10,6 @@ import subNoteContent from './sub-note-content.vue';
import noteCard from './note-card.vue'; import noteCard from './note-card.vue';
import userCard from './user-card.vue'; import userCard from './user-card.vue';
import noteDetail from './note-detail.vue'; import noteDetail from './note-detail.vue';
import followButton from './follow-button.vue';
import friendsMaker from './friends-maker.vue'; import friendsMaker from './friends-maker.vue';
import notification from './notification.vue'; import notification from './notification.vue';
import notifications from './notifications.vue'; import notifications from './notifications.vue';
@@ -19,7 +18,6 @@ import usersList from './users-list.vue';
import userPreview from './user-preview.vue'; import userPreview from './user-preview.vue';
import userTimeline from './user-timeline.vue'; import userTimeline from './user-timeline.vue';
import userListTimeline from './user-list-timeline.vue'; import userListTimeline from './user-list-timeline.vue';
import activity from './activity.vue';
import widgetContainer from './widget-container.vue'; import widgetContainer from './widget-container.vue';
import postForm from './post-form.vue'; import postForm from './post-form.vue';
@@ -33,7 +31,6 @@ Vue.component('mk-sub-note-content', subNoteContent);
Vue.component('mk-note-card', noteCard); Vue.component('mk-note-card', noteCard);
Vue.component('mk-user-card', userCard); Vue.component('mk-user-card', userCard);
Vue.component('mk-note-detail', noteDetail); Vue.component('mk-note-detail', noteDetail);
Vue.component('mk-follow-button', followButton);
Vue.component('mk-friends-maker', friendsMaker); Vue.component('mk-friends-maker', friendsMaker);
Vue.component('mk-notification', notification); Vue.component('mk-notification', notification);
Vue.component('mk-notifications', notifications); Vue.component('mk-notifications', notifications);
@@ -42,6 +39,5 @@ Vue.component('mk-users-list', usersList);
Vue.component('mk-user-preview', userPreview); Vue.component('mk-user-preview', userPreview);
Vue.component('mk-user-timeline', userTimeline); Vue.component('mk-user-timeline', userTimeline);
Vue.component('mk-user-list-timeline', userListTimeline); Vue.component('mk-user-list-timeline', userListTimeline);
Vue.component('mk-activity', activity);
Vue.component('mk-widget-container', widgetContainer); Vue.component('mk-widget-container', widgetContainer);
Vue.component('mk-post-form', postForm); Vue.component('mk-post-form', postForm);

View File

@@ -7,7 +7,7 @@
:disabled="conversationFetching" :disabled="conversationFetching"
> >
<template v-if="!conversationFetching"><fa icon="ellipsis-v"/></template> <template v-if="!conversationFetching"><fa icon="ellipsis-v"/></template>
<template v-if="conversationFetching"><fa icon="spinner .pulse"/></template> <template v-if="conversationFetching"><fa icon="spinner" pulse/></template>
</button> </button>
<div class="conversation"> <div class="conversation">
<x-sub v-for="note in conversation" :key="note.id" :note="note"/> <x-sub v-for="note in conversation" :key="note.id" :note="note"/>

View File

@@ -26,7 +26,7 @@
<footer v-if="more"> <footer v-if="more">
<button @click="loadMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }"> <button @click="loadMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }">
<template v-if="!moreFetching">{{ $t('@.load-more') }}</template> <template v-if="!moreFetching">{{ $t('@.load-more') }}</template>
<template v-if="moreFetching"><fa icon="spinner .pulse" fixed-width/></template> <template v-if="moreFetching"><fa icon="spinner" pulse fixed-width/></template>
</button> </button>
</footer> </footer>
</div> </div>

View File

@@ -18,7 +18,7 @@
</component> </component>
<button class="more" v-if="moreNotifications" @click="fetchMoreNotifications" :disabled="fetchingMoreNotifications"> <button class="more" v-if="moreNotifications" @click="fetchMoreNotifications" :disabled="fetchingMoreNotifications">
<template v-if="fetchingMoreNotifications"><fa icon="spinner .pulse" fixed-width/></template> <template v-if="fetchingMoreNotifications"><fa icon="spinner" pulse fixed-width/></template>
{{ fetchingMoreNotifications ? $t('@.loading') : $t('@.load-more') }} {{ fetchingMoreNotifications ? $t('@.loading') : $t('@.load-more') }}
</button> </button>

View File

@@ -32,7 +32,7 @@
<button class="drive" @click="chooseFileFromDrive"><fa icon="cloud"/></button> <button class="drive" @click="chooseFileFromDrive"><fa icon="cloud"/></button>
<button class="kao" @click="kao"><fa :icon="['far', 'smile']"/></button> <button class="kao" @click="kao"><fa :icon="['far', 'smile']"/></button>
<button class="poll" @click="poll = true"><fa icon="chart-pie"/></button> <button class="poll" @click="poll = true"><fa icon="chart-pie"/></button>
<button class="poll" @click="useCw = !useCw"><fa icon="eye-slash"/></button> <button class="poll" @click="useCw = !useCw"><fa :icon="['far', 'eye-slash']"/></button>
<button class="geo" @click="geo ? removeGeo() : setGeo()"><fa icon="map-marker-alt"/></button> <button class="geo" @click="geo ? removeGeo() : setGeo()"><fa icon="map-marker-alt"/></button>
<button class="visibility" @click="setVisibility" ref="visibilityButton"> <button class="visibility" @click="setVisibility" ref="visibilityButton">
<span v-if="visibility === 'public'"><fa icon="globe"/></span> <span v-if="visibility === 'public'"><fa icon="globe"/></span>

View File

@@ -5,7 +5,7 @@
</header> </header>
<a class="name" :href="user | userPage" target="_blank">{{ user | userName }}</a> <a class="name" :href="user | userPage" target="_blank">{{ user | userName }}</a>
<p class="username"><mk-acct :user="user"/></p> <p class="username"><mk-acct :user="user"/></p>
<mk-follow-button :user="user"/> <mk-follow-button class="follow-button" :user="user"/>
</div> </div>
</template> </template>
@@ -53,7 +53,7 @@ export default Vue.extend({
font-size 15px font-size 15px
color #ccc color #ccc
> .mk-follow-button > .follow-button
display inline-block display inline-block
margin 8px 0 16px 0 margin 8px 0 16px 0

View File

@@ -44,7 +44,8 @@ export default Vue.extend({
this.$root.api('users/notes', { this.$root.api('users/notes', {
userId: this.user.id, userId: this.user.id,
withFiles: this.withMedia, withFiles: this.withMedia,
limit: fetchLimit + 1 limit: fetchLimit + 1,
untilDate: new Date().getTime() + 1000 * 86400 * 365
}).then(notes => { }).then(notes => {
if (notes.length == fetchLimit + 1) { if (notes.length == fetchLimit + 1) {
notes.pop(); notes.pop();
@@ -66,7 +67,7 @@ export default Vue.extend({
userId: this.user.id, userId: this.user.id,
withFiles: this.withMedia, withFiles: this.withMedia,
limit: fetchLimit + 1, limit: fetchLimit + 1,
untilId: (this.$refs.timeline as any).tail().id untilDate: new Date((this.$refs.timeline as any).tail().createdAt).getTime()
}); });
promise.then(notes => { promise.then(notes => {

View File

@@ -14,7 +14,7 @@
<p class="no" v-if="!fetching && users.length == 0"> <p class="no" v-if="!fetching && users.length == 0">
<slot></slot> <slot></slot>
</p> </p>
<p class="fetching" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p> <p class="fetching" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p>
</div> </div>
</template> </template>

View File

@@ -5,12 +5,12 @@
<div class="signin-as" v-html="this.$t('signed-in-as').replace('{}', `<b>${name}</b>`)"></div> <div class="signin-as" v-html="this.$t('signed-in-as').replace('{}', `<b>${name}</b>`)"></div>
<div> <div>
<mk-profile-editor/> <x-profile-editor/>
<ui-card> <ui-card>
<div slot="title"><fa icon="palette"/> {{ $t('theme') }}</div> <div slot="title"><fa icon="palette"/> {{ $t('theme') }}</div>
<section> <section>
<mk-theme/> <x-theme/>
</section> </section>
</ui-card> </ui-card>
@@ -85,9 +85,9 @@
</section> </section>
</ui-card> </ui-card>
<mk-drive-settings/> <x-drive-settings/>
<mk-mute-and-block/> <x-mute-and-block/>
<ui-card> <ui-card>
<div slot="title"><fa icon="volume-up"/> {{ $t('sound') }}</div> <div slot="title"><fa icon="volume-up"/> {{ $t('sound') }}</div>
@@ -140,12 +140,12 @@
</section> </section>
</ui-card> </ui-card>
<mk-api-settings /> <x-api-settings />
<ui-card> <ui-card>
<div slot="title"><fa icon="unlock-alt"/> {{ $t('password') }}</div> <div slot="title"><fa icon="unlock-alt"/> {{ $t('password') }}</div>
<section> <section>
<mk-password-settings/> <x-password-settings/>
</section> </section>
</ui-card> </ui-card>
@@ -182,6 +182,16 @@ import checkForUpdate from '../../../common/scripts/check-for-update';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('mobile/views/pages/settings.vue'), i18n: i18n('mobile/views/pages/settings.vue'),
components: {
XTheme: () => import('../../../common/views/components/theme.vue').then(m => m.default),
XDriveSettings: () => import('../../../common/views/components/drive-settings.vue').then(m => m.default),
XMuteAndBlock: () => import('../../../common/views/components/mute-and-block.vue').then(m => m.default),
XPasswordSettings: () => import('../../../common/views/components/password-settings.vue').then(m => m.default),
XProfileEditor: () => import('../../../common/views/components/profile-editor.vue').then(m => m.default),
XApiSettings: () => import('../../../common/views/components/api-settings.vue').then(m => m.default),
},
data() { data() {
return { return {
apiUrl, apiUrl,

View File

@@ -116,7 +116,7 @@ export default Vue.extend({
menu() { menu() {
let menu = [{ let menu = [{
icon: this.user.isMuted ? '<fa icon="eye"/>' : '<fa icon="eye-slash"/>', icon: this.user.isMuted ? ['fas', 'eye'] : ['far', 'eye-slash'],
text: this.user.isMuted ? this.$t('unmute') : this.$t('mute'), text: this.user.isMuted ? this.$t('unmute') : this.$t('mute'),
action: () => { action: () => {
if (this.user.isMuted) { if (this.user.isMuted) {
@@ -138,7 +138,7 @@ export default Vue.extend({
} }
} }
}, { }, {
icon: this.user.isBlocking ? '<fa icon="user"/>' : '<fa icon="user-slash"/>', icon: this.user.isBlocking ? ['fas', 'user'] : ['fas', 'user-slash'],
text: this.user.isBlocking ? this.$t('unblock') : this.$t('block'), text: this.user.isBlocking ? this.$t('unblock') : this.$t('block'),
action: () => { action: () => {
if (this.user.isBlocking) { if (this.user.isBlocking) {
@@ -243,9 +243,6 @@ main
font-size 18px font-size 18px
color var(--text) color var(--text)
> .mk-follow-button
margin 0
> .title > .title
margin 8px 0 margin 8px 0

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="root followers-you-know"> <div class="root followers-you-know">
<p class="initializing" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p> <p class="initializing" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p>
<div v-if="!fetching && users.length > 0"> <div v-if="!fetching && users.length > 0">
<a v-for="user in users" :key="user.id" :href="user | userPage"> <a v-for="user in users" :key="user.id" :href="user | userPage">
<img :src="user.avatarUrl" :alt="user | userName"/> <img :src="user.avatarUrl" :alt="user | userName"/>

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="root friends"> <div class="root friends">
<p class="fetching" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p> <p class="fetching" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p>
<div v-if="!fetching && users.length > 0"> <div v-if="!fetching && users.length > 0">
<mk-user-card v-for="user in users" :key="user.id" :user="user"/> <mk-user-card v-for="user in users" :key="user.id" :user="user"/>
</div> </div>

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="root notes"> <div class="root notes">
<p class="fetching" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p> <p class="fetching" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p>
<div v-if="!fetching && notes.length > 0"> <div v-if="!fetching && notes.length > 0">
<mk-note-card v-for="note in notes" :key="note.id" :note="note"/> <mk-note-card v-for="note in notes" :key="note.id" :note="note"/>
</div> </div>
@@ -22,7 +22,8 @@ export default Vue.extend({
}, },
mounted() { mounted() {
this.$root.api('users/notes', { this.$root.api('users/notes', {
userId: this.user.id userId: this.user.id,
untilDate: new Date().getTime() + 1000 * 86400 * 365
}).then(notes => { }).then(notes => {
this.notes = notes; this.notes = notes;
this.fetching = false; this.fetching = false;

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="root photos"> <div class="root photos">
<p class="initializing" v-if="fetching"><fa icon="spinner .pulse" fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p> <p class="initializing" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p>
<div class="stream" v-if="!fetching && images.length > 0"> <div class="stream" v-if="!fetching && images.length > 0">
<a v-for="image in images" <a v-for="image in images"
class="img" class="img"
@@ -29,7 +29,8 @@ export default Vue.extend({
this.$root.api('users/notes', { this.$root.api('users/notes', {
userId: this.user.id, userId: this.user.id,
withFiles: true, withFiles: true,
limit: 6 limit: 6,
untilDate: new Date().getTime() + 1000 * 86400 * 365
}).then(notes => { }).then(notes => {
notes.forEach(note => { notes.forEach(note => {
note.media.forEach(media => { note.media.forEach(media => {

View File

@@ -16,7 +16,7 @@
<section class="activity"> <section class="activity">
<h2><fa icon="chart-bar"/>{{ $t('activity') }}</h2> <h2><fa icon="chart-bar"/>{{ $t('activity') }}</h2>
<div> <div>
<mk-activity :user="user"/> <x-activity :user="user"/>
</div> </div>
</section> </section>
<section class="frequently-replied-users"> <section class="frequently-replied-users">
@@ -49,7 +49,8 @@ export default Vue.extend({
XNotes, XNotes,
XPhotos, XPhotos,
XFriends, XFriends,
XFollowersYouKnow XFollowersYouKnow,
XActivity: () => import('../../components/activity.vue').then(m => m.default)
}, },
props: ['user'] props: ['user']
}); });

View File

@@ -3,7 +3,7 @@
<mk-widget-container :show-header="!props.compact"> <mk-widget-container :show-header="!props.compact">
<template slot="header"><fa icon="chart-bar"/>{{ $t('activity') }}</template> <template slot="header"><fa icon="chart-bar"/>{{ $t('activity') }}</template>
<div :class="$style.body"> <div :class="$style.body">
<mk-activity :user="$store.state.i"/> <x-activity :user="$store.state.i"/>
</div> </div>
</mk-widget-container> </mk-widget-container>
</div> </div>
@@ -20,6 +20,9 @@ export default define({
}) })
}).extend({ }).extend({
i18n: i18n(), i18n: i18n(),
components: {
XActivity: () => import('../components/activity.vue').then(m => m.default)
},
methods: { methods: {
func() { func() {
this.props.compact = !this.props.compact; this.props.compact = !this.props.compact;

View File

@@ -18,6 +18,7 @@
secondary: '$secondary', secondary: '$secondary',
bg: ':darken<8<$secondary', bg: ':darken<8<$secondary',
text: '$text', text: '$text',
textHighlighted: ':lighten<7<$text',
scrollbarTrack: ':darken<5<$secondary', scrollbarTrack: ':darken<5<$secondary',
scrollbarHandle: ':lighten<5<$secondary', scrollbarHandle: ':lighten<5<$secondary',

View File

@@ -18,6 +18,7 @@
secondary: '$secondary', secondary: '$secondary',
bg: ':darken<8<$secondary', bg: ':darken<8<$secondary',
text: '$text', text: '$text',
textHighlighted: ':darken<7<$text',
scrollbarTrack: '#fff', scrollbarTrack: '#fff',
scrollbarHandle: '#00000033', scrollbarHandle: '#00000033',

View File

@@ -56,7 +56,7 @@ export default (endpoint: string, user: IUser, app: IApp, data: any, file?: any)
console.warn(`SLOW API CALL DETECTED: ${ep.name} (${time}ms)`); console.warn(`SLOW API CALL DETECTED: ${ep.name} (${time}ms)`);
} }
} catch (e) { } catch (e) {
if (e.name == 'INVALID_PARAM') { if (e && e.name == 'INVALID_PARAM') {
rej({ rej({
code: e.name, code: e.name,
param: e.param, param: e.param,

View File

@@ -216,7 +216,7 @@ export default define(meta, (ps, user, app) => new Promise(async (res, rej) => {
} }
// 投稿を作成 // 投稿を作成
const note = await create(user, { create(user, {
createdAt: new Date(), createdAt: new Date(),
files: files, files: files,
poll: ps.poll, poll: ps.poll,
@@ -229,12 +229,14 @@ export default define(meta, (ps, user, app) => new Promise(async (res, rej) => {
visibility: ps.visibility, visibility: ps.visibility,
visibleUsers, visibleUsers,
geo: ps.geo geo: ps.geo
}); })
.then(note => pack(note, user))
const noteObj = await pack(note, user); .then(noteObj => {
// Reponse
res({ res({
createdNote: noteObj createdNote: noteObj
}); });
})
.catch(e => {
rej(e);
});
})); }));

View File

@@ -153,9 +153,7 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
} }
//#region Construct query //#region Construct query
const sort = { const sort = { } as any;
_id: -1
};
const query = { const query = {
deletedAt: null, deletedAt: null,
@@ -168,15 +166,17 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
$gt: ps.sinceId $gt: ps.sinceId
}; };
} else if (ps.untilId) { } else if (ps.untilId) {
sort._id = -1;
query._id = { query._id = {
$lt: ps.untilId $lt: ps.untilId
}; };
} else if (ps.sinceDate) { } else if (ps.sinceDate) {
sort._id = 1; sort.createdAt = 1;
query.createdAt = { query.createdAt = {
$gt: new Date(ps.sinceDate) $gt: new Date(ps.sinceDate)
}; };
} else if (ps.untilDate) { } else if (ps.untilDate) {
sort.createdAt = -1;
query.createdAt = { query.createdAt = {
$lt: new Date(ps.untilDate) $lt: new Date(ps.untilDate)
}; };

View File

@@ -116,27 +116,27 @@ export default async (user: IUser, data: Option, silent = false) => new Promise<
// リプライ対象が削除された投稿だったらreject // リプライ対象が削除された投稿だったらreject
if (data.reply && data.reply.deletedAt != null) { if (data.reply && data.reply.deletedAt != null) {
return rej(); return rej('Reply target has been deleted');
} }
// Renote対象が削除された投稿だったらreject // Renote対象が削除された投稿だったらreject
if (data.renote && data.renote.deletedAt != null) { if (data.renote && data.renote.deletedAt != null) {
return rej(); return rej('Renote target has been deleted');
} }
// Renote対象が「ホームまたは全体」以外の公開範囲ならreject // Renote対象が「ホームまたは全体」以外の公開範囲ならreject
if (data.renote && data.renote.visibility != 'public' && data.renote.visibility != 'home') { if (data.renote && data.renote.visibility != 'public' && data.renote.visibility != 'home') {
return rej(); return rej('Renote target is not public or home');
} }
// リプライ対象が自分以外の非公開の投稿なら禁止 // リプライ対象が自分以外の非公開の投稿なら禁止
if (data.reply && data.reply.visibility == 'private' && !data.reply.userId.equals(user._id)) { if (data.reply && data.reply.visibility == 'private' && !data.reply.userId.equals(user._id)) {
return rej(); return rej('Reply target is private of others');
} }
// Renote対象が自分以外の非公開の投稿なら禁止 // Renote対象が自分以外の非公開の投稿なら禁止
if (data.renote && data.renote.visibility == 'private' && !data.renote.userId.equals(user._id)) { if (data.renote && data.renote.visibility == 'private' && !data.renote.userId.equals(user._id)) {
return rej(); return rej('Renote target is private of others');
} }
if (data.text) { if (data.text) {

View File

@@ -9,6 +9,7 @@ const { VueLoaderPlugin } = require('vue-loader');
const WebpackOnBuildPlugin = require('on-build-webpack'); const WebpackOnBuildPlugin = require('on-build-webpack');
//const HardSourceWebpackPlugin = require('hard-source-webpack-plugin'); //const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
const ProgressBarPlugin = require('progress-bar-webpack-plugin'); const ProgressBarPlugin = require('progress-bar-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const constants = require('./src/const.json'); const constants = require('./src/const.json');
@@ -146,6 +147,9 @@ module.exports = {
resolveLoader: { resolveLoader: {
modules: ['node_modules'] modules: ['node_modules']
}, },
optimization: {
minimizer: [new TerserPlugin()]
},
cache: true, cache: true,
devtool: false, //'source-map', devtool: false, //'source-map',
mode: isProduction ? 'production' : 'development' mode: isProduction ? 'production' : 'development'