summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorsyuilo <syuilotan@yahoo.co.jp>2018-09-18 02:14:12 +0900
committersyuilo <syuilotan@yahoo.co.jp>2018-09-18 02:14:12 +0900
commitab83e08bc7deb244d35e2315abead473d536d2c3 (patch)
tree1a6b441f69460b0bf620694d7b3f2019dfe4c830 /src
parentRefactor (diff)
downloadsharkey-ab83e08bc7deb244d35e2315abead473d536d2c3.tar.gz
sharkey-ab83e08bc7deb244d35e2315abead473d536d2c3.tar.bz2
sharkey-ab83e08bc7deb244d35e2315abead473d536d2c3.zip
メッセージタイムラインを追加
Diffstat (limited to 'src')
-rw-r--r--src/client/app/desktop/views/components/timeline.core.vue148
-rw-r--r--src/client/app/desktop/views/components/timeline.vue18
-rw-r--r--src/client/app/desktop/views/pages/deck/deck.column-core.vue5
-rw-r--r--src/client/app/desktop/views/pages/deck/deck.direct-column.vue38
-rw-r--r--src/client/app/desktop/views/pages/deck/deck.direct.vue97
-rw-r--r--src/client/app/desktop/views/pages/deck/deck.vue9
-rw-r--r--src/client/app/mobile/views/pages/home.timeline.vue147
-rw-r--r--src/client/app/mobile/views/pages/home.vue49
-rw-r--r--src/server/api/endpoints/notes/mentions.ts7
-rw-r--r--src/services/note/create.ts22
10 files changed, 390 insertions, 150 deletions
diff --git a/src/client/app/desktop/views/components/timeline.core.vue b/src/client/app/desktop/views/components/timeline.core.vue
index d2176dee87..c8aa36f171 100644
--- a/src/client/app/desktop/views/components/timeline.core.vue
+++ b/src/client/app/desktop/views/components/timeline.core.vue
@@ -38,7 +38,14 @@ export default Vue.extend({
streamManager: null,
connection: null,
connectionId: null,
- date: null
+ date: null,
+ baseQuery: {
+ includeMyRenotes: this.$store.state.settings.showMyRenotes,
+ includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
+ includeLocalRenotes: this.$store.state.settings.showLocalRenotes
+ },
+ query: {},
+ endpoint: null
};
},
@@ -47,53 +54,102 @@ export default Vue.extend({
return this.$store.state.i.followingCount == 0;
},
- endpoint(): string {
- switch (this.src) {
- case 'home': return 'notes/timeline';
- case 'local': return 'notes/local-timeline';
- case 'hybrid': return 'notes/hybrid-timeline';
- case 'global': return 'notes/global-timeline';
- case 'mentions': return 'notes/mentions';
- case 'tag': return 'notes/search_by_tag';
- }
- },
-
canFetchMore(): boolean {
return !this.moreFetching && !this.fetching && this.existMore;
}
},
mounted() {
+ const prepend = note => {
+ (this.$refs.timeline as any).prepend(note);
+ };
+
if (this.src == 'tag') {
+ this.endpoint = 'notes/search_by_tag';
+ this.query = {
+ query: this.tagTl.query
+ };
this.connection = new HashtagStream((this as any).os, this.$store.state.i, this.tagTl.query);
- this.connection.on('note', this.onNote);
+ this.connection.on('note', prepend);
+ this.$once('beforeDestroy', () => {
+ this.connection.off('note', prepend);
+ this.connection.close();
+ });
} else if (this.src == 'home') {
+ this.endpoint = 'notes/timeline';
+ const onChangeFollowing = () => {
+ this.fetch();
+ };
this.streamManager = (this as any).os.stream;
this.connection = this.streamManager.getConnection();
this.connectionId = this.streamManager.use();
- this.connection.on('note', this.onNote);
- this.connection.on('follow', this.onChangeFollowing);
- this.connection.on('unfollow', this.onChangeFollowing);
+ this.connection.on('note', prepend);
+ this.connection.on('follow', onChangeFollowing);
+ this.connection.on('unfollow', onChangeFollowing);
+ this.$once('beforeDestroy', () => {
+ this.connection.off('note', prepend);
+ this.connection.off('follow', onChangeFollowing);
+ this.connection.off('unfollow', onChangeFollowing);
+ this.streamManager.dispose(this.connectionId);
+ });
} else if (this.src == 'local') {
+ this.endpoint = 'notes/local-timeline';
this.streamManager = (this as any).os.streams.localTimelineStream;
this.connection = this.streamManager.getConnection();
this.connectionId = this.streamManager.use();
- this.connection.on('note', this.onNote);
+ this.connection.on('note', prepend);
+ this.$once('beforeDestroy', () => {
+ this.connection.off('note', prepend);
+ this.streamManager.dispose(this.connectionId);
+ });
} else if (this.src == 'hybrid') {
+ this.endpoint = 'notes/hybrid-timeline';
this.streamManager = (this as any).os.streams.hybridTimelineStream;
this.connection = this.streamManager.getConnection();
this.connectionId = this.streamManager.use();
- this.connection.on('note', this.onNote);
+ this.connection.on('note', prepend);
+ this.$once('beforeDestroy', () => {
+ this.connection.off('note', prepend);
+ this.streamManager.dispose(this.connectionId);
+ });
} else if (this.src == 'global') {
+ this.endpoint = 'notes/global-timeline';
this.streamManager = (this as any).os.streams.globalTimelineStream;
this.connection = this.streamManager.getConnection();
this.connectionId = this.streamManager.use();
- this.connection.on('note', this.onNote);
+ this.connection.on('note', prepend);
+ this.$once('beforeDestroy', () => {
+ this.connection.off('note', prepend);
+ this.streamManager.dispose(this.connectionId);
+ });
} else if (this.src == 'mentions') {
+ this.endpoint = 'notes/mentions';
+ this.streamManager = (this as any).os.stream;
+ this.connection = this.streamManager.getConnection();
+ this.connectionId = this.streamManager.use();
+ this.connection.on('mention', prepend);
+ this.$once('beforeDestroy', () => {
+ this.connection.off('mention', prepend);
+ this.streamManager.dispose(this.connectionId);
+ });
+ } else if (this.src == 'messages') {
+ this.endpoint = 'notes/mentions';
+ this.query = {
+ visibility: 'specified'
+ };
+ const onNote = note => {
+ if (note.visibility == 'specified') {
+ prepend(note);
+ }
+ };
this.streamManager = (this as any).os.stream;
this.connection = this.streamManager.getConnection();
this.connectionId = this.streamManager.use();
- this.connection.on('mention', this.onNote);
+ this.connection.on('mention', onNote);
+ this.$once('beforeDestroy', () => {
+ this.connection.off('mention', onNote);
+ this.streamManager.dispose(this.connectionId);
+ });
}
document.addEventListener('keydown', this.onKeydown);
@@ -102,28 +158,7 @@ export default Vue.extend({
},
beforeDestroy() {
- if (this.src == 'tag') {
- this.connection.off('note', this.onNote);
- this.connection.close();
- } else if (this.src == 'home') {
- this.connection.off('note', this.onNote);
- this.connection.off('follow', this.onChangeFollowing);
- this.connection.off('unfollow', this.onChangeFollowing);
- this.streamManager.dispose(this.connectionId);
- } else if (this.src == 'local') {
- this.connection.off('note', this.onNote);
- this.streamManager.dispose(this.connectionId);
- } else if (this.src == 'hybrid') {
- this.connection.off('note', this.onNote);
- this.streamManager.dispose(this.connectionId);
- } else if (this.src == 'global') {
- this.connection.off('note', this.onNote);
- this.streamManager.dispose(this.connectionId);
- } else if (this.src == 'mentions') {
- this.connection.off('mention', this.onNote);
- this.streamManager.dispose(this.connectionId);
- }
-
+ this.$emit('beforeDestroy');
document.removeEventListener('keydown', this.onKeydown);
},
@@ -132,14 +167,10 @@ export default Vue.extend({
this.fetching = true;
(this.$refs.timeline as any).init(() => new Promise((res, rej) => {
- (this as any).api(this.endpoint, {
+ (this as any).api(this.endpoint, Object.assign({
limit: fetchLimit + 1,
- untilDate: this.date ? this.date.getTime() : undefined,
- includeMyRenotes: this.$store.state.settings.showMyRenotes,
- includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
- includeLocalRenotes: this.$store.state.settings.showLocalRenotes,
- query: this.tagTl ? this.tagTl.query : undefined
- }).then(notes => {
+ untilDate: this.date ? this.date.getTime() : undefined
+ }, this.baseQuery, this.query)).then(notes => {
if (notes.length == fetchLimit + 1) {
notes.pop();
this.existMore = true;
@@ -156,14 +187,10 @@ export default Vue.extend({
this.moreFetching = true;
- const promise = (this as any).api(this.endpoint, {
+ const promise = (this as any).api(this.endpoint, Object.assign({
limit: fetchLimit + 1,
- untilId: (this.$refs.timeline as any).tail().id,
- includeMyRenotes: this.$store.state.settings.showMyRenotes,
- includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
- includeLocalRenotes: this.$store.state.settings.showLocalRenotes,
- query: this.tagTl ? this.tagTl.query : undefined
- });
+ untilId: (this.$refs.timeline as any).tail().id
+ }, this.baseQuery, this.query));
promise.then(notes => {
if (notes.length == fetchLimit + 1) {
@@ -178,15 +205,6 @@ export default Vue.extend({
return promise;
},
- onNote(note) {
- // Prepend a note
- (this.$refs.timeline as any).prepend(note);
- },
-
- onChangeFollowing() {
- this.fetch();
- },
-
focus() {
(this.$refs.timeline as any).focus();
},
diff --git a/src/client/app/desktop/views/components/timeline.vue b/src/client/app/desktop/views/components/timeline.vue
index 2dc84004df..ccc35f95ff 100644
--- a/src/client/app/desktop/views/components/timeline.vue
+++ b/src/client/app/desktop/views/components/timeline.vue
@@ -5,10 +5,11 @@
<span :data-active="src == 'local'" @click="src = 'local'" v-if="enableLocalTimeline">%fa:R comments% %i18n:@local%</span>
<span :data-active="src == 'hybrid'" @click="src = 'hybrid'" v-if="enableLocalTimeline">%fa:share-alt% %i18n:@hybrid%</span>
<span :data-active="src == 'global'" @click="src = 'global'">%fa:globe% %i18n:@global%</span>
- <span :data-active="src == 'mentions'" @click="src = 'mentions'">%fa:at% %i18n:@mentions%</span>
<span :data-active="src == 'tag'" @click="src = 'tag'" v-if="tagTl">%fa:hashtag% {{ tagTl.title }}</span>
<span :data-active="src == 'list'" @click="src = 'list'" v-if="list">%fa:list% {{ list.title }}</span>
<div class="buttons">
+ <button :data-active="src == 'mentions'" @click="src = 'mentions'" title="%i18n:@mentions%">%fa:at%</button>
+ <button :data-active="src == 'messages'" @click="src = 'messages'" title="%i18n:@messages%">%fa:envelope R%</button>
<button @click="chooseTag" title="%i18n:@hashtag%" ref="tagButton">%fa:hashtag%</button>
<button @click="chooseList" title="%i18n:@list%" ref="listButton">%fa:list%</button>
</div>
@@ -18,6 +19,7 @@
<x-core v-if="src == 'hybrid'" ref="tl" key="hybrid" src="hybrid"/>
<x-core v-if="src == 'global'" ref="tl" key="global" src="global"/>
<x-core v-if="src == 'mentions'" ref="tl" key="mentions" src="mentions"/>
+ <x-core v-if="src == 'messages'" ref="tl" key="messages" src="messages"/>
<x-core v-if="src == 'tag'" ref="tl" key="tag" src="tag" :tag-tl="tagTl"/>
<mk-user-list-timeline v-if="src == 'list'" ref="tl" :key="list.id" :list="list"/>
</div>
@@ -202,6 +204,20 @@ root(isDark)
&:active
color isDark ? #b2c1d5 : #999
+ &[data-active]
+ color $theme-color
+ cursor default
+
+ &:before
+ content ""
+ display block
+ position absolute
+ bottom 0
+ left 0
+ width 100%
+ height 2px
+ background $theme-color
+
> span
display inline-block
padding 0 10px
diff --git a/src/client/app/desktop/views/pages/deck/deck.column-core.vue b/src/client/app/desktop/views/pages/deck/deck.column-core.vue
index a320f697b3..e1490cb0e4 100644
--- a/src/client/app/desktop/views/pages/deck/deck.column-core.vue
+++ b/src/client/app/desktop/views/pages/deck/deck.column-core.vue
@@ -8,6 +8,7 @@
<x-tl-column v-else-if="column.type == 'list'" :column="column" :is-stacked="isStacked"/>
<x-tl-column v-else-if="column.type == 'hashtag'" :column="column" :is-stacked="isStacked"/>
<x-mentions-column v-else-if="column.type == 'mentions'" :column="column" :is-stacked="isStacked"/>
+<x-direct-column v-else-if="column.type == 'direct'" :column="column" :is-stacked="isStacked"/>
</template>
<script lang="ts">
@@ -16,13 +17,15 @@ import XTlColumn from './deck.tl-column.vue';
import XNotificationsColumn from './deck.notifications-column.vue';
import XWidgetsColumn from './deck.widgets-column.vue';
import XMentionsColumn from './deck.mentions-column.vue';
+import XDirectColumn from './deck.direct-column.vue';
export default Vue.extend({
components: {
XTlColumn,
XNotificationsColumn,
XWidgetsColumn,
- XMentionsColumn
+ XMentionsColumn,
+ XDirectColumn
},
props: {
diff --git a/src/client/app/desktop/views/pages/deck/deck.direct-column.vue b/src/client/app/desktop/views/pages/deck/deck.direct-column.vue
new file mode 100644
index 0000000000..d5093761f4
--- /dev/null
+++ b/src/client/app/desktop/views/pages/deck/deck.direct-column.vue
@@ -0,0 +1,38 @@
+<template>
+<x-column :name="name" :column="column" :is-stacked="isStacked">
+ <span slot="header">%fa:envelope R%{{ name }}</span>
+
+ <x-direct/>
+</x-column>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+import XColumn from './deck.column.vue';
+import XDirect from './deck.direct.vue';
+
+export default Vue.extend({
+ components: {
+ XColumn,
+ XDirect
+ },
+
+ props: {
+ column: {
+ type: Object,
+ required: true
+ },
+ isStacked: {
+ type: Boolean,
+ required: true
+ }
+ },
+
+ computed: {
+ name(): string {
+ if (this.column.name) return this.column.name;
+ return '%i18n:common.deck.direct%';
+ }
+ },
+});
+</script>
diff --git a/src/client/app/desktop/views/pages/deck/deck.direct.vue b/src/client/app/desktop/views/pages/deck/deck.direct.vue
new file mode 100644
index 0000000000..ec9e6b9c3d
--- /dev/null
+++ b/src/client/app/desktop/views/pages/deck/deck.direct.vue
@@ -0,0 +1,97 @@
+<template>
+ <x-notes ref="timeline" :more="existMore ? more : null"/>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+import XNotes from './deck.notes.vue';
+
+const fetchLimit = 10;
+
+export default Vue.extend({
+ components: {
+ XNotes
+ },
+
+ props: {
+ },
+
+ data() {
+ return {
+ fetching: true,
+ moreFetching: false,
+ existMore: false,
+ connection: null,
+ connectionId: null
+ };
+ },
+
+ mounted() {
+ this.connection = (this as any).os.stream.getConnection();
+ this.connectionId = (this as any).os.stream.use();
+
+ this.connection.on('mention', this.onNote);
+
+ this.fetch();
+ },
+
+ beforeDestroy() {
+ this.connection.off('mention', this.onNote);
+ (this as any).os.stream.dispose(this.connectionId);
+ },
+
+ methods: {
+ fetch() {
+ this.fetching = true;
+
+ (this.$refs.timeline as any).init(() => new Promise((res, rej) => {
+ (this as any).api('notes/mentions', {
+ limit: fetchLimit + 1,
+ includeMyRenotes: this.$store.state.settings.showMyRenotes,
+ includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
+ includeLocalRenotes: this.$store.state.settings.showLocalRenotes,
+ visibility: 'specified'
+ }).then(notes => {
+ if (notes.length == fetchLimit + 1) {
+ notes.pop();
+ this.existMore = true;
+ }
+ res(notes);
+ this.fetching = false;
+ this.$emit('loaded');
+ }, rej);
+ }));
+ },
+ more() {
+ this.moreFetching = true;
+
+ const promise = (this as any).api('notes/mentions', {
+ limit: fetchLimit + 1,
+ untilId: (this.$refs.timeline as any).tail().id,
+ includeMyRenotes: this.$store.state.settings.showMyRenotes,
+ includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
+ includeLocalRenotes: this.$store.state.settings.showLocalRenotes,
+ visibility: 'specified'
+ });
+
+ promise.then(notes => {
+ if (notes.length == fetchLimit + 1) {
+ notes.pop();
+ } else {
+ this.existMore = false;
+ }
+ notes.forEach(n => (this.$refs.timeline as any).append(n));
+ this.moreFetching = false;
+ });
+
+ return promise;
+ },
+ onNote(note) {
+ // Prepend a note
+ if (note.visibility == 'specified') {
+ (this.$refs.timeline as any).prepend(note);
+ }
+ }
+ }
+});
+</script>
diff --git a/src/client/app/desktop/views/pages/deck/deck.vue b/src/client/app/desktop/views/pages/deck/deck.vue
index aafe9a45d3..e5aeba251a 100644
--- a/src/client/app/desktop/views/pages/deck/deck.vue
+++ b/src/client/app/desktop/views/pages/deck/deck.vue
@@ -148,6 +148,15 @@ export default Vue.extend({
});
}
}, {
+ icon: '%fa:envelope R%',
+ text: '%i18n:common.deck.direct%',
+ action: () => {
+ this.$store.dispatch('settings/addDeckColumn', {
+ id: uuid(),
+ type: 'direct'
+ });
+ }
+ }, {
icon: '%fa:list%',
text: '%i18n:common.deck.list%',
action: () => {
diff --git a/src/client/app/mobile/views/pages/home.timeline.vue b/src/client/app/mobile/views/pages/home.timeline.vue
index fecb2384ba..225abcff6b 100644
--- a/src/client/app/mobile/views/pages/home.timeline.vue
+++ b/src/client/app/mobile/views/pages/home.timeline.vue
@@ -37,7 +37,14 @@ export default Vue.extend({
connection: null,
connectionId: null,
unreadCount: 0,
- date: null
+ date: null,
+ baseQuery: {
+ includeMyRenotes: this.$store.state.settings.showMyRenotes,
+ includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
+ includeLocalRenotes: this.$store.state.settings.showLocalRenotes
+ },
+ query: {},
+ endpoint: null
};
},
@@ -46,80 +53,109 @@ export default Vue.extend({
return this.$store.state.i.followingCount == 0;
},
- endpoint(): string {
- switch (this.src) {
- case 'home': return 'notes/timeline';
- case 'local': return 'notes/local-timeline';
- case 'hybrid': return 'notes/hybrid-timeline';
- case 'global': return 'notes/global-timeline';
- case 'mentions': return 'notes/mentions';
- case 'tag': return 'notes/search_by_tag';
- }
- },
-
canFetchMore(): boolean {
return !this.moreFetching && !this.fetching && this.existMore;
}
},
mounted() {
+ const prepend = note => {
+ (this.$refs.timeline as any).prepend(note);
+ };
+
if (this.src == 'tag') {
+ this.endpoint = 'notes/search_by_tag';
+ this.query = {
+ query: this.tagTl.query
+ };
this.connection = new HashtagStream((this as any).os, this.$store.state.i, this.tagTl.query);
- this.connection.on('note', this.onNote);
+ this.connection.on('note', prepend);
+ this.$once('beforeDestroy', () => {
+ this.connection.off('note', prepend);
+ this.connection.close();
+ });
} else if (this.src == 'home') {
+ this.endpoint = 'notes/timeline';
+ const onChangeFollowing = () => {
+ this.fetch();
+ };
this.streamManager = (this as any).os.stream;
this.connection = this.streamManager.getConnection();
this.connectionId = this.streamManager.use();
- this.connection.on('note', this.onNote);
- this.connection.on('follow', this.onChangeFollowing);
- this.connection.on('unfollow', this.onChangeFollowing);
+ this.connection.on('note', prepend);
+ this.connection.on('follow', onChangeFollowing);
+ this.connection.on('unfollow', onChangeFollowing);
+ this.$once('beforeDestroy', () => {
+ this.connection.off('note', prepend);
+ this.connection.off('follow', onChangeFollowing);
+ this.connection.off('unfollow', onChangeFollowing);
+ this.streamManager.dispose(this.connectionId);
+ });
} else if (this.src == 'local') {
+ this.endpoint = 'notes/local-timeline';
this.streamManager = (this as any).os.streams.localTimelineStream;
this.connection = this.streamManager.getConnection();
this.connectionId = this.streamManager.use();
- this.connection.on('note', this.onNote);
+ this.connection.on('note', prepend);
+ this.$once('beforeDestroy', () => {
+ this.connection.off('note', prepend);
+ this.streamManager.dispose(this.connectionId);
+ });
} else if (this.src == 'hybrid') {
+ this.endpoint = 'notes/hybrid-timeline';
this.streamManager = (this as any).os.streams.hybridTimelineStream;
this.connection = this.streamManager.getConnection();
this.connectionId = this.streamManager.use();
- this.connection.on('note', this.onNote);
+ this.connection.on('note', prepend);
+ this.$once('beforeDestroy', () => {
+ this.connection.off('note', prepend);
+ this.streamManager.dispose(this.connectionId);
+ });
} else if (this.src == 'global') {
+ this.endpoint = 'notes/global-timeline';
this.streamManager = (this as any).os.streams.globalTimelineStream;
this.connection = this.streamManager.getConnection();
this.connectionId = this.streamManager.use();
- this.connection.on('note', this.onNote);
+ this.connection.on('note', prepend);
+ this.$once('beforeDestroy', () => {
+ this.connection.off('note', prepend);
+ this.streamManager.dispose(this.connectionId);
+ });
} else if (this.src == 'mentions') {
+ this.endpoint = 'notes/mentions';
+ this.streamManager = (this as any).os.stream;
+ this.connection = this.streamManager.getConnection();
+ this.connectionId = this.streamManager.use();
+ this.connection.on('mention', prepend);
+ this.$once('beforeDestroy', () => {
+ this.connection.off('mention', prepend);
+ this.streamManager.dispose(this.connectionId);
+ });
+ } else if (this.src == 'messages') {
+ this.endpoint = 'notes/mentions';
+ this.query = {
+ visibility: 'specified'
+ };
+ const onNote = note => {
+ if (note.visibility == 'specified') {
+ prepend(note);
+ }
+ };
this.streamManager = (this as any).os.stream;
this.connection = this.streamManager.getConnection();
this.connectionId = this.streamManager.use();
- this.connection.on('mention', this.onNote);
+ this.connection.on('mention', onNote);
+ this.$once('beforeDestroy', () => {
+ this.connection.off('mention', onNote);
+ this.streamManager.dispose(this.connectionId);
+ });
}
this.fetch();
},
beforeDestroy() {
- if (this.src == 'tag') {
- this.connection.off('note', this.onNote);
- this.connection.close();
- } else if (this.src == 'home') {
- this.connection.off('note', this.onNote);
- this.connection.off('follow', this.onChangeFollowing);
- this.connection.off('unfollow', this.onChangeFollowing);
- this.streamManager.dispose(this.connectionId);
- } else if (this.src == 'local') {
- this.connection.off('note', this.onNote);
- this.streamManager.dispose(this.connectionId);
- } else if (this.src == 'hybrid') {
- this.connection.off('note', this.onNote);
- this.streamManager.dispose(this.connectionId);
- } else if (this.src == 'global') {
- this.connection.off('note', this.onNote);
- this.streamManager.dispose(this.connectionId);
- } else if (this.src == 'mentions') {
- this.connection.off('mention', this.onNote);
- this.streamManager.dispose(this.connectionId);
- }
+ this.$emit('beforeDestroy');
},
methods: {
@@ -127,14 +163,10 @@ export default Vue.extend({
this.fetching = true;
(this.$refs.timeline as any).init(() => new Promise((res, rej) => {
- (this as any).api(this.endpoint, {
+ (this as any).api(this.endpoint, Object.assign({
limit: fetchLimit + 1,
- untilDate: this.date ? this.date.getTime() : undefined,
- includeMyRenotes: this.$store.state.settings.showMyRenotes,
- includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
- includeLocalRenotes: this.$store.state.settings.showLocalRenotes,
- query: this.tagTl ? this.tagTl.query : undefined
- }).then(notes => {
+ untilDate: this.date ? this.date.getTime() : undefined
+ }, this.baseQuery, this.query)).then(notes => {
if (notes.length == fetchLimit + 1) {
notes.pop();
this.existMore = true;
@@ -151,14 +183,10 @@ export default Vue.extend({
this.moreFetching = true;
- const promise = (this as any).api(this.endpoint, {
+ const promise = (this as any).api(this.endpoint, Object.assign({
limit: fetchLimit + 1,
- untilId: (this.$refs.timeline as any).tail().id,
- includeMyRenotes: this.$store.state.settings.showMyRenotes,
- includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
- includeLocalRenotes: this.$store.state.settings.showLocalRenotes,
- query: this.tagTl ? this.tagTl.query : undefined
- });
+ untilId: (this.$refs.timeline as any).tail().id
+ }, this.baseQuery, this.query));
promise.then(notes => {
if (notes.length == fetchLimit + 1) {
@@ -173,15 +201,6 @@ export default Vue.extend({
return promise;
},
- onNote(note) {
- // Prepend a note
- (this.$refs.timeline as any).prepend(note);
- },
-
- onChangeFollowing() {
- this.fetch();
- },
-
focus() {
(this.$refs.timeline as any).focus();
},
diff --git a/src/client/app/mobile/views/pages/home.vue b/src/client/app/mobile/views/pages/home.vue
index 3ec2f16b75..e61916fe18 100644
--- a/src/client/app/mobile/views/pages/home.vue
+++ b/src/client/app/mobile/views/pages/home.vue
@@ -7,6 +7,7 @@
<span v-if="src == 'hybrid'">%fa:share-alt%%i18n:@hybrid%</span>
<span v-if="src == 'global'">%fa:globe%%i18n:@global%</span>
<span v-if="src == 'mentions'">%fa:at%%i18n:@mentions%</span>
+ <span v-if="src == 'messages'">%fa:envelope R%%i18n:@messages%</span>
<span v-if="src == 'list'">%fa:list%{{ list.title }}</span>
<span v-if="src == 'tag'">%fa:hashtag%{{ tagTl.title }}</span>
</span>
@@ -23,16 +24,21 @@
<main :data-darkmode="$store.state.device.darkmode">
<div class="nav" v-if="showNav">
<div class="bg" @click="showNav = false"></div>
+ <div class="pointer"></div>
<div class="body">
<div>
<span :data-active="src == 'home'" @click="src = 'home'">%fa:home% %i18n:@home%</span>
<span :data-active="src == 'local'" @click="src = 'local'" v-if="enableLocalTimeline">%fa:R comments% %i18n:@local%</span>
<span :data-active="src == 'hybrid'" @click="src = 'hybrid'" v-if="enableLocalTimeline">%fa:share-alt% %i18n:@hybrid%</span>
<span :data-active="src == 'global'" @click="src = 'global'">%fa:globe% %i18n:@global%</span>
+ <div class="hr"></div>
<span :data-active="src == 'mentions'" @click="src = 'mentions'">%fa:at% %i18n:@mentions%</span>
+ <span :data-active="src == 'messages'" @click="src = 'messages'">%fa:envelope R% %i18n:@messages%</span>
<template v-if="lists">
+ <div class="hr"></div>
<span v-for="l in lists" :data-active="src == 'list' && list == l" @click="src = 'list'; list = l" :key="l.id">%fa:list% {{ l.title }}</span>
</template>
+ <div class="hr" v-if="$store.state.settings.tagTimelines && $store.state.settings.tagTimelines.length > 0"></div>
<span v-for="tl in $store.state.settings.tagTimelines" :data-active="src == 'tag' && tagTl == tl" @click="src = 'tag'; tagTl = tl" :key="tl.id">%fa:hashtag% {{ tl.title }}</span>
</div>
</div>
@@ -44,6 +50,7 @@
<x-tl v-if="src == 'hybrid'" ref="tl" key="hybrid" src="hybrid"/>
<x-tl v-if="src == 'global'" ref="tl" key="global" src="global"/>
<x-tl v-if="src == 'mentions'" ref="tl" key="mentions" src="mentions"/>
+ <x-tl v-if="src == 'messages'" ref="tl" key="messages" src="messages"/>
<x-tl v-if="src == 'tag'" ref="tl" key="tag" src="tag" :tag-tl="tagTl"/>
<mk-user-list-timeline v-if="src == 'list'" ref="tl" :key="list.id" :list="list"/>
</div>
@@ -150,6 +157,26 @@ export default Vue.extend({
root(isDark)
> .nav
+ > .pointer
+ position fixed
+ z-index 10002
+ top 56px
+ left 0
+ right 0
+
+ $size = 16px
+
+ &:after
+ content ""
+ display block
+ position absolute
+ top -($size * 2)
+ left s('calc(50% - %s)', $size)
+ border-top solid $size transparent
+ border-left solid $size transparent
+ border-right solid $size transparent
+ border-bottom solid $size isDark ? #272f3a : #fff
+
> .bg
position fixed
z-index 10000
@@ -166,28 +193,22 @@ root(isDark)
left 0
right 0
width 300px
+ max-height calc(100% - 70px)
margin 0 auto
+ overflow auto
+ -webkit-overflow-scrolling touch
background isDark ? #272f3a : #fff
border-radius 8px
box-shadow 0 0 16px rgba(#000, 0.1)
- $balloon-size = 16px
-
- &:after
- content ""
- display block
- position absolute
- top -($balloon-size * 2) + 1.5px
- left s('calc(50% - %s)', $balloon-size)
- border-top solid $balloon-size transparent
- border-left solid $balloon-size transparent
- border-right solid $balloon-size transparent
- border-bottom solid $balloon-size isDark ? #272f3a : #fff
-
> div
padding 8px 0
- > *
+ > .hr
+ margin 8px 0
+ border-top solid 1px isDark ? rgba(#000, 0.3) : rgba(#000, 0.1)
+
+ > *:not(.hr)
display block
padding 8px 16px
color isDark ? #cdd0d8 : #666
diff --git a/src/server/api/endpoints/notes/mentions.ts b/src/server/api/endpoints/notes/mentions.ts
index 3b2e262e4f..8675a9f562 100644
--- a/src/server/api/endpoints/notes/mentions.ts
+++ b/src/server/api/endpoints/notes/mentions.ts
@@ -27,6 +27,9 @@ export const meta = {
untilId: $.type(ID).optional.note({
}),
+
+ visibility: $.str.optional.note({
+ }),
}
};
@@ -52,6 +55,10 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) =
_id: -1
};
+ if (ps.visibility) {
+ query.visibility = ps.visibility;
+ }
+
if (ps.following) {
const followingIds = await getFriendIds(user._id);
diff --git a/src/services/note/create.ts b/src/services/note/create.ts
index 7daf83b294..7c1e71dcb3 100644
--- a/src/services/note/create.ts
+++ b/src/services/note/create.ts
@@ -142,6 +142,14 @@ export default async (user: IUser, data: Option, silent = false) => new Promise<
mentionedUsers.push(await User.findOne({ _id: data.reply.userId }));
}
+ if (data.visibility == 'specified') {
+ data.visibleUsers.forEach(u => {
+ if (!mentionedUsers.some(x => x._id.equals(u._id))) {
+ mentionedUsers.push(u);
+ }
+ });
+ }
+
const note = await insertNote(user, data, tags, mentionedUsers);
res(note);
@@ -188,7 +196,7 @@ export default async (user: IUser, data: Option, silent = false) => new Promise<
const nm = new NotificationManager(user, note);
const nmRelatedPromises = [];
- createMentionedEvents(mentionedUsers, noteObj, nm);
+ createMentionedEvents(mentionedUsers, note, nm);
const noteActivity = await renderActivity(data, note);
@@ -318,7 +326,7 @@ async function publish(user: IUser, note: INote, noteObj: any, reply: INote, ren
if (['public', 'home', 'followers'].includes(note.visibility)) {
// フォロワーに配信
- publishToFollowers(note, noteObj, user, noteActivity);
+ publishToFollowers(note, user, noteActivity);
}
// リストに配信
@@ -456,7 +464,7 @@ async function publishToUserLists(note: INote, noteObj: any) {
});
}
-async function publishToFollowers(note: INote, noteObj: any, user: IUser, noteActivity: any) {
+async function publishToFollowers(note: INote, user: IUser, noteActivity: any) {
const detailPackedNote = await pack(note, null, {
detail: true,
skipHide: true
@@ -505,9 +513,13 @@ function deliverNoteToMentionedRemoteUsers(mentionedUsers: IUser[], user: ILocal
});
}
-function createMentionedEvents(mentionedUsers: IUser[], noteObj: any, nm: NotificationManager) {
+function createMentionedEvents(mentionedUsers: IUser[], note: INote, nm: NotificationManager) {
mentionedUsers.filter(u => isLocalUser(u)).forEach(async (u) => {
- publishUserStream(u._id, 'mention', noteObj);
+ const detailPackedNote = await pack(note, u, {
+ detail: true
+ });
+
+ publishUserStream(u._id, 'mention', detailPackedNote);
// Create notification
nm.push(u._id, 'mention');