From b0c7cb88035b5ca723de2a5a08cd2840214d9b97 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Mon, 5 Mar 2018 08:44:37 +0900
Subject: [PATCH] wip

---
 src/api/endpoints/posts/create.ts             | 13 ++++++
 src/api/models/post.ts                        |  9 ++++
 .../views/components/post-form-window.vue     | 13 +++++-
 .../desktop/views/components/post-form.vue    | 21 ++++++++-
 .../app/mobile/views/components/post-form.vue | 24 +++++++++--
 src/web/docs/api/entities/post.yaml           | 43 +++++++++++++++++++
 6 files changed, 117 insertions(+), 6 deletions(-)

diff --git a/src/api/endpoints/posts/create.ts b/src/api/endpoints/posts/create.ts
index a9d52fd128..15cbc4845c 100644
--- a/src/api/endpoints/posts/create.ts
+++ b/src/api/endpoints/posts/create.ts
@@ -39,6 +39,18 @@ module.exports = (params, user: IUser, app) => new Promise(async (res, rej) => {
 	const [tags = [], tagsErr] = $(params.tags).optional.array('string').unique().eachQ(t => t.range(1, 32)).$;
 	if (tagsErr) return rej('invalid tags');
 
+	// Get 'geo' parameter
+	const [geo, geoErr] = $(params.geo).optional.nullable.strict.object()
+		.have('latitude', $().number().range(-180, 180))
+		.have('longitude', $().number().range(-90, 90))
+		.have('altitude', $().nullable.number())
+		.have('accuracy', $().nullable.number())
+		.have('altitudeAccuracy', $().nullable.number())
+		.have('heading', $().nullable.number().range(0, 360))
+		.have('speed', $().nullable.number())
+		.$;
+	if (geoErr) return rej('invalid geo');
+
 	// Get 'media_ids' parameter
 	const [mediaIds, mediaIdsErr] = $(params.media_ids).optional.array('id').unique().range(1, 4).$;
 	if (mediaIdsErr) return rej('invalid media_ids');
@@ -244,6 +256,7 @@ module.exports = (params, user: IUser, app) => new Promise(async (res, rej) => {
 		user_id: user._id,
 		app_id: app ? app._id : null,
 		via_mobile: viaMobile,
+		geo,
 
 		// 以下非正規化データ
 		_reply: reply ? { user_id: reply.user_id } : undefined,
diff --git a/src/api/models/post.ts b/src/api/models/post.ts
index edb69e0c15..c37c8371c0 100644
--- a/src/api/models/post.ts
+++ b/src/api/models/post.ts
@@ -32,6 +32,15 @@ export type IPost = {
 	category: string;
 	is_category_verified: boolean;
 	via_mobile: boolean;
+	geo: {
+		latitude: number;
+		longitude: number;
+		altitude: number;
+		accuracy: number;
+		altitudeAccuracy: number;
+		heading: number;
+		speed: number;
+	};
 };
 
 /**
diff --git a/src/web/app/desktop/views/components/post-form-window.vue b/src/web/app/desktop/views/components/post-form-window.vue
index 4427f59829..31a07a890e 100644
--- a/src/web/app/desktop/views/components/post-form-window.vue
+++ b/src/web/app/desktop/views/components/post-form-window.vue
@@ -1,6 +1,7 @@
 <template>
 <mk-window ref="window" is-modal @closed="$destroy">
 	<span slot="header">
+		<span :class="$style.icon" v-if="geo">%fa:map-marker-alt%</span>
 		<span v-if="!reply">%i18n:desktop.tags.mk-post-form-window.post%</span>
 		<span v-if="reply">%i18n:desktop.tags.mk-post-form-window.reply%</span>
 		<span :class="$style.count" v-if="media.length != 0">{{ '%i18n:desktop.tags.mk-post-form-window.attaches%'.replace('{}', media.length) }}</span>
@@ -12,7 +13,8 @@
 		:reply="reply"
 		@posted="onPosted"
 		@change-uploadings="onChangeUploadings"
-		@change-attached-media="onChangeMedia"/>
+		@change-attached-media="onChangeMedia"
+		@geo-attached="onGeoAttached"/>
 </mk-window>
 </template>
 
@@ -24,7 +26,8 @@ export default Vue.extend({
 	data() {
 		return {
 			uploadings: [],
-			media: []
+			media: [],
+			geo: null
 		};
 	},
 	mounted() {
@@ -39,6 +42,9 @@ export default Vue.extend({
 		onChangeMedia(media) {
 			this.media = media;
 		},
+		onGeoAttached(geo) {
+			this.geo = geo;
+		},
 		onPosted() {
 			(this.$refs.window as any).close();
 		}
@@ -47,6 +53,9 @@ export default Vue.extend({
 </script>
 
 <style lang="stylus" module>
+.icon
+	margin-right 8px
+
 .count
 	margin-left 8px
 	opacity 0.8
diff --git a/src/web/app/desktop/views/components/post-form.vue b/src/web/app/desktop/views/components/post-form.vue
index 5cf5cffc6a..6e334e7ca8 100644
--- a/src/web/app/desktop/views/components/post-form.vue
+++ b/src/web/app/desktop/views/components/post-form.vue
@@ -27,6 +27,7 @@
 	<button class="drive" title="%i18n:desktop.tags.mk-post-form.attach-media-from-drive%" @click="chooseFileFromDrive">%fa:cloud%</button>
 	<button class="kao" title="%i18n:desktop.tags.mk-post-form.insert-a-kao%" @click="kao">%fa:R smile%</button>
 	<button class="poll" title="%i18n:desktop.tags.mk-post-form.create-poll%" @click="poll = true">%fa:chart-pie%</button>
+	<button class="geo" title="位置情報を添付する" @click="setGeo">%fa:map-marker-alt%</button>
 	<p class="text-count" :class="{ over: text.length > 1000 }">{{ '%i18n:desktop.tags.mk-post-form.text-remain%'.replace('{}', 1000 - text.length) }}</p>
 	<button :class="{ posting }" class="submit" :disabled="!canPost" @click="post">
 		{{ posting ? '%i18n:desktop.tags.mk-post-form.posting%' : submitText }}<mk-ellipsis v-if="posting"/>
@@ -53,6 +54,7 @@ export default Vue.extend({
 			files: [],
 			uploadings: [],
 			poll: false,
+			geo: null,
 			autocomplete: null,
 			draghover: false
 		};
@@ -193,6 +195,21 @@ export default Vue.extend({
 			}
 			//#endregion
 		},
+		setGeo() {
+			if (navigator.geolocation == null) {
+				alert('お使いの端末は位置情報に対応していません');
+				return;
+			}
+
+			navigator.geolocation.getCurrentPosition(pos => {
+				this.geo = pos.coords;
+				this.$emit('geo-attached', this.geo);
+			}, err => {
+				alert('エラー: ' + err.message);
+			}, {
+				enableHighAccuracy: true
+			});
+		},
 		post() {
 			this.posting = true;
 
@@ -201,7 +218,8 @@ export default Vue.extend({
 				media_ids: this.files.length > 0 ? this.files.map(f => f.id) : undefined,
 				reply_id: this.reply ? this.reply.id : undefined,
 				repost_id: this.repost ? this.repost.id : undefined,
-				poll: this.poll ? (this.$refs.poll as any).get() : undefined
+				poll: this.poll ? (this.$refs.poll as any).get() : undefined,
+				geo: this.geo,
 			}).then(data => {
 				this.clear();
 				this.deleteDraft();
@@ -459,6 +477,7 @@ export default Vue.extend({
 	> .drive
 	> .kao
 	> .poll
+	> .geo
 		display inline-block
 		cursor pointer
 		padding 0
diff --git a/src/web/app/mobile/views/components/post-form.vue b/src/web/app/mobile/views/components/post-form.vue
index d16d5d358b..559a6c1c4b 100644
--- a/src/web/app/mobile/views/components/post-form.vue
+++ b/src/web/app/mobile/views/components/post-form.vue
@@ -23,6 +23,7 @@
 		<button class="drive" @click="chooseFileFromDrive">%fa:cloud%</button>
 		<button class="kao" @click="kao">%fa:R smile%</button>
 		<button class="poll" @click="poll = true">%fa:chart-pie%</button>
+		<button class="geo" @click="setGeo">%fa:map-marker-alt%</button>
 		<input ref="file" class="file" type="file" accept="image/*" multiple="multiple" @change="onChangeFile"/>
 	</div>
 </div>
@@ -44,7 +45,8 @@ export default Vue.extend({
 			text: '',
 			uploadings: [],
 			files: [],
-			poll: false
+			poll: false,
+			geo: null
 		};
 	},
 	mounted() {
@@ -83,6 +85,20 @@ export default Vue.extend({
 		onChangeUploadings(uploads) {
 			this.$emit('change-uploadings', uploads);
 		},
+		setGeo() {
+			if (navigator.geolocation == null) {
+				alert('お使いの端末は位置情報に対応していません');
+				return;
+			}
+
+			navigator.geolocation.getCurrentPosition(pos => {
+				this.geo = pos.coords;
+			}, err => {
+				alert('エラー: ' + err.message);
+			}, {
+				enableHighAccuracy: true
+			});
+		},
 		clear() {
 			this.text = '';
 			this.files = [];
@@ -97,6 +113,7 @@ export default Vue.extend({
 				media_ids: this.files.length > 0 ? this.files.map(f => f.id) : undefined,
 				reply_id: this.reply ? this.reply.id : undefined,
 				poll: this.poll ? (this.$refs.poll as any).get() : undefined,
+				geo: this.geo,
 				via_mobile: viaMobile
 			}).then(data => {
 				this.$emit('post');
@@ -223,8 +240,9 @@ export default Vue.extend({
 
 		> .upload
 		> .drive
-		.kao
-		.poll
+		> .kao
+		> .poll
+		> .geo
 			display inline-block
 			padding 0
 			margin 0
diff --git a/src/web/docs/api/entities/post.yaml b/src/web/docs/api/entities/post.yaml
index e4359ffd0c..451d2579d9 100644
--- a/src/web/docs/api/entities/post.yaml
+++ b/src/web/docs/api/entities/post.yaml
@@ -128,3 +128,46 @@ props:
             desc:
               ja: "この選択肢に投票された数"
               en: "The number voted for this choice"
+  - name: "geo"
+    type: "object"
+    optional: true
+    desc:
+      ja: "位置情報"
+      en: "Geo location"
+    defName: "geo"
+    def:
+      - name: "latitude"
+        type: "number"
+        optional: false
+        desc:
+          ja: "緯度。-180〜180で表す。"
+      - name: "longitude"
+        type: "number"
+        optional: false
+        desc:
+          ja: "経度。-90〜90で表す。"
+      - name: "altitude"
+        type: "number"
+        optional: false
+        desc:
+          ja: "高度。メートル単位で表す。"
+      - name: "accuracy"
+        type: "number"
+        optional: false
+        desc:
+          ja: "緯度、経度の精度。メートル単位で表す。"
+      - name: "altitudeAccuracy"
+        type: "number"
+        optional: false
+        desc:
+          ja: "高度の精度。メートル単位で表す。"
+      - name: "heading"
+        type: "number"
+        optional: false
+        desc:
+          ja: "方角。0〜360の角度で表す。0が北、90が東、180が南、270が西。"
+      - name: "speed"
+        type: "number"
+        optional: false
+        desc:
+          ja: "速度。メートル / 秒数で表す。"