* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wop

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* add notes

* wip

* wip

* wip

* wip

* sound

* wip

* add kick_gaba2

* wip
This commit is contained in:
syuilo 2020-08-18 22:44:21 +09:00 committed by GitHub
parent 122076e8ea
commit 9855405b89
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
70 changed files with 2191 additions and 184 deletions

View file

@ -0,0 +1,141 @@
<template>
<button class="hdcaacmi _button"
:class="{ wait, active: isFollowing, full }"
@click="onClick"
:disabled="wait"
>
<template v-if="!wait">
<template v-if="isFollowing">
<span v-if="full">{{ $t('unfollow') }}</span><fa :icon="faMinus"/>
</template>
<template v-else>
<span v-if="full">{{ $t('follow') }}</span><fa :icon="faPlus"/>
</template>
</template>
<template v-else>
<span v-if="full">{{ $t('processing') }}</span><fa :icon="faSpinner" pulse fixed-width/>
</template>
</button>
</template>
<script lang="ts">
import Vue from 'vue';
import { faSpinner, faPlus, faMinus, } from '@fortawesome/free-solid-svg-icons';
export default Vue.extend({
props: {
channel: {
type: Object,
required: true
},
full: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
isFollowing: this.channel.isFollowing,
wait: false,
faSpinner, faPlus, faMinus,
};
},
methods: {
async onClick() {
this.wait = true;
try {
if (this.isFollowing) {
await this.$root.api('channels/unfollow', {
channelId: this.channel.id
});
this.isFollowing = false;
} else {
await this.$root.api('channels/follow', {
channelId: this.channel.id
});
this.isFollowing = true;
}
} catch (e) {
console.error(e);
} finally {
this.wait = false;
}
}
}
});
</script>
<style lang="scss" scoped>
.hdcaacmi {
position: relative;
display: inline-block;
font-weight: bold;
color: var(--accent);
background: transparent;
border: solid 1px var(--accent);
padding: 0;
height: 31px;
font-size: 16px;
border-radius: 32px;
background: #fff;
&.full {
padding: 0 8px 0 12px;
font-size: 14px;
}
&:not(.full) {
width: 31px;
}
&:focus {
&:after {
content: "";
pointer-events: none;
position: absolute;
top: -5px;
right: -5px;
bottom: -5px;
left: -5px;
border: 2px solid var(--focus);
border-radius: 32px;
}
}
&:hover {
//background: mix($primary, #fff, 20);
}
&:active {
//background: mix($primary, #fff, 40);
}
&.active {
color: #fff;
background: var(--accent);
&:hover {
background: var(--accentLighten);
border-color: var(--accentLighten);
}
&:active {
background: var(--accentDarken);
border-color: var(--accentDarken);
}
}
&.wait {
cursor: wait !important;
opacity: 0.7;
}
> span {
margin-right: 6px;
}
}
</style>

View file

@ -0,0 +1,144 @@
<template>
<router-link :to="`/channels/${channel.id}`" class="eftoefju _panel" tabindex="-1">
<div class="banner" v-if="channel.bannerUrl" :style="`background-image: url('${channel.bannerUrl}')`">
<div class="fade"></div>
<div class="name"><fa :icon="faSatelliteDish"/> {{ channel.name }}</div>
<div class="status">
<div><fa :icon="faUsers" fixed-width/><i18n path="_channel.usersCount" tag="span" style="margin-left: 4px;"><b place="n">{{ channel.usersCount }}</b></i18n></div>
<div><fa :icon="faPencilAlt" fixed-width/><i18n path="_channel.notesCount" tag="span" style="margin-left: 4px;"><b place="n">{{ channel.notesCount }}</b></i18n></div>
</div>
</div>
<article v-if="channel.description">
<p :title="channel.description">{{ channel.description.length > 85 ? channel.description.slice(0, 85) + '…' : channel.description }}</p>
</article>
<footer>
<span>
{{ $t('updatedAt') }}: <mk-time :time="channel.lastNotedAt"/>
</span>
</footer>
</router-link>
</template>
<script lang="ts">
import Vue from 'vue';
import { faSatelliteDish, faUsers, faPencilAlt } from '@fortawesome/free-solid-svg-icons';
export default Vue.extend({
props: {
channel: {
type: Object,
required: true
},
},
data() {
return {
faSatelliteDish, faUsers, faPencilAlt,
};
},
});
</script>
<style lang="scss" scoped>
.eftoefju {
display: block;
overflow: hidden;
width: 100%;
border: 1px solid var(--divider);
&:hover {
text-decoration: none;
}
> .banner {
position: relative;
width: 100%;
height: 200px;
background-position: center;
background-size: cover;
> .fade {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 64px;
background: linear-gradient(0deg, var(--panel), var(--X15));
}
> .name {
position: absolute;
top: 16px;
left: 16px;
padding: 12px 16px;
background: rgba(0, 0, 0, 0.7);
color: #fff;
font-size: 1.2em;
}
> .status {
position: absolute;
z-index: 1;
bottom: 16px;
right: 16px;
padding: 8px 12px;
font-size: 80%;
background: rgba(0, 0, 0, 0.7);
border-radius: 6px;
color: #fff;
}
}
> article {
padding: 16px;
> p {
margin: 0;
font-size: 1em;
}
}
> footer {
padding: 12px 16px;
border-top: solid 1px var(--divider);
> span {
opacity: 0.7;
font-size: 0.9em;
}
}
@media (max-width: 550px) {
font-size: 0.9em;
> .banner {
height: 80px;
> .status {
display: none;
}
}
> article {
padding: 12px;
}
> footer {
display: none;
}
}
@media (max-width: 500px) {
font-size: 0.8em;
> .banner {
height: 70px;
}
> article {
padding: 8px;
}
}
}
</style>

View file

@ -57,6 +57,7 @@
<mk-url-preview v-for="url in urls" :url="url" :key="url" :compact="true" :detail="detail" class="url-preview"/>
<div class="renote" v-if="appearNote.renote"><x-note-preview :note="appearNote.renote"/></div>
</div>
<router-link v-if="appearNote.channel && !inChannel" class="channel" :to="`/channels/${appearNote.channel.id}`"><fa :icon="faSatelliteDish"/> {{ appearNote.channel.name }}</router-link>
</div>
<footer class="footer">
<x-reactions-viewer :note="appearNote" ref="reactionsViewer"/>
@ -96,7 +97,7 @@
<script lang="ts">
import Vue from 'vue';
import { faBolt, faTimes, faBullhorn, faStar, faLink, faExternalLinkSquareAlt, faPlus, faMinus, faRetweet, faReply, faReplyAll, faEllipsisH, faHome, faUnlock, faEnvelope, faThumbtack, faBan, faQuoteRight, faInfoCircle, faBiohazard, faPlug } from '@fortawesome/free-solid-svg-icons';
import { faSatelliteDish, faBolt, faTimes, faBullhorn, faStar, faLink, faExternalLinkSquareAlt, faPlus, faMinus, faRetweet, faReply, faReplyAll, faEllipsisH, faHome, faUnlock, faEnvelope, faThumbtack, faBan, faQuoteRight, faInfoCircle, faBiohazard, faPlug } from '@fortawesome/free-solid-svg-icons';
import { faCopy, faTrashAlt, faEdit, faEye, faEyeSlash } from '@fortawesome/free-regular-svg-icons';
import { parse } from '../../mfm/parse';
import { sum, unique } from '../../prelude/array';
@ -133,6 +134,12 @@ export default Vue.extend({
MkUrlPreview,
},
inject: {
inChannel: {
default: null
}
},
props: {
note: {
type: Object,
@ -159,7 +166,7 @@ export default Vue.extend({
isDeleted: false,
muted: false,
noteBody: this.$refs.noteBody,
faEdit, faBolt, faTimes, faBullhorn, faPlus, faMinus, faRetweet, faReply, faReplyAll, faEllipsisH, faHome, faUnlock, faEnvelope, faThumbtack, faBan, faBiohazard, faPlug
faEdit, faBolt, faTimes, faBullhorn, faPlus, faMinus, faRetweet, faReply, faReplyAll, faEllipsisH, faHome, faUnlock, faEnvelope, faThumbtack, faBan, faBiohazard, faPlug, faSatelliteDish
};
},
@ -954,6 +961,11 @@ export default Vue.extend({
}
}
}
> .channel {
opacity: 0.7;
font-size: 80%;
}
}
> .footer {

View file

@ -10,7 +10,7 @@
<div>
<span class="local-only" v-if="localOnly" v-text="$t('_visibility.localOnly')" />
<span class="text-count" :class="{ over: trimmedLength(text) > max }">{{ max - trimmedLength(text) }}</span>
<button class="_button visibility" @click="setVisibility" ref="visibilityButton" v-tooltip="$t('visibility')">
<button class="_button visibility" @click="setVisibility" ref="visibilityButton" v-tooltip="$t('visibility')" :disabled="channel != null">
<span v-if="visibility === 'public'"><fa :icon="faGlobe"/></span>
<span v-if="visibility === 'home'"><fa :icon="faHome"/></span>
<span v-if="visibility === 'followers'"><fa :icon="faUnlock"/></span>
@ -88,6 +88,10 @@ export default Vue.extend({
type: Object,
required: false
},
channel: {
type: Object,
required: false
},
mention: {
type: Object,
required: false
@ -140,30 +144,38 @@ export default Vue.extend({
},
computed: {
draftId(): string {
return this.renote
? `renote:${this.renote.id}`
: this.reply
? `reply:${this.reply.id}`
: 'note';
draftKey(): string {
let key = this.channel ? `channel:${this.channel.id}` : '';
if (this.renote) {
key += `renote:${this.renote.id}`;
} else if (this.reply) {
key += `reply:${this.reply.id}`;
} else {
key += 'note';
}
return key;
},
placeholder(): string {
const xs = [
this.$t('_postForm._placeholders.a'),
this.$t('_postForm._placeholders.b'),
this.$t('_postForm._placeholders.c'),
this.$t('_postForm._placeholders.d'),
this.$t('_postForm._placeholders.e'),
this.$t('_postForm._placeholders.f')
];
const x = xs[Math.floor(Math.random() * xs.length)];
return this.renote
? this.$t('_postForm.quotePlaceholder')
: this.reply
? this.$t('_postForm.replyPlaceholder')
: x;
if (this.renote) {
return this.$t('_postForm.quotePlaceholder');
} else if (this.reply) {
return this.$t('_postForm.replyPlaceholder');
} else if (this.channel) {
return this.$t('_postForm.channelPlaceholder');
} else {
const xs = [
this.$t('_postForm._placeholders.a'),
this.$t('_postForm._placeholders.b'),
this.$t('_postForm._placeholders.c'),
this.$t('_postForm._placeholders.d'),
this.$t('_postForm._placeholders.e'),
this.$t('_postForm._placeholders.f')
];
return xs[Math.floor(Math.random() * xs.length)];
}
},
submitText(): string {
@ -224,9 +236,11 @@ export default Vue.extend({
}
//
this.applyVisibility(this.$store.state.settings.rememberNoteVisibility ? this.$store.state.deviceUser.visibility : this.$store.state.settings.defaultNoteVisibility);
if (this.channel == null) {
this.applyVisibility(this.$store.state.settings.rememberNoteVisibility ? this.$store.state.deviceUser.visibility : this.$store.state.settings.defaultNoteVisibility);
this.localOnly = this.$store.state.settings.rememberNoteVisibility ? this.$store.state.deviceUser.localOnly : this.$store.state.settings.defaultNoteLocalOnly;
this.localOnly = this.$store.state.settings.rememberNoteVisibility ? this.$store.state.deviceUser.localOnly : this.$store.state.settings.defaultNoteLocalOnly;
}
//
if (this.reply && ['home', 'followers', 'specified'].includes(this.reply.visibility)) {
@ -266,7 +280,7 @@ export default Vue.extend({
this.$nextTick(() => {
// 稿
if (!this.instant && !this.mention) {
const draft = JSON.parse(localStorage.getItem('drafts') || '{}')[this.draftId];
const draft = JSON.parse(localStorage.getItem('drafts') || '{}')[this.draftKey];
if (draft) {
this.text = draft.data.text;
this.useCw = draft.data.useCw;
@ -398,6 +412,10 @@ export default Vue.extend({
},
setVisibility() {
if (this.channel) {
// TODO: information dialog
return;
}
const w = this.$root.new(MkVisibilityChooser, {
source: this.$refs.visibilityButton,
currentVisibility: this.visibility,
@ -510,7 +528,7 @@ export default Vue.extend({
const data = JSON.parse(localStorage.getItem('drafts') || '{}');
data[this.draftId] = {
data[this.draftKey] = {
updatedAt: new Date(),
data: {
text: this.text,
@ -529,7 +547,7 @@ export default Vue.extend({
deleteDraft() {
const data = JSON.parse(localStorage.getItem('drafts') || '{}');
delete data[this.draftId];
delete data[this.draftKey];
localStorage.setItem('drafts', JSON.stringify(data));
},
@ -540,6 +558,7 @@ export default Vue.extend({
fileIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined,
replyId: this.reply ? this.reply.id : undefined,
renoteId: this.renote ? this.renote.id : this.quoteId ? this.quoteId : undefined,
channelId: this.channel ? this.channel.id : undefined,
poll: this.poll ? (this.$refs.poll as any).get() : undefined,
cw: this.useCw ? this.cw || '' : undefined,
localOnly: this.localOnly,

View file

@ -24,6 +24,10 @@ export default Vue.extend({
type: String,
required: false
},
channel: {
type: String,
required: false
},
sound: {
type: Boolean,
required: false,
@ -31,6 +35,12 @@ export default Vue.extend({
}
},
provide() {
return {
inChannel: this.src === 'channel'
};
},
data() {
return {
connection: null,
@ -117,6 +127,15 @@ export default Vue.extend({
this.connection.on('note', prepend);
this.connection.on('userAdded', onUserAdded);
this.connection.on('userRemoved', onUserRemoved);
} else if (this.src == 'channel') {
endpoint = 'channels/timeline';
this.query = {
channelId: this.channel
};
this.connection = this.$root.stream.connectToChannel('channel', {
channelId: this.channel
});
this.connection.on('note', prepend);
}
this.pagination = {