| @@ -96,7 +96,6 @@ | |||||||
|     "gulp-babel": "6.1.2", |     "gulp-babel": "6.1.2", | ||||||
|     "gulp-cssnano": "2.1.2", |     "gulp-cssnano": "2.1.2", | ||||||
|     "gulp-imagemin": "3.1.1", |     "gulp-imagemin": "3.1.1", | ||||||
|     "gulp-livescript": "3.0.1", |  | ||||||
|     "gulp-pug": "3.2.0", |     "gulp-pug": "3.2.0", | ||||||
|     "gulp-rename": "1.2.2", |     "gulp-rename": "1.2.2", | ||||||
|     "gulp-replace": "0.5.4", |     "gulp-replace": "0.5.4", | ||||||
| @@ -108,7 +107,6 @@ | |||||||
|     "is-root": "1.0.0", |     "is-root": "1.0.0", | ||||||
|     "is-url": "1.2.2", |     "is-url": "1.2.2", | ||||||
|     "js-yaml": "3.8.1", |     "js-yaml": "3.8.1", | ||||||
|     "livescript": "1.5.0", |  | ||||||
|     "mime-types": "2.1.14", |     "mime-types": "2.1.14", | ||||||
|     "mocha": "3.2.0", |     "mocha": "3.2.0", | ||||||
|     "mongodb": "2.2.24", |     "mongodb": "2.2.24", | ||||||
|   | |||||||
| @@ -106,21 +106,25 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \api | 		this.mixin('api'); | ||||||
|  |  | ||||||
| 		@session = @opts.session | 		this.session = this.opts.session; | ||||||
| 		@app = @session.app | 		this.app = this.session.app; | ||||||
|  |  | ||||||
| 		@cancel = ~> | 		this.cancel = () => { | ||||||
| 			@api \auth/deny do | 			this.api('auth/deny', { | ||||||
| 				token: @session.token | 				token: this.session.token | ||||||
| 			.then ~> | 			}).then(() => { | ||||||
| 				@trigger \denied | 				this.trigger('denied'); | ||||||
|  | 			}); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@accept = ~> | 		this.accept = () => { | ||||||
| 			@api \auth/accept do | 			this.api('auth/accept', { | ||||||
| 				token: @session.token | 				token: this.session.token | ||||||
| 			.then ~> | 			}).then(() => { | ||||||
| 				@trigger \accepted | 				this.trigger('accepted'); | ||||||
|  | 			}); | ||||||
|  | 		}; | ||||||
| 	</script> | 	</script> | ||||||
| </mk-form> | </mk-form> | ||||||
|   | |||||||
| @@ -88,50 +88,60 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \i | 		this.mixin('i'); | ||||||
| 		@mixin \api | 		this.mixin('api'); | ||||||
|  |  | ||||||
| 		@state = null | 		this.state = null; | ||||||
| 		@fetching = true | 		this.fetching = true; | ||||||
|  |  | ||||||
| 		@token = window.location.href.split \/ .pop! | 		this.token = window.location.href.split('/').pop(); | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			if not @SIGNIN then return | 			if (!this.SIGNIN) return; | ||||||
|  |  | ||||||
| 			# Fetch session | 			// Fetch session | ||||||
| 			@api \auth/session/show do | 			this.api('auth/session/show', { | ||||||
| 				token: @token | 				token: this.token | ||||||
| 			.then (session) ~> | 			}).then(session => { | ||||||
| 				@session = session | 				this.session = session; | ||||||
| 				@fetching = false | 				this.fetching = false; | ||||||
|  |  | ||||||
| 				# 既に連携していた場合 | 				// 既に連携していた場合 | ||||||
| 				if @session.app.is_authorized | 				if (this.session.app.is_authorized) { | ||||||
| 					@api \auth/accept do | 					this.api('auth/accept', { | ||||||
| 						token: @session.token | 						token: this.session.token | ||||||
| 					.then ~> | 					}).then(() => { | ||||||
| 						@accepted! | 						this.accepted(); | ||||||
| 				else | 					}); | ||||||
| 					@state = \waiting | 				} else { | ||||||
| 					@update! | 					this.update({ | ||||||
|  | 						state: 'waiting' | ||||||
|  | 					}); | ||||||
|  |  | ||||||
| 					@refs.form.on \denied ~> | 					this.refs.form.on('denied', () => { | ||||||
| 						@state = \denied | 						this.update({ | ||||||
| 						@update! | 							state: 'denied' | ||||||
|  | 						}); | ||||||
|  | 					}); | ||||||
|  |  | ||||||
| 					@refs.form.on \accepted @accepted | 					this.refs.form.on('accepted', this.accepted); | ||||||
|  | 				} | ||||||
|  | 			}).catch(error => { | ||||||
|  | 				this.update({ | ||||||
|  | 					fetching: false, | ||||||
|  | 					state: 'fetch-session-error' | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 			.catch (error) ~> | 		this.accepted = () => { | ||||||
| 				@fetching = false | 			this.update({ | ||||||
| 				@state = \fetch-session-error | 				state: 'accepted' | ||||||
| 				@update! | 			}); | ||||||
|  |  | ||||||
| 		@accepted = ~> | 			if (this.session.app.callback_url) { | ||||||
| 			@state = \accepted | 				location.href = this.session.app.callback_url + '?token=' + this.session.token; | ||||||
| 			@update! | 			} | ||||||
|  | 		}; | ||||||
| 			if @session.app.callback_url |  | ||||||
| 				location.href = @session.app.callback_url + '?token=' + @session.token |  | ||||||
| 	</script> | 	</script> | ||||||
| </mk-index> | </mk-index> | ||||||
|   | |||||||
| @@ -27,13 +27,16 @@ riot.mixin({ | |||||||
| // ↓ iOS待ちPolyfill (SEE: http://caniuse.com/#feat=fetch) | // ↓ iOS待ちPolyfill (SEE: http://caniuse.com/#feat=fetch) | ||||||
| require('whatwg-fetch'); | require('whatwg-fetch'); | ||||||
|  |  | ||||||
| // ↓ NodeList、HTMLCollectionで forEach を使えるようにする | // ↓ NodeList、HTMLCollection、FileListで forEach を使えるようにする | ||||||
| if (NodeList.prototype.forEach === undefined) { | if (NodeList.prototype.forEach === undefined) { | ||||||
| 	NodeList.prototype.forEach = Array.prototype.forEach; | 	NodeList.prototype.forEach = Array.prototype.forEach; | ||||||
| } | } | ||||||
| if (HTMLCollection.prototype.forEach === undefined) { | if (HTMLCollection.prototype.forEach === undefined) { | ||||||
| 	HTMLCollection.prototype.forEach = Array.prototype.forEach; | 	HTMLCollection.prototype.forEach = Array.prototype.forEach; | ||||||
| } | } | ||||||
|  | if (FileList.prototype.forEach === undefined) { | ||||||
|  | 	FileList.prototype.forEach = Array.prototype.forEach; | ||||||
|  | } | ||||||
|  |  | ||||||
| // ↓ iOSでプライベートモードだとlocalStorageが使えないので既存のメソッドを上書きする | // ↓ iOSでプライベートモードだとlocalStorageが使えないので既存のメソッドを上書きする | ||||||
| try { | try { | ||||||
|   | |||||||
							
								
								
									
										8
									
								
								src/web/app/common/scripts/contains.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/web/app/common/scripts/contains.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | module.exports = function(parent, child) { | ||||||
|  | 	let node = child.parentNode; | ||||||
|  | 	while (node) { | ||||||
|  | 		if (node == parent) return true; | ||||||
|  | 		node = node.parentNode; | ||||||
|  | 	} | ||||||
|  | 	return false; | ||||||
|  | } | ||||||
| @@ -21,6 +21,6 @@ | |||||||
| 					text-decoration underline | 					text-decoration underline | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \i | 		this.mixin('i'); | ||||||
| 	</script> | 	</script> | ||||||
| </mk-api-info> | </mk-api-info> | ||||||
|   | |||||||
| @@ -17,18 +17,17 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \api | 		this.mixin('api'); | ||||||
|  |  | ||||||
| 		@apps = [] | 		this.apps = []; | ||||||
| 		@fetching = true | 		this.fetching = true; | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			@api \i/authorized_apps | 			this.api('i/authorized_apps').then(apps => { | ||||||
| 			.then (apps) ~> | 				this.apps = apps; | ||||||
| 				@apps = apps | 				this.fetching = false; | ||||||
| 				@fetching = false | 				this.update(); | ||||||
| 				@update! | 			}); | ||||||
| 			.catch (err) ~> | 		}); | ||||||
| 				console.error err |  | ||||||
| 	</script> | 	</script> | ||||||
| </mk-authorized-apps> | </mk-authorized-apps> | ||||||
|   | |||||||
| @@ -1,11 +1,7 @@ | |||||||
| <mk-copyright><span>(c) syuilo 2014-2017</span> | <mk-copyright> | ||||||
|  | 	<span>(c) syuilo 2014-2017</span> | ||||||
| 	<style> | 	<style> | ||||||
| 		:scope | 		:scope | ||||||
| 			display block | 			display block | ||||||
|  |  | ||||||
| 			 |  | ||||||
|  |  | ||||||
| 			 |  | ||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| </mk-copyright> | </mk-copyright> | ||||||
|   | |||||||
| @@ -1,8 +1,7 @@ | |||||||
| <mk-core-error> | <mk-core-error> | ||||||
| 	<!--i: i.fa.fa-times-circle--><img src="/_/resources/error.jpg" alt=""/> | 	<!--i: i.fa.fa-times-circle--> | ||||||
| 	<h1> | 	<img src="/_/resources/error.jpg" alt=""/> | ||||||
| 		<mk-ripple-string>サーバーに接続できません</mk-ripple-string> | 	<h1>サーバーに接続できません</h1> | ||||||
| 	</h1> |  | ||||||
| 	<p class="text">インターネット回線に問題があるか、サーバーがダウンまたはメンテナンスしている可能性があります。しばらくしてから<a onclick={ retry }>再度お試し</a>ください。</p> | 	<p class="text">インターネット回線に問題があるか、サーバーがダウンまたはメンテナンスしている可能性があります。しばらくしてから<a onclick={ retry }>再度お試し</a>ください。</p> | ||||||
| 	<p class="thanks">いつもMisskeyをご利用いただきありがとうございます。</p> | 	<p class="thanks">いつもMisskeyをご利用いただきありがとうございます。</p> | ||||||
| 	<style> | 	<style> | ||||||
| @@ -57,8 +56,9 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@retry = ~> | 		this.retry = () => { | ||||||
| 			@unmount! | 			this.unmount(); | ||||||
| 			@opts.retry! | 			this.opts.retry(); | ||||||
|  | 		} | ||||||
| 	</script> | 	</script> | ||||||
| </mk-core-error> | </mk-core-error> | ||||||
|   | |||||||
| @@ -20,10 +20,5 @@ | |||||||
| 					opacity 1 | 					opacity 1 | ||||||
| 				40% | 				40% | ||||||
| 					opacity 0 | 					opacity 0 | ||||||
|  |  | ||||||
| 			 |  | ||||||
|  |  | ||||||
| 			 |  | ||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| </mk-ellipsis> | </mk-ellipsis> | ||||||
|   | |||||||
| @@ -5,6 +5,6 @@ | |||||||
| 			display inline | 			display inline | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@kind = @opts.type.split \/ .0 | 		this.kind = this.opts.type.split('/')[0]; | ||||||
| 	</script> | 	</script> | ||||||
| </mk-file-type-icon> | </mk-file-type-icon> | ||||||
|   | |||||||
| @@ -1,7 +1,6 @@ | |||||||
| require('./core-error.tag'); | require('./core-error.tag'); | ||||||
| require('./url.tag'); | require('./url.tag'); | ||||||
| require('./url-preview.tag'); | require('./url-preview.tag'); | ||||||
| require('./ripple-string.tag'); |  | ||||||
| require('./time.tag'); | require('./time.tag'); | ||||||
| require('./file-type-icon.tag'); | require('./file-type-icon.tag'); | ||||||
| require('./uploader.tag'); | require('./uploader.tag'); | ||||||
| @@ -24,3 +23,4 @@ require('./messaging/room.tag'); | |||||||
| require('./messaging/message.tag'); | require('./messaging/message.tag'); | ||||||
| require('./messaging/index.tag'); | require('./messaging/index.tag'); | ||||||
| require('./messaging/form.tag'); | require('./messaging/form.tag'); | ||||||
|  | require('./stream-indicator.tag'); | ||||||
|   | |||||||
| @@ -21,9 +21,5 @@ | |||||||
| 					margin 0 | 					margin 0 | ||||||
| 					text-align center | 					text-align center | ||||||
|  |  | ||||||
| 			 |  | ||||||
|  |  | ||||||
| 			 |  | ||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| </mk-introduction> | </mk-introduction> | ||||||
|   | |||||||
| @@ -2,9 +2,15 @@ | |||||||
| 	<textarea ref="text" onkeypress={ onkeypress } onpaste={ onpaste } placeholder="ここにメッセージを入力"></textarea> | 	<textarea ref="text" onkeypress={ onkeypress } onpaste={ onpaste } placeholder="ここにメッセージを入力"></textarea> | ||||||
| 	<div class="files"></div> | 	<div class="files"></div> | ||||||
| 	<mk-uploader ref="uploader"></mk-uploader> | 	<mk-uploader ref="uploader"></mk-uploader> | ||||||
| 	<button class="send" onclick={ send } disabled={ sending } title="メッセージを送信"><i class="fa fa-paper-plane" if={ !sending }></i><i class="fa fa-spinner fa-spin" if={ sending }></i></button> | 	<button class="send" onclick={ send } disabled={ sending } title="メッセージを送信"> | ||||||
| 	<button class="attach-from-local" type="button" title="PCから画像を添付する"><i class="fa fa-upload"></i></button> | 		<i class="fa fa-paper-plane" if={ !sending }></i><i class="fa fa-spinner fa-spin" if={ sending }></i> | ||||||
| 	<button class="attach-from-drive" type="button" title="アルバムから画像を添付する"><i class="fa fa-folder-open"></i></button> | 	</button> | ||||||
|  | 	<button class="attach-from-local" type="button" title="PCから画像を添付する"> | ||||||
|  | 		<i class="fa fa-upload"></i> | ||||||
|  | 	</button> | ||||||
|  | 	<button class="attach-from-drive" type="button" title="アルバムから画像を添付する"> | ||||||
|  | 		<i class="fa fa-folder-open"></i> | ||||||
|  | 	</button> | ||||||
| 	<input name="file" type="file" accept="image/*"/> | 	<input name="file" type="file" accept="image/*"/> | ||||||
| 	<style> | 	<style> | ||||||
| 		:scope | 		:scope | ||||||
| @@ -111,49 +117,60 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \api | 		this.mixin('api'); | ||||||
|  |  | ||||||
| 		@onpaste = (e) ~> | 		this.onpaste = (e) => { | ||||||
| 			data = e.clipboard-data | 			const data = e.clipboardData; | ||||||
| 			items = data.items | 			const items = data.items; | ||||||
| 			for i from 0 to items.length - 1 | 			for (let i = 0; i < items.length; i++) { | ||||||
| 				item = items[i] | 				const item = items[i]; | ||||||
| 				switch (item.kind) | 				if (item.kind == 'file') { | ||||||
| 					| \file => | 					this.upload(item.getAsFile()); | ||||||
| 						@upload item.get-as-file! | 				} | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@onkeypress = (e) ~> | 		this.onkeypress = (e) => { | ||||||
| 			if (e.which == 10 || e.which == 13) && e.ctrl-key | 			if ((e.which == 10 || e.which == 13) && e.ctrlKey) { | ||||||
| 				@send! | 				this.send(); | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@select-file = ~> | 		this.selectFile = () => { | ||||||
| 			@refs.file.click! | 			this.refs.file.click(); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@select-file-from-drive = ~> | 		this.selectFileFromDrive = () => { | ||||||
| 			browser = document.body.append-child document.create-element \mk-select-file-from-drive-window | 			const browser = document.body.appendChild(document.createElement('mk-select-file-from-drive-window')); | ||||||
| 			event = riot.observable! | 			const event = riot.observable(); | ||||||
| 			riot.mount browser, do | 			riot.mount(browser, { | ||||||
| 				multiple: true | 				multiple: true, | ||||||
| 				event: event | 				event: event | ||||||
| 			event.one \selected (files) ~> | 			}); | ||||||
| 				files.for-each @add-file | 			event.one('selected', files => { | ||||||
|  | 				files.forEach(this.addFile); | ||||||
|  | 			}); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@send = ~> | 		this.send = () => { | ||||||
| 			@sending = true | 			this.sending = true; | ||||||
| 			@api \messaging/messages/create do | 			this.api('messaging/messages/create', { | ||||||
| 				user_id: @opts.user.id | 				user_id: this.opts.user.id, | ||||||
| 				text: @refs.text.value | 				text: this.refs.text.value | ||||||
| 			.then (message) ~> | 			}).then(message => { | ||||||
| 				@clear! | 				this.clear(); | ||||||
| 			.catch (err) ~> | 			}).catch(err => { | ||||||
| 				console.error err | 				console.error(err); | ||||||
| 			.then ~> | 			}).then(() => { | ||||||
| 				@sending = false | 				this.sending = false; | ||||||
| 				@update! | 				this.update(); | ||||||
|  | 			}); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@clear = ~> | 		this.clear = () => { | ||||||
| 			@refs.text.value = '' | 			this.refs.text.value = ''; | ||||||
| 			@files = [] | 			this.files = []; | ||||||
| 			@update! | 			this.update(); | ||||||
|  | 		}; | ||||||
| 	</script> | 	</script> | ||||||
| </mk-messaging-form> | </mk-messaging-form> | ||||||
|   | |||||||
| @@ -286,72 +286,88 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \i | 		this.mixin('i'); | ||||||
| 		@mixin \api | 		this.mixin('api'); | ||||||
|  |  | ||||||
| 		@search-result = [] | 		this.searchResult = []; | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			@api \messaging/history | 			this.api('messaging/history').then(history => { | ||||||
| 			.then (history) ~> | 				this.isLoading = false; | ||||||
| 				@is-loading = false | 				history.forEach(message => { | ||||||
| 				history.for-each (message) ~> | 					message.is_me = message.user_id == this.I.id | ||||||
| 					message.is_me = message.user_id == @I.id | 					message._click = () => { | ||||||
| 					message._click = ~> | 						this.trigger('navigate-user', message.is_me ? message.recipient : message.user); | ||||||
| 						if message.is_me | 					}; | ||||||
| 							@trigger \navigate-user message.recipient | 				}); | ||||||
| 						else | 				this.history = history; | ||||||
| 							@trigger \navigate-user message.user | 				this.update(); | ||||||
| 				@history = history | 			}); | ||||||
| 				@update! | 		}); | ||||||
| 			.catch (err) ~> |  | ||||||
| 				console.error err |  | ||||||
|  |  | ||||||
| 		@search = ~> | 		this.search = () => { | ||||||
| 			q = @refs.search.value | 			const q = this.refs.search.value; | ||||||
| 			if q == '' | 			if (q == '') { | ||||||
| 				@search-result = [] | 				this.searchResult = []; | ||||||
| 			else | 				return; | ||||||
| 				@api \users/search do | 			} | ||||||
| 					query: q | 			this.api('users/search', { | ||||||
| 					max: 5 | 				query: q, | ||||||
| 				.then (users) ~> | 				max: 5 | ||||||
| 					users.for-each (user) ~> | 			}).then(users => { | ||||||
| 						user._click = ~> | 				users.forEach(user => { | ||||||
| 							@trigger \navigate-user user | 					user._click = () => { | ||||||
| 							@search-result = [] | 						this.trigger('navigate-user', user); | ||||||
| 					@search-result = users | 						this.searchResult = []; | ||||||
| 					@update! | 					}; | ||||||
| 				.catch (err) ~> | 				}); | ||||||
| 					console.error err | 				this.update({ | ||||||
|  | 					searchResult: users | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@on-search-keydown = (e) ~> | 		this.onSearchKeydown = e => { | ||||||
| 			key = e.which | 			switch (e.which) { | ||||||
| 			switch (key) | 				case 9: // [TAB] | ||||||
| 				| 9, 40 => # Key[TAB] or Key[↓] | 				case 40: // [↓] | ||||||
| 					e.prevent-default! | 					e.preventDefault(); | ||||||
| 					e.stop-propagation! | 					e.stopPropagation(); | ||||||
| 					@refs.search-result.child-nodes[0].focus! | 					this.refs.searchResult.childNodes[0].focus(); | ||||||
|  | 					break; | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@on-search-result-keydown = (i, e) ~> | 		this.onSearchResultKeydown = (i, e) => { | ||||||
| 			key = e.which | 			const cancel = () => { | ||||||
| 			switch (key) | 				e.preventDefault(); | ||||||
| 				| 10, 13 => # Key[ENTER] | 				e.stopPropagation(); | ||||||
| 					e.prevent-default! | 			}; | ||||||
| 					e.stop-propagation! | 			switch (true) { | ||||||
| 					@search-result[i]._click! | 				case e.which == 10: // [ENTER] | ||||||
| 				| 27 => # Key[ESC] | 				case e.which == 13: // [ENTER] | ||||||
| 					e.prevent-default! | 					cancel(); | ||||||
| 					e.stop-propagation! | 					this.searchResult[i]._click(); | ||||||
| 					@refs.search.focus! | 					break; | ||||||
| 				| 38 => # Key[↑] |  | ||||||
| 					e.prevent-default! | 				case e.which == 27: // [ESC] | ||||||
| 					e.stop-propagation! | 					cancel(); | ||||||
| 					(@refs.search-result.child-nodes[i].previous-element-sibling || @refs.search-result.child-nodes[@search-result.length - 1]).focus! | 					this.refs.search.focus(); | ||||||
| 				| 9, 40 => # Key[TAB] or Key[↓] | 					break; | ||||||
| 					e.prevent-default! |  | ||||||
| 					e.stop-propagation! | 				case e.which == 9 && e.shiftKey: // [TAB] + [Shift] | ||||||
| 					(@refs.search-result.child-nodes[i].next-element-sibling || @refs.search-result.child-nodes[0]).focus! | 				case e.which == 38: // [↑] | ||||||
|  | 					cancel(); | ||||||
|  | 					(this.refs.searchResult.childNodes[i].previousElementSibling || this.refs.searchResult.childNodes[this.searchResult.length - 1]).focus(); | ||||||
|  | 					break; | ||||||
|  |  | ||||||
|  | 				case e.which == 9: // [TAB] | ||||||
|  | 				case e.which == 40: // [↓] | ||||||
|  | 					cancel(); | ||||||
|  | 					(this.refs.searchResult.childNodes[i].nextElementSibling || this.refs.searchResult.childNodes[0]).focus(); | ||||||
|  | 					break; | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 	</script> | 	</script> | ||||||
| </mk-messaging> | </mk-messaging> | ||||||
|   | |||||||
| @@ -203,28 +203,32 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \i | 		this.mixin('i'); | ||||||
| 		@mixin \text | 		this.mixin('text'); | ||||||
|  |  | ||||||
| 		@message = @opts.message | 		this.message = this.opts.message; | ||||||
| 		@message.is_me = @message.user.id == @I.id | 		this.message.is_me = this.message.user.id == this.I.id; | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			if @message.text? | 			if (this.message.text) { | ||||||
| 				tokens = @analyze @message.text | 				const tokens = this.analyze(this.message.text); | ||||||
|  |  | ||||||
| 				@refs.text.innerHTML = @compile tokens | 				this.refs.text.innerHTML = this.compile(tokens); | ||||||
|  |  | ||||||
| 				@refs.text.children.for-each (e) ~> | 				this.refs.text.children.forEach(e => { | ||||||
| 					if e.tag-name == \MK-URL | 					if (e.tagName == 'MK-URL') riot.mount(e); | ||||||
| 						riot.mount e | 				}); | ||||||
|  |  | ||||||
| 				# URLをプレビュー | 				// URLをプレビュー | ||||||
| 				tokens | 				tokens | ||||||
| 					.filter (t) -> t.type == \link | 					.filter(t => t.type == 'link') | ||||||
| 					.map (t) ~> | 					.map(t => { | ||||||
| 						@preview = @refs.text.append-child document.create-element \mk-url-preview | 						const el = this.refs.text.appendChild(document.createElement('mk-url-preview')); | ||||||
| 						riot.mount @preview, do | 						riot.mount(el, { | ||||||
| 							url: t.content | 							url: t.content | ||||||
|  | 						}); | ||||||
|  | 					}); | ||||||
|  | 			} | ||||||
|  | 		}); | ||||||
| 	</script> | 	</script> | ||||||
| </mk-messaging-message> | </mk-messaging-message> | ||||||
|   | |||||||
| @@ -124,101 +124,117 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \i | 		this.mixin('i'); | ||||||
| 		@mixin \api | 		this.mixin('api'); | ||||||
| 		@mixin \messaging-stream | 		this.mixin('messaging-stream'); | ||||||
|  |  | ||||||
| 		@user = @opts.user | 		this.user = this.opts.user; | ||||||
| 		@init = true | 		this.init = true; | ||||||
| 		@sending = false | 		this.sending = false; | ||||||
| 		@messages = [] | 		this.messages = []; | ||||||
|  |  | ||||||
| 		@connection = new @MessagingStreamConnection @I, @user.id | 		this.connection = new this.MessagingStreamConnection(this.I, this.user.id); | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			@connection.event.on \message @on-message | 			this.connection.event.on('message', this.onMessage); | ||||||
| 			@connection.event.on \read @on-read | 			this.connection.event.on('read', this.onRead); | ||||||
|  |  | ||||||
| 			document.add-event-listener \visibilitychange @on-visibilitychange | 			document.addEventListener('visibilitychange', this.onVisibilitychange); | ||||||
|  |  | ||||||
| 			@api \messaging/messages do | 			this.api('messaging/messages', { | ||||||
| 				user_id: @user.id | 				user_id: this.user.id | ||||||
| 			.then (messages) ~> | 			}).then(messages => { | ||||||
| 				@init = false | 				this.init = false; | ||||||
| 				@messages = messages.reverse! | 				this.messages = messages.reverse(); | ||||||
| 				@update! | 				this.update(); | ||||||
| 				@scroll-to-bottom! | 				this.scrollToBottom(); | ||||||
| 			.catch (err) ~> | 			}); | ||||||
| 				console.error err | 		}); | ||||||
|  |  | ||||||
| 		@on \unmount ~> | 		this.on('unmount', () => { | ||||||
| 			@connection.event.off \message @on-message | 			this.connection.event.off('message', this.onMessage); | ||||||
| 			@connection.event.off \read @on-read | 			this.connection.event.off('read', this.onRead); | ||||||
| 			@connection.close! | 			this.connection.close(); | ||||||
|  |  | ||||||
| 			document.remove-event-listener \visibilitychange @on-visibilitychange | 			document.removeEventListener('visibilitychange', this.onVisibilitychange); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@on \update ~> | 		this.on('update', () => { | ||||||
| 			@messages.for-each (message) ~> | 			this.messages.forEach(message => { | ||||||
| 				date = (new Date message.created_at).get-date! | 				const date = (new Date(message.created_at)).getDate(); | ||||||
| 				month = (new Date message.created_at).get-month! + 1 | 				const month = (new Date(message.created_at)).getMonth() + 1; | ||||||
| 				message._date = date | 				message._date = date; | ||||||
| 				message._datetext = month + '月 ' + date + '日' | 				message._datetext = month + '月 ' + date + '日'; | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@on-message = (message) ~> | 		this.onMessage = (message) => { | ||||||
| 			is-bottom = @is-bottom! | 			const isbottom = this.isBottom(); | ||||||
|  |  | ||||||
| 			@messages.push message | 			this.messages.push(message); | ||||||
| 			if message.user_id != @I.id and not document.hidden | 			if (message.user_id != this.I.id && !document.hidden) { | ||||||
| 				@connection.socket.send JSON.stringify do | 				this.connection.socket.send(JSON.stringify({ | ||||||
| 					type: \read | 					type: 'read', | ||||||
| 					id: message.id | 					id: message.id | ||||||
| 			@update! | 				})); | ||||||
|  | 			} | ||||||
|  | 			this.update(); | ||||||
|  |  | ||||||
| 			if is-bottom | 			if (isBottom) { | ||||||
| 				# Scroll to bottom | 				// Scroll to bottom | ||||||
| 				@scroll-to-bottom! | 				this.scrollToBottom(); | ||||||
| 			else if message.user_id != @I.id | 			} else if (message.user_id != this.I.id) { | ||||||
| 				# Notify | 				// Notify | ||||||
| 				@notify '新しいメッセージがあります' | 				this.notify('新しいメッセージがあります'); | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@on-read = (ids) ~> | 		this.onRead = ids => { | ||||||
| 			if not Array.isArray ids then ids = [ids] | 			if (!Array.isArray(ids)) ids = [ids]; | ||||||
| 			ids.for-each (id) ~> | 			ids.forEach(id => { | ||||||
| 				if (@messages.some (x) ~> x.id == id) | 				if (this.messages.some(x => x.id == id)) { | ||||||
| 					exist = (@messages.map (x) -> x.id).index-of id | 					const exist = this.messages.map(x => x.id).indexOf(id); | ||||||
| 					@messages[exist].is_read = true | 					this.messages[exist].is_read = true; | ||||||
| 					@update! | 					this.update(); | ||||||
|  | 				} | ||||||
|  | 			}); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@is-bottom = ~> | 		this.isBottom = () => { | ||||||
| 			current = @root.scroll-top + @root.offset-height | 			const current = this.root.scrollTop + this.root.offsetHeight; | ||||||
| 			max = @root.scroll-height | 			const max = this.root.scrollHeight; | ||||||
| 			current > (max - 32) | 			return current > (max - 32); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@scroll-to-bottom = ~> | 		this.scrollToBottom = () => { | ||||||
| 			@root.scroll-top = @root.scroll-height | 			this.root.scrollTop = this.root.scrollHeight; | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@notify = (message) ~> | 		this.notify = message => { | ||||||
| 			n = document.create-element \p | 			const n = document.createElement('p'); | ||||||
| 			n.inner-HTML = '<i class="fa fa-arrow-circle-down"></i>' + message | 			n.innerHTML = '<i class="fa fa-arrow-circle-down"></i>' + message; | ||||||
| 			n.onclick = ~> | 			n.onclick = () => { | ||||||
| 				@scroll-to-bottom! | 				this.scrollToBottom(); | ||||||
| 				n.parent-node.remove-child n | 				n.parentNode.removeChild(n); | ||||||
| 			@refs.notifications.append-child n | 			}; | ||||||
|  | 			this.refs.notifications.appendChild(n); | ||||||
|  |  | ||||||
| 			set-timeout ~> | 			setTimeout(() => { | ||||||
| 				n.style.opacity = 0 | 				n.style.opacity = 0; | ||||||
| 				set-timeout ~> | 				setTimeout(() => n.parentNode.removeChild(n), 1000); | ||||||
| 					n.parent-node.remove-child n | 			}, 4000); | ||||||
| 				, 1000ms | 		}; | ||||||
| 			, 4000ms |  | ||||||
|  |  | ||||||
| 		@on-visibilitychange = ~> | 		this.onVisibilitychange = () => { | ||||||
| 			if document.hidden then return | 			if (document.hidden) return; | ||||||
| 			@messages.for-each (message) ~> | 			this.messages.forEach(message => { | ||||||
| 				if message.user_id != @I.id and not message.is_read | 				if (message.user_id !== this.I.id && !message.is_read) { | ||||||
| 					@connection.socket.send JSON.stringify do | 					this.connection.socket.send(JSON.stringify({ | ||||||
| 						type: \read | 						type: 'read', | ||||||
| 						id: message.id | 						id: message.id | ||||||
|  | 					})); | ||||||
|  | 				} | ||||||
|  | 			}); | ||||||
|  | 		}; | ||||||
| 	</script> | 	</script> | ||||||
| </mk-messaging-room> | </mk-messaging-room> | ||||||
|   | |||||||
| @@ -2,17 +2,17 @@ | |||||||
| 	<style> | 	<style> | ||||||
| 		:scope | 		:scope | ||||||
| 			display inline | 			display inline | ||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			# バグ? https://github.com/riot/riot/issues/2103 | 			// https://github.com/riot/riot/issues/2103 | ||||||
| 			#value = @opts.value | 			//value = this.opts.value | ||||||
| 			value = @opts.riot-value | 			let value = this.opts.riotValue; | ||||||
| 			max = @opts.max | 			const max = this.opts.max; | ||||||
|  |  | ||||||
| 			if max? then if value > max then value = max | 			if (max != null && value > max) value = max; | ||||||
|  |  | ||||||
| 			@root.innerHTML = value.to-locale-string! | 			this.root.innerHTML = value.toLocaleString(); | ||||||
|  | 		}); | ||||||
| 	</script> | 	</script> | ||||||
| </mk-number> | </mk-number> | ||||||
|   | |||||||
| @@ -86,26 +86,31 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@choices = ['', ''] | 		this.choices = ['', '']; | ||||||
|  |  | ||||||
| 		@oninput = (i, e) ~> | 		this.oninput = (i, e) => { | ||||||
| 			@choices[i] = e.target.value | 			this.choices[i] = e.target.value; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		@add = ~> | 		this.add = () => { | ||||||
| 			@choices.push '' | 			this.choices.push(''); | ||||||
| 			@update! | 			this.update(); | ||||||
| 			@refs.choices.child-nodes[@choices.length - 1].child-nodes[0].focus! | 			this.refs.choices.childNodes[this.choices.length - 1].childNodes[0].focus(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		@remove = (i) ~> | 		this.remove = (i) => { | ||||||
| 			@choices = @choices.filter((_, _i) -> _i != i) | 			this.choices = this.choices.filter((_, _i) => _i != i); | ||||||
| 			@update! | 			this.update(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		@destroy = ~> | 		this.destroy = () => { | ||||||
| 			@opts.ondestroy! | 			this.opts.ondestroy(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		@get = ~> | 		this.get = () => { | ||||||
| 			return { | 			return { | ||||||
| 				choices: @choices.filter (choice) -> choice != '' | 				choices: this.choices.filter(choice => choice != '') | ||||||
| 			} | 			} | ||||||
|  | 		} | ||||||
| 	</script> | 	</script> | ||||||
| </mk-poll-editor> | </mk-poll-editor> | ||||||
|   | |||||||
| @@ -68,32 +68,37 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \api | 		this.mixin('api'); | ||||||
|  |  | ||||||
| 		@post = @opts.post | 		this.post = this.opts.post; | ||||||
| 		@poll = @post.poll | 		this.poll = this.post.poll; | ||||||
| 		@total = @poll.choices.reduce ((a, b) -> a + b.votes), 0 | 		this.total = this.poll.choices.reduce((a, b) => a + b.votes, 0); | ||||||
| 		@is-voted = @poll.choices.some (c) -> c.is_voted | 		this.isVoted = this.poll.choices.some(c => c.is_voted); | ||||||
| 		@result = @is-voted | 		this.result = this.isVoted; | ||||||
|  |  | ||||||
| 		@toggle-result = ~> | 		this.toggleResult = () => { | ||||||
| 			@result = !@result | 			this.result = !this.result; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		@vote = (id) ~> | 		this.vote = (id) => { | ||||||
| 			if (@poll.choices.some (c) -> c.is_voted) then return | 			if (this.poll.choices.some(c => c.is_voted)) return; | ||||||
| 			@api \posts/polls/vote do | 			this.api('posts/polls/vote', { | ||||||
| 				post_id: @post.id | 				post_id: this.post.id, | ||||||
| 				choice: id | 				choice: id | ||||||
| 			.then ~> | 			}).then(() => { | ||||||
| 				@poll.choices.for-each (c) -> | 				this.poll.choices.forEach(c => { | ||||||
| 					if c.id == id | 					if (c.id == id) { | ||||||
| 						c.votes++ | 						c.votes++; | ||||||
| 						c.is_voted = true | 						c.is_voted = true; | ||||||
| 				@update do | 					} | ||||||
| 					poll: @poll | 				}); | ||||||
| 					is-voted: true | 				this.update({ | ||||||
| 					result: true | 					poll: this.poll, | ||||||
| 					total: @total + 1 | 					isVoted: true, | ||||||
|  | 					result: true, | ||||||
|  | 					total: this.total + 1 | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
| 	</script> | 	</script> | ||||||
| </mk-poll> | </mk-poll> | ||||||
|   | |||||||
| @@ -4,5 +4,5 @@ | |||||||
| 			display inline | 			display inline | ||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script>@root.innerHTML = @opts.content</script> | 	<script>this.root.innerHTML = this.opts.content</script> | ||||||
| </mk-raw> | </mk-raw> | ||||||
|   | |||||||
| @@ -1,26 +0,0 @@ | |||||||
| <mk-ripple-string><yield/> |  | ||||||
| 	<style> |  | ||||||
| 		:scope |  | ||||||
| 			display inline |  | ||||||
|  |  | ||||||
| 			> span |  | ||||||
| 				animation ripple-string 5s infinite ease-in-out both |  | ||||||
|  |  | ||||||
| 			@keyframes ripple-string |  | ||||||
| 				0%, 50%, 100% |  | ||||||
| 					opacity 1 |  | ||||||
| 				25% |  | ||||||
| 					opacity 0.5 |  | ||||||
|  |  | ||||||
| 	</style> |  | ||||||
| 	<script> |  | ||||||
| 		@on \mount ~> |  | ||||||
| 			text = @root.innerHTML |  | ||||||
| 			@root.innerHTML = '' |  | ||||||
| 			(text.split '').for-each (c, i) ~> |  | ||||||
| 				ce = document.create-element \span |  | ||||||
| 				ce.innerHTML = c |  | ||||||
| 				ce.style.animation-delay = (i / 10) + 's' |  | ||||||
| 				@root.append-child ce |  | ||||||
| 	</script> |  | ||||||
| </mk-ripple-string> |  | ||||||
| @@ -48,28 +48,30 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \api | 		this.mixin('api'); | ||||||
| 		@mixin \stream | 		this.mixin('stream'); | ||||||
|  |  | ||||||
| 		@history = [] | 		this.history = []; | ||||||
| 		@fetching = true | 		this.fetching = true; | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			@api \i/signin_history | 			this.api('i/signin_history').then(history => { | ||||||
| 			.then (history) ~> | 				this.update({ | ||||||
| 				@history = history | 					fetching: false, | ||||||
| 				@fetching = false | 					history: history | ||||||
| 				@update! | 				}); | ||||||
| 			.catch (err) ~> | 			}); | ||||||
| 				console.error err |  | ||||||
|  |  | ||||||
| 			@stream.on \signin @on-signin | 			this.stream.on('signin', this.onSignin); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@on \unmount ~> | 		this.on('unmount', () => { | ||||||
| 			@stream.off \signin @on-signin | 			this.stream.off('signin', this.onSignin); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@on-signin = (signin) ~> | 		this.onSignin = signin => { | ||||||
| 			@history.unshift signin | 			this.history.unshift(signin); | ||||||
| 			@update! | 			this.update(); | ||||||
|  | 		}; | ||||||
| 	</script> | 	</script> | ||||||
| </mk-signin-history> | </mk-signin-history> | ||||||
|   | |||||||
| @@ -97,42 +97,50 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \api | 		this.mixin('api'); | ||||||
|  |  | ||||||
| 		@user = null | 		this.user = null; | ||||||
| 		@signing = false | 		this.signing = false; | ||||||
|  |  | ||||||
| 		@oninput = ~> | 		this.oninput = () => { | ||||||
| 			@api \users/show do | 			this.api('users/show', { | ||||||
| 				username: @refs.username.value | 				username: this.refs.username.value | ||||||
| 			.then (user) ~> | 			}).then(user => { | ||||||
| 				@user = user | 				this.user = user; | ||||||
| 				@trigger \user user | 				this.trigger('user', user); | ||||||
| 				@update! | 				this.update(); | ||||||
|  | 			}); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@onsubmit = (e) ~> | 		this.onsubmit = e => { | ||||||
| 			e.prevent-default! | 			e.preventDefault(); | ||||||
|  |  | ||||||
| 			if @refs.username.value == '' | 			if (this.refs.username.value == '') { | ||||||
| 				@refs.username.focus! | 				this.refs.username.focus(); | ||||||
| 				return false | 				return false; | ||||||
| 			if @refs.password.value == '' | 			} | ||||||
| 				@refs.password.focus! | 			if (this.refs.password.value == '') { | ||||||
| 				return false | 				this.refs.password.focus(); | ||||||
|  | 				return false; | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			@signing = true | 			this.update({ | ||||||
| 			@update! | 				signing: true | ||||||
|  | 			}); | ||||||
|  |  | ||||||
| 			@api \signin do | 			this.api('signin', { | ||||||
| 				username: @refs.username.value | 				username: this.refs.username.value, | ||||||
| 				password: @refs.password.value | 				password: this.refs.password.value | ||||||
| 			.then ~> | 			}).then(() => { | ||||||
| 				location.reload! | 				location.reload(); | ||||||
| 			.catch ~> | 			}).catch(() => { | ||||||
| 				alert 'something happened' | 				alert('something happened'); | ||||||
| 				@signing = false | 				this.update({ | ||||||
| 				@update! | 					signing: false | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
|  |  | ||||||
| 			false | 			return false; | ||||||
|  | 		}; | ||||||
| 	</script> | 	</script> | ||||||
| </mk-signin> | </mk-signin> | ||||||
|   | |||||||
| @@ -174,120 +174,126 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \api | 		this.mixin('api'); | ||||||
| 		@mixin \get-password-strength | 		this.mixin('get-password-strength'); | ||||||
|  |  | ||||||
| 		@username-state = null | 		this.usernameState = null; | ||||||
| 		@password-strength = '' | 		this.passwordStrength = ''; | ||||||
| 		@password-retype-state = null | 		this.passwordRetypeState = null; | ||||||
| 		@recaptchaed = false | 		this.recaptchaed = false; | ||||||
|  |  | ||||||
| 		window.on-recaptchaed = ~> | 		window.onEecaptchaed = () => { | ||||||
| 			@recaptchaed = true | 			this.recaptchaed = true; | ||||||
| 			@update! | 			this.update(); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		window.on-recaptcha-expired = ~> | 		window.onRecaptchaExpired = () => { | ||||||
| 			@recaptchaed = false | 			this.recaptchaed = false; | ||||||
| 			@update! | 			this.update(); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			head = (document.get-elements-by-tag-name \head).0 | 			const head = document.getElementsByTagName('head')[0]; | ||||||
| 			script = document.create-element \script | 			const script = document.createElement('script'); | ||||||
| 				..set-attribute \src \https://www.google.com/recaptcha/api.js | 			script.setAttribute('src', 'https://www.google.com/recaptcha/api.js'); | ||||||
| 			head.append-child script | 			head.appendChild(script); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@on-change-username = ~> | 		this.onChangeUsername = () => { | ||||||
| 			username = @refs.username.value | 			const username = this.refs.username.value; | ||||||
|  |  | ||||||
| 			if username == '' | 			if (username == '') { | ||||||
| 				@username-state = null | 				this.update({ | ||||||
| 				@update! | 					usernameState: null | ||||||
| 				return | 				}); | ||||||
|  | 				return; | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			err = switch | 			const err = | ||||||
| 				| not username.match /^[a-zA-Z0-9\-]+$/ => \invalid-format | 				!username.match(/^[a-zA-Z0-9\-]+$/) ? 'invalid-format' : | ||||||
| 				| username.length < 3chars              => \min-range | 				username.length < 3 ? 'min-range' : | ||||||
| 				| username.length > 20chars             => \max-range | 				username.length > 20 ? 'max-range' : | ||||||
| 				| _                                     => null | 				null; | ||||||
|  |  | ||||||
| 			if err? | 			if (err) { | ||||||
| 				@username-state = err | 				this.update({ | ||||||
| 				@update! | 					usernameState: err | ||||||
| 			else | 				}); | ||||||
| 				@username-state = \wait | 				return; | ||||||
| 				@update! | 			} | ||||||
|  |  | ||||||
| 				@api \username/available do | 			this.update({ | ||||||
| 					username: username | 				usernameState: 'wait' | ||||||
| 				.then (result) ~> | 			}); | ||||||
| 					if result.available |  | ||||||
| 						@username-state = \ok |  | ||||||
| 					else |  | ||||||
| 						@username-state = \unavailable |  | ||||||
| 					@update! |  | ||||||
| 				.catch (err) ~> |  | ||||||
| 					@username-state = \error |  | ||||||
| 					@update! |  | ||||||
|  |  | ||||||
| 		@on-change-password = ~> | 			this.api('username/available', { | ||||||
| 			password = @refs.password.value |  | ||||||
|  |  | ||||||
| 			if password == '' |  | ||||||
| 				@password-strength = '' |  | ||||||
| 				return |  | ||||||
|  |  | ||||||
| 			strength = @get-password-strength password |  | ||||||
|  |  | ||||||
| 			if strength > 0.3 |  | ||||||
| 				@password-strength = \medium |  | ||||||
| 				if strength > 0.7 |  | ||||||
| 					@password-strength = \high |  | ||||||
| 			else |  | ||||||
| 				@password-strength = \low |  | ||||||
|  |  | ||||||
| 			@update! |  | ||||||
|  |  | ||||||
| 			@refs.password-metar.style.width = (strength * 100) + \% |  | ||||||
|  |  | ||||||
| 		@on-change-password-retype = ~> |  | ||||||
| 			password = @refs.password.value |  | ||||||
| 			retyped-password = @refs.password-retype.value |  | ||||||
|  |  | ||||||
| 			if retyped-password == '' |  | ||||||
| 				@password-retype-state = null |  | ||||||
| 				return |  | ||||||
|  |  | ||||||
| 			if password == retyped-password |  | ||||||
| 				@password-retype-state = \match |  | ||||||
| 			else |  | ||||||
| 				@password-retype-state = \not-match |  | ||||||
|  |  | ||||||
| 		@onsubmit = (e) ~> |  | ||||||
| 			e.prevent-default! |  | ||||||
|  |  | ||||||
| 			username = @refs.username.value |  | ||||||
| 			password = @refs.password.value |  | ||||||
|  |  | ||||||
| 			locker = document.body.append-child document.create-element \mk-locker |  | ||||||
|  |  | ||||||
| 			@api \signup do |  | ||||||
| 				username: username | 				username: username | ||||||
| 				password: password | 			}).then(result => { | ||||||
| 				'g-recaptcha-response': grecaptcha.get-response! | 				this.update({ | ||||||
| 			.then ~> | 					usernameState: result.available ? 'ok' : 'unavailable' | ||||||
| 				@api \signin do | 				}); | ||||||
| 					username: username | 			}).catch(err => { | ||||||
|  | 				this.update({ | ||||||
|  | 					usernameState: 'error' | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		this.onChangePassword = () => { | ||||||
|  | 			const password = this.refs.password.value; | ||||||
|  |  | ||||||
|  | 			if (password == '') { | ||||||
|  | 				this.passwordStrength = ''; | ||||||
|  | 				return; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			const strength = this.getPasswordStrength(password); | ||||||
|  | 			this.passwordStrength = strength > 0.7 ? 'high' : strength > 0.3 ? 'medium' : 'low'; | ||||||
|  | 			this.update(); | ||||||
|  | 			this.refs.passwordMetar.style.width = `${strength * 100}%`; | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		this.onChangePasswordRetype = () => { | ||||||
|  | 			const password = this.refs.password.value; | ||||||
|  | 			const retypedPassword = this.refs.passwordRetype.value; | ||||||
|  |  | ||||||
|  | 			if (retypedPassword == '') { | ||||||
|  | 				this.passwordRetypeState = null; | ||||||
|  | 				return; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			this.passwordRetypeState = password == retypedPassword ? 'match' : 'not-match'; | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		this.onsubmit = e => { | ||||||
|  | 			e.preventDefault(); | ||||||
|  |  | ||||||
|  | 			const username = this.refs.username.value; | ||||||
|  | 			const password = this.refs.password.value; | ||||||
|  |  | ||||||
|  | 			const locker = document.body.appendChild(document.createElement('mk-locker')); | ||||||
|  |  | ||||||
|  | 			this.api('signup', { | ||||||
|  | 				username: username, | ||||||
|  | 				password: password, | ||||||
|  | 				'g-recaptcha-response': grecaptcha.getResponse() | ||||||
|  | 			}).then(() => { | ||||||
|  | 				this.api('signin', { | ||||||
|  | 					username: username, | ||||||
| 					password: password | 					password: password | ||||||
| 				.then ~> | 				}).then(() => { | ||||||
| 					location.href = CONFIG.url | 					location.href = CONFIG.url | ||||||
| 			.catch ~> | 				}); | ||||||
| 				alert '何らかの原因によりアカウントの作成に失敗しました。再度お試しください。' | 			}).catch(() => { | ||||||
|  | 				alert('何らかの原因によりアカウントの作成に失敗しました。再度お試しください。'); | ||||||
|  |  | ||||||
| 				grecaptcha.reset! | 				grecaptcha.reset(); | ||||||
| 				@recaptchaed = false | 				this.recaptchaed = false; | ||||||
|  |  | ||||||
| 				locker.parent-node.remove-child locker | 				locker.parentNode.removeChild(locker); | ||||||
|  | 			}); | ||||||
|  |  | ||||||
| 			false | 			return false; | ||||||
|  | 		}; | ||||||
| 	</script> | 	</script> | ||||||
| </mk-signup> | </mk-signup> | ||||||
|   | |||||||
| @@ -20,8 +20,8 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		now = new Date! | 		const now = new Date(); | ||||||
| 		@d = now.get-date! | 		this.d = now.getDate(); | ||||||
| 		@m = now.get-month! + 1 | 		this.m = now.getMonth() + 1; | ||||||
| 	</script> | 	</script> | ||||||
| </mk-special-message> | </mk-special-message> | ||||||
|   | |||||||
							
								
								
									
										65
									
								
								src/web/app/common/tags/stream-indicator.tag
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								src/web/app/common/tags/stream-indicator.tag
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | |||||||
|  | <mk-stream-indicator> | ||||||
|  | 	<p if={ state == 'initializing' }> | ||||||
|  | 		<i class="fa fa-spinner fa-spin"></i> | ||||||
|  | 		<span>接続中<mk-ellipsis></mk-ellipsis></span> | ||||||
|  | 	</p> | ||||||
|  | 	<p if={ state == 'reconnecting' }> | ||||||
|  | 		<i class="fa fa-spinner fa-spin"></i> | ||||||
|  | 		<span>切断されました 接続中<mk-ellipsis></mk-ellipsis></span> | ||||||
|  | 	</p> | ||||||
|  | 	<p if={ state == 'connected' }> | ||||||
|  | 		<i class="fa fa-check"></i> | ||||||
|  | 		<span>接続完了</span> | ||||||
|  | 	</p> | ||||||
|  | 	<style> | ||||||
|  | 		:scope | ||||||
|  | 			display block | ||||||
|  | 			pointer-events none | ||||||
|  | 			position fixed | ||||||
|  | 			z-index 16384 | ||||||
|  | 			bottom 8px | ||||||
|  | 			right 8px | ||||||
|  | 			margin 0 | ||||||
|  | 			padding 6px 12px | ||||||
|  | 			font-size 0.9em | ||||||
|  | 			color #fff | ||||||
|  | 			background rgba(0, 0, 0, 0.8) | ||||||
|  |  | ||||||
|  | 			> p | ||||||
|  | 				display block | ||||||
|  | 				margin 0 | ||||||
|  |  | ||||||
|  | 				> i | ||||||
|  | 					margin-right 0.25em | ||||||
|  |  | ||||||
|  | 	</style> | ||||||
|  | 	<script> | ||||||
|  | 		this.mixin('stream'); | ||||||
|  |  | ||||||
|  | 		this.on('before-mount', () => { | ||||||
|  | 			this.state = this.getStreamState(); | ||||||
|  |  | ||||||
|  | 			if (this.state == 'connected') { | ||||||
|  | 				this.root.style.opacity = 0; | ||||||
|  | 			} | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		this.streamStateEv.on('connected', () => { | ||||||
|  | 			this.state = this.getStreamState(); | ||||||
|  | 			this.update(); | ||||||
|  | 			setTimeout(() => { | ||||||
|  | 				Velocity(this.root, { | ||||||
|  | 					opacity: 0 | ||||||
|  | 				}, 200, 'linear'); | ||||||
|  | 			}, 1000); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		this.streamStateEv.on('closed', () => { | ||||||
|  | 			this.state = this.getStreamState(); | ||||||
|  | 			this.update(); | ||||||
|  | 			Velocity(this.root, { | ||||||
|  | 				opacity: 1 | ||||||
|  | 			}, 0); | ||||||
|  | 		}); | ||||||
|  | 	</script> | ||||||
|  | </mk-stream-indicator> | ||||||
| @@ -1,41 +1,50 @@ | |||||||
| <mk-time> | <mk-time> | ||||||
| 	<time datetime={ opts.time }><span if={ mode == 'relative' }>{ relative }</span><span if={ mode == 'absolute' }>{ absolute }</span><span if={ mode == 'detail' }>{ absolute } ({ relative })</span></time> | 	<time datetime={ opts.time }> | ||||||
|  | 		<span if={ mode == 'relative' }>{ relative }</span> | ||||||
|  | 		<span if={ mode == 'absolute' }>{ absolute }</span> | ||||||
|  | 		<span if={ mode == 'detail' }>{ absolute } ({ relative })</span> | ||||||
|  | 	</time> | ||||||
| 	<script> | 	<script> | ||||||
| 		@time = new Date @opts.time | 		this.time = new Date(this.opts.time); | ||||||
| 		@mode = @opts.mode || \relative | 		this.mode = this.opts.mode || 'relative'; | ||||||
| 		@tickid = null | 		this.tickid = null; | ||||||
|  |  | ||||||
| 		@absolute = | 		this.absolute = | ||||||
| 			@time.get-full-year! + \年 + | 			this.time.getFullYear()  + '年' + | ||||||
| 			@time.get-month! + 1 + \月 + | 			this.time.getMonth() + 1 + '月' + | ||||||
| 			@time.get-date!      + \日 + | 			this.time.getDate()      + '日' + | ||||||
| 			' ' + | 			' ' + | ||||||
| 			@time.get-hours!     + \時 + | 			this.time.getHours()     + '時' + | ||||||
| 			@time.get-minutes!   + \分 | 			this.time.getMinutes()   + '分'; | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			if @mode == \relative or @mode == \detail | 			if (this.mode == 'relative' || this.mode == 'detail') { | ||||||
| 				@tick! | 				this.tick(); | ||||||
| 				@tickid = set-interval @tick, 1000ms | 				this.tickid = setInterval(this.tick, 1000); | ||||||
|  | 			} | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@on \unmount ~> | 		this.on('unmount', () => { | ||||||
| 			if @mode == \relative or @mode == \detail | 			if (this.mode === 'relative' || this.mode === 'detail') { | ||||||
| 				clear-interval @tickid | 				clearInterval(this.tickid); | ||||||
|  | 			} | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@tick = ~> | 		this.tick = () => { | ||||||
| 			now = new Date! | 			const now = new Date(); | ||||||
| 			ago = (now - @time) / 1000ms | 			const ago = (now - this.time) / 1000/*ms*/; | ||||||
| 			@relative = switch | 			this.relative = | ||||||
| 				| ago >= 31536000s => ~~(ago / 31536000s) + '年前' | 				ago >= 31536000 ? ~~(ago / 31536000) + '年前' : | ||||||
| 				| ago >= 2592000s  => ~~(ago / 2592000s)  + 'ヶ月前' | 				ago >= 2592000  ? ~~(ago / 2592000)  + 'ヶ月前' : | ||||||
| 				| ago >= 604800s   => ~~(ago / 604800s)   + '週間前' | 				ago >= 604800   ? ~~(ago / 604800)   + '週間前' : | ||||||
| 				| ago >= 86400s    => ~~(ago / 86400s)    + '日前' | 				ago >= 86400    ? ~~(ago / 86400)    + '日前' : | ||||||
| 				| ago >= 3600s     => ~~(ago / 3600s)     + '時間前' | 				ago >= 3600     ? ~~(ago / 3600)     + '時間前' : | ||||||
| 				| ago >= 60s       => ~~(ago / 60s)       + '分前' | 				ago >= 60       ? ~~(ago / 60)       + '分前' : | ||||||
| 				| ago >= 10s       => ~~(ago % 60s)       + '秒前' | 				ago >= 10       ? ~~(ago % 60)       + '秒前' : | ||||||
| 				| ago >= 0s        =>                       'たった今' | 				ago >= 0        ?                      'たった今' : | ||||||
| 				| ago <  0s        =>                       '未来' | 				ago <  0        ?                      '未来' : | ||||||
| 				| _                =>                       'なぞのじかん' | 				                                       'なぞのじかん'; | ||||||
| 			@update! | 			this.update(); | ||||||
|  | 		}; | ||||||
| 	</script> | 	</script> | ||||||
| </mk-time> | </mk-time> | ||||||
|   | |||||||
| @@ -24,6 +24,6 @@ | |||||||
| 				color #8899a6 | 				color #8899a6 | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \i | 		this.mixin('i'); | ||||||
| 	</script> | 	</script> | ||||||
| </mk-twitter-setting> | </mk-twitter-setting> | ||||||
|   | |||||||
| @@ -140,56 +140,59 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \i | 		this.mixin('i'); | ||||||
|  |  | ||||||
| 		@uploads = [] | 		this.uploads = []; | ||||||
|  |  | ||||||
|  | 		this.upload = (file, folder) => { | ||||||
|  | 			const id = Math.random(); | ||||||
|  |  | ||||||
| 		@upload = (file, folder) ~> | 			const ctx = { | ||||||
| 			id = Math.random! | 				id: id, | ||||||
|  | 				name: file.name || 'untitled', | ||||||
| 			ctx = |  | ||||||
| 				id: id |  | ||||||
| 				name: file.name || \untitled |  | ||||||
| 				progress: undefined | 				progress: undefined | ||||||
|  | 			}; | ||||||
|  |  | ||||||
| 			@uploads.push ctx | 			this.uploads.push(ctx); | ||||||
| 			@trigger \change-uploads @uploads | 			this.trigger('change-uploads', this.uploads); | ||||||
| 			@update! | 			this.update(); | ||||||
|  |  | ||||||
| 			reader = new FileReader! | 			const reader = new FileReader(); | ||||||
| 			reader.onload = (e) ~> | 			reader.onload = e => { | ||||||
| 				ctx.img = e.target.result | 				ctx.img = e.target.result; | ||||||
| 				@update! | 				this.update(); | ||||||
| 			reader.read-as-data-URL file | 			}; | ||||||
|  | 			reader.readAsDataURL(file); | ||||||
|  |  | ||||||
| 			data = new FormData! | 			const data = new FormData(); | ||||||
| 			data.append \i @I.token | 			data.append('i', this.I.token); | ||||||
| 			data.append \file file | 			data.append('file', file); | ||||||
|  |  | ||||||
| 			if folder? | 			if (folder) data.append('folder_id', folder); | ||||||
| 				data.append \folder_id folder |  | ||||||
|  |  | ||||||
| 			xhr = new XMLHttpRequest! | 			const xhr = new XMLHttpRequest(); | ||||||
| 			xhr.open \POST CONFIG.apiUrl + '/drive/files/create' true | 			xhr.open('POST', CONFIG.apiUrl + '/drive/files/create', true); | ||||||
| 			xhr.onload = (e) ~> | 			xhr.onload = e => { | ||||||
| 				drive-file = JSON.parse e.target.response | 				const driveFile = JSON.parse(e.target.response); | ||||||
|  |  | ||||||
| 				@trigger \uploaded drive-file | 				this.trigger('uploaded', driveFile); | ||||||
|  |  | ||||||
| 				@uploads = @uploads.filter (x) -> x.id != id | 				this.uploads = this.uploads.filter(x => x.id != id); | ||||||
| 				@trigger \change-uploads @uploads | 				this.trigger('change-uploads', this.uploads); | ||||||
|  |  | ||||||
| 				@update! | 				this.update(); | ||||||
|  | 			}; | ||||||
|  |  | ||||||
| 			xhr.upload.onprogress = (e) ~> | 			xhr.upload.onprogress = e => { | ||||||
| 				if e.length-computable | 				if (e.lengthComputable) { | ||||||
| 					if ctx.progress == undefined | 					if (ctx.progress == undefined) ctx.progress = {}; | ||||||
| 						ctx.progress = {} | 					ctx.progress.max = e.total; | ||||||
| 					ctx.progress.max = e.total | 					ctx.progress.value = e.loaded; | ||||||
| 					ctx.progress.value = e.loaded | 					this.update(); | ||||||
| 					@update! | 				} | ||||||
|  | 			}; | ||||||
|  |  | ||||||
| 			xhr.send data | 			xhr.send(data); | ||||||
|  | 		}; | ||||||
| 	</script> | 	</script> | ||||||
| </mk-uploader> | </mk-uploader> | ||||||
|   | |||||||
| @@ -91,22 +91,24 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \api | 		this.mixin('api'); | ||||||
|  |  | ||||||
| 		@url = @opts.url | 		this.url = this.opts.url; | ||||||
| 		@loading = true | 		this.loading = true; | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			fetch CONFIG.url + '/api:url?url=' + @url | 			fetch(CONFIG.url + '/api:url?url=' + this.url).then(res => { | ||||||
| 			.then (res) ~> | 				res.json().then(info => { | ||||||
| 				info <~ res.json!.then | 					this.title = info.title; | ||||||
| 				@title = info.title | 					this.description = info.description; | ||||||
| 				@description = info.description | 					this.thumbnail = info.thumbnail; | ||||||
| 				@thumbnail = info.thumbnail | 					this.icon = info.icon; | ||||||
| 				@icon = info.icon | 					this.sitename = info.sitename; | ||||||
| 				@sitename = info.sitename |  | ||||||
|  |  | ||||||
| 				@loading = false | 					this.loading = false; | ||||||
| 				@update! | 					this.update(); | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
| 	</script> | 	</script> | ||||||
| </mk-url-preview> | </mk-url-preview> | ||||||
|   | |||||||
| @@ -30,19 +30,20 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@url = @opts.href | 		this.url = this.opts.href; | ||||||
|  |  | ||||||
| 		@on \before-mount ~> | 		this.on('before-mount', () => { | ||||||
| 			parser = document.create-element \a | 			parser = document.createElement('a');  | ||||||
| 			parser.href = @url | 			parser.href = this.url; | ||||||
|  |  | ||||||
| 			@schema = parser.protocol | 			this.schema = parser.protocol; | ||||||
| 			@hostname = parser.hostname | 			this.hostname = parser.hostname; | ||||||
| 			@port = parser.port | 			this.port = parser.port; | ||||||
| 			@pathname = parser.pathname | 			this.pathname = parser.pathname; | ||||||
| 			@query = parser.search | 			this.query = parser.search; | ||||||
| 			@hash = parser.hash | 			this.hash = parser.hash; | ||||||
|  |  | ||||||
| 			@update! | 			this.update(); | ||||||
|  | 		}); | ||||||
| 	</script> | 	</script> | ||||||
| </mk-url> | </mk-url> | ||||||
|   | |||||||
| @@ -6,100 +6,90 @@ | |||||||
| 				display block | 				display block | ||||||
| 				width 256px | 				width 256px | ||||||
| 				height 256px | 				height 256px | ||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@on \mount ~> | 		const Vec2 = function(x, y) { | ||||||
| 			@draw! | 			this.x = x; | ||||||
| 			@clock = set-interval @draw, 1000ms | 			this.y = y; | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@on \unmount ~> | 		this.on('mount', () => { | ||||||
| 			clear-interval @clock | 			this.draw() | ||||||
|  | 			this.clock = setInterval(this.draw, 1000); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@draw = ~> | 		this.on('unmount', () => { | ||||||
| 			now = new Date! | 			clearInterval(this.clock); | ||||||
| 			s = now.get-seconds! | 		}); | ||||||
| 			m = now.get-minutes! |  | ||||||
| 			h = now.get-hours! |  | ||||||
|  |  | ||||||
| 			vec2 = (x, y) -> | 		this.draw = () => { | ||||||
| 				@x = x | 			const now = new Date(); | ||||||
| 				@y = y | 			const s = now.getSeconds(); | ||||||
|  | 			const m = now.getMinutes(); | ||||||
|  | 			const h = now.getHours(); | ||||||
|  |  | ||||||
| 			ctx = @refs.canvas.get-context \2d | 			const ctx = this.refs.canvas.getContext('2d'); | ||||||
| 			canv-w = @refs.canvas.width | 			const canvW = this.refs.canvas.width; | ||||||
| 			canv-h = @refs.canvas.height | 			const canvH = this.refs.canvas.height; | ||||||
| 			ctx.clear-rect 0, 0, canv-w, canv-h | 			ctx.clearRect(0, 0, canvW, canvH); | ||||||
|  |  | ||||||
| 			# 背景 | 			{ // 背景 | ||||||
| 			center = (Math.min (canv-w / 2), (canv-h / 2)) | 				const center = Math.min((canvW / 2), (canvH / 2)); | ||||||
| 			line-start = center * 0.90 | 				const lineStart =    center * 0.90; | ||||||
| 			line-end-short = center * 0.87 | 				const shortLineEnd = center * 0.87; | ||||||
| 			line-end-long = center * 0.84 | 				const longLineEnd =  center * 0.84; | ||||||
| 			for i from 0 to 59 by 1 | 				for (let i = 0; i < 60; i++) { | ||||||
| 				angle = Math.PI * i / 30 | 					const angle = Math.PI * i / 30; | ||||||
| 				uv = new vec2 (Math.sin angle), (-Math.cos angle) | 					const uv = new Vec2(Math.sin(angle), -Math.cos(angle)); | ||||||
| 				ctx.begin-path! | 					ctx.beginPath(); | ||||||
| 				ctx.line-width = 1 | 					ctx.lineWidth = 1; | ||||||
| 				ctx.move-to do | 					ctx.moveTo((canvW / 2) + uv.x * lineStart, (canvH / 2) + uv.y * lineStart); | ||||||
| 					(canv-w / 2) + uv.x * line-start | 					if (i % 5 == 0) { | ||||||
| 					(canv-h / 2) + uv.y * line-start | 						ctx.strokeStyle = 'rgba(255, 255, 255, 0.2)'; | ||||||
| 				if i % 5 == 0 | 						ctx.lineTo((canvW / 2) + uv.x * longLineEnd, (canvH / 2) + uv.y * longLineEnd); | ||||||
| 					ctx.stroke-style = 'rgba(255, 255, 255, 0.2)' | 					} else { | ||||||
| 					ctx.line-to do | 						ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)'; | ||||||
| 						(canv-w / 2) + uv.x * line-end-long | 						ctx.lineTo((canvW / 2) + uv.x * shortLineEnd, (canvH / 2) + uv.y * shortLineEnd); | ||||||
| 						(canv-h / 2) + uv.y * line-end-long | 					} | ||||||
| 				else | 					ctx.stroke(); | ||||||
| 					ctx.stroke-style = 'rgba(255, 255, 255, 0.1)' | 				} | ||||||
| 					ctx.line-to do | 			} | ||||||
| 						(canv-w / 2) + uv.x * line-end-short |  | ||||||
| 						(canv-h / 2) + uv.y * line-end-short |  | ||||||
| 				ctx.stroke! |  | ||||||
|  |  | ||||||
| 			# 分 | 			{ // 分 | ||||||
| 			angle = Math.PI * (m + s / 60) / 30 | 				const angle = Math.PI * (m + s / 60) / 30; | ||||||
| 			length = (Math.min canv-w, canv-h) / 2.6 | 				const length = Math.min(canvW, canvH) / 2.6; | ||||||
| 			uv = new vec2 (Math.sin angle), (-Math.cos angle) | 				const uv = new Vec2(Math.sin(angle), -Math.cos(angle)); | ||||||
| 			ctx.begin-path! | 				ctx.beginPath(); | ||||||
| 			ctx.stroke-style = \#ffffff | 				ctx.strokeStyle = '#ffffff'; | ||||||
| 			ctx.line-width = 2 | 				ctx.lineWidth = 2; | ||||||
| 			ctx.move-to do | 				ctx.moveTo(canvW / 2 - uv.x * length / 5, canvH / 2 - uv.y * length / 5); | ||||||
| 				(canv-w / 2) - uv.x * length / 5 | 				ctx.lineTo(canvW / 2 + uv.x * length,     canvH / 2 + uv.y * length); | ||||||
| 				(canv-h / 2) - uv.y * length / 5 | 				ctx.stroke(); | ||||||
| 			ctx.line-to do | 			} | ||||||
| 				(canv-w / 2) + uv.x * length |  | ||||||
| 				(canv-h / 2) + uv.y * length |  | ||||||
| 			ctx.stroke! |  | ||||||
|  |  | ||||||
| 			# 時 | 			{ // 時 | ||||||
| 			angle = Math.PI * (h % 12 + m / 60) / 6 | 				const angle = Math.PI * (h % 12 + m / 60) / 6; | ||||||
| 			length = (Math.min canv-w, canv-h) / 4 | 				const length = Math.min(canvW, canvH) / 4; | ||||||
| 			uv = new vec2 (Math.sin angle), (-Math.cos angle) | 				const uv = new Vec2(Math.sin(angle), -Math.cos(angle)); | ||||||
| 			ctx.begin-path! | 				ctx.beginPath(); | ||||||
| 			#ctx.stroke-style = \#ffffff | 				ctx.strokeStyle = CONFIG.themeColor; | ||||||
| 			ctx.stroke-style = CONFIG.theme-color | 				ctx.lineWidth = 2; | ||||||
| 			ctx.line-width = 2 | 				ctx.moveTo(canvW / 2 - uv.x * length / 5, canvH / 2 - uv.y * length / 5); | ||||||
| 			ctx.move-to do | 				ctx.lineTo(canvW / 2 + uv.x * length,     canvH / 2 + uv.y * length); | ||||||
| 				(canv-w / 2) - uv.x * length / 5 | 				ctx.stroke(); | ||||||
| 				(canv-h / 2) - uv.y * length / 5 | 			} | ||||||
| 			ctx.line-to do |  | ||||||
| 				(canv-w / 2) + uv.x * length |  | ||||||
| 				(canv-h / 2) + uv.y * length |  | ||||||
| 			ctx.stroke! |  | ||||||
|  |  | ||||||
| 			# 秒 | 			{ // 秒 | ||||||
| 			angle = Math.PI * s / 30 | 				const angle = Math.PI * s / 30; | ||||||
| 			length = (Math.min canv-w, canv-h) / 2.6 | 				const length = Math.min(canvW, canvH) / 2.6; | ||||||
| 			uv = new vec2 (Math.sin angle), (-Math.cos angle) | 				const uv = new Vec2(Math.sin(angle), -Math.cos(angle)); | ||||||
| 			ctx.begin-path! | 				ctx.beginPath(); | ||||||
| 			ctx.stroke-style = 'rgba(255, 255, 255, 0.5)' | 				ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)'; | ||||||
| 			ctx.line-width = 1 | 				ctx.lineWidth = 1; | ||||||
| 			ctx.move-to do | 				ctx.moveTo(canvW / 2 - uv.x * length / 5, canvH / 2 - uv.y * length / 5); | ||||||
| 				(canv-w / 2) - uv.x * length / 5 | 				ctx.lineTo(canvW / 2 + uv.x * length,     canvH / 2 + uv.y * length); | ||||||
| 				(canv-h / 2) - uv.y * length / 5 | 				ctx.stroke(); | ||||||
| 			ctx.line-to do | 			} | ||||||
| 				(canv-w / 2) + uv.x * length | 		}; | ||||||
| 				(canv-h / 2) + uv.y * length |  | ||||||
| 			ctx.stroke! |  | ||||||
| 	</script> | 	</script> | ||||||
| </mk-analog-clock> | </mk-analog-clock> | ||||||
|   | |||||||
| @@ -80,108 +80,118 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \api | 		const contains = require('../../common/scripts/contains'); | ||||||
|  |  | ||||||
| 		@q = @opts.q | 		this.mixin('api'); | ||||||
| 		@textarea = @opts.textarea |  | ||||||
| 		@loading = true |  | ||||||
| 		@users = [] |  | ||||||
| 		@select = -1 |  | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.q = this.opts.q; | ||||||
| 			@textarea.add-event-listener \keydown @on-keydown | 		this.textarea = this.opts.textarea; | ||||||
|  | 		this.fetching = true; | ||||||
|  | 		this.users = []; | ||||||
|  | 		this.select = -1; | ||||||
|  |  | ||||||
| 			all = document.query-selector-all 'body *' | 		this.on('mount', () => { | ||||||
| 			Array.prototype.for-each.call all, (el) ~> | 			this.textarea.addEventListener('keydown', this.onKeydown); | ||||||
| 				el.add-event-listener \mousedown @mousedown |  | ||||||
|  |  | ||||||
| 			@api \users/search_by_username do | 			document.querySelectorAll('body *').forEach(el => { | ||||||
| 				query: @q | 				el.addEventListener('mousedown', this.mousedown); | ||||||
| 				limit: 30users | 			}); | ||||||
| 			.then (users) ~> |  | ||||||
| 				@users = users |  | ||||||
| 				@loading = false |  | ||||||
| 				@update! |  | ||||||
| 			.catch (err) ~> |  | ||||||
| 				console.error err |  | ||||||
|  |  | ||||||
| 		@on \unmount ~> | 			this.api('users/search_by_username', { | ||||||
| 			@textarea.remove-event-listener \keydown @on-keydown | 				query: this.q, | ||||||
|  | 				limit: 30 | ||||||
|  | 			}).then(users => { | ||||||
|  | 				this.update({ | ||||||
|  | 					fetching: false, | ||||||
|  | 					users: users | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 			all = document.query-selector-all 'body *' | 		this.on('unmount', () => { | ||||||
| 			Array.prototype.for-each.call all, (el) ~> | 			this.textarea.removeEventListener('keydown', this.onKeydown); | ||||||
| 				el.remove-event-listener \mousedown @mousedown |  | ||||||
|  |  | ||||||
| 		@mousedown = (e) ~> | 			document.querySelectorAll('body *').forEach(el => { | ||||||
| 			if (!contains @root, e.target) and (@root != e.target) | 				el.removeEventListener('mousedown', this.mousedown); | ||||||
| 				@close! | 			}); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@on-click = (e) ~> | 		this.mousedown = e => { | ||||||
| 			@complete e.item | 			if (!contains(this.root, e.target) && (this.root != e.target)) this.close(); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@on-keydown = (e) ~> | 		this.onClick = e => { | ||||||
| 			key = e.which | 			this.complete(e.item); | ||||||
| 			switch (key) | 		}; | ||||||
| 				| 10, 13 => # Key[ENTER] |  | ||||||
| 					if @select != -1 |  | ||||||
| 						e.prevent-default! |  | ||||||
| 						e.stop-propagation! |  | ||||||
| 						@complete @users[@select] |  | ||||||
| 					else |  | ||||||
| 						@close! |  | ||||||
| 				| 27 => # Key[ESC] |  | ||||||
| 					e.prevent-default! |  | ||||||
| 					e.stop-propagation! |  | ||||||
| 					@close! |  | ||||||
| 				| 38 => # Key[↑] |  | ||||||
| 					if @select != -1 |  | ||||||
| 						e.prevent-default! |  | ||||||
| 						e.stop-propagation! |  | ||||||
| 						@select-prev! |  | ||||||
| 					else |  | ||||||
| 						@close! |  | ||||||
| 				| 9, 40 => # Key[TAB] or Key[↓] |  | ||||||
| 					e.prevent-default! |  | ||||||
| 					e.stop-propagation! |  | ||||||
| 					@select-next! |  | ||||||
| 				| _ => |  | ||||||
| 					@close! |  | ||||||
|  |  | ||||||
| 		@select-next = ~> | 		this.onKeydown = e => { | ||||||
| 			@select++ | 			const cancel = () => { | ||||||
|  | 				e.preventDefault(); | ||||||
|  | 				e.stopPropagation(); | ||||||
|  | 			}; | ||||||
|  |  | ||||||
| 			if @select >= @users.length | 			switch (e.which) { | ||||||
| 				@select = 0 | 				case 10: // [ENTER] | ||||||
|  | 				case 13: // [ENTER] | ||||||
|  | 					if (this.select !== -1) { | ||||||
|  | 						cancel(); | ||||||
|  | 						this.complete(this.users[this.select]); | ||||||
|  | 					} else { | ||||||
|  | 						this.close(); | ||||||
|  | 					} | ||||||
|  | 					break; | ||||||
|  |  | ||||||
| 			@apply-select! | 				case 27: // [ESC] | ||||||
|  | 					cancel(); | ||||||
|  | 					this.close(); | ||||||
|  | 					break; | ||||||
|  |  | ||||||
| 		@select-prev = ~> | 				case 38: // [↑] | ||||||
| 			@select-- | 					if (this.select !== -1) { | ||||||
|  | 						cancel(); | ||||||
|  | 						this.selectPrev(); | ||||||
|  | 					} else { | ||||||
|  | 						this.close(); | ||||||
|  | 					} | ||||||
|  | 					break; | ||||||
|  |  | ||||||
| 			if @select < 0 | 				case 9: // [TAB] | ||||||
| 				@select = @users.length - 1 | 				case 40: // [↓] | ||||||
|  | 					cancel(); | ||||||
|  | 					this.selectNext(); | ||||||
|  | 					break; | ||||||
|  |  | ||||||
| 			@apply-select! | 				default: | ||||||
|  | 					this.close(); | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@apply-select = ~> | 		this.selectNext = () => { | ||||||
| 			@refs.users.children.for-each (el) ~> | 			if (++this.select >= this.users.length) this.select = 0; | ||||||
| 				el.remove-attribute \data-selected | 			this.applySelect(); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 			@refs.users.children[@select].set-attribute \data-selected \true | 		this.selectPrev = () => { | ||||||
| 			@refs.users.children[@select].focus! | 			if (--this.select < 0) this.select = this.users.length - 1; | ||||||
|  | 			this.applySelect(); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@complete = (user) ~> | 		this.applySelect = () => { | ||||||
| 			@opts.complete user | 			this.refs.users.children.forEach(el => { | ||||||
|  | 				el.removeAttribute('data-selected'); | ||||||
|  | 			}); | ||||||
|  |  | ||||||
| 		@close = ~> | 			this.refs.users.children[this.select].setAttribute('data-selected', 'true'); | ||||||
| 			@opts.close! | 			this.refs.users.children[this.select].focus(); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		this.complete = user => { | ||||||
|  | 			this.opts.complete(user); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		this.close = () => { | ||||||
|  | 			this.opts.close(); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		function contains(parent, child) |  | ||||||
| 			node = child.parent-node |  | ||||||
| 			while node? |  | ||||||
| 				if node == parent |  | ||||||
| 					return true |  | ||||||
| 				node = node.parent-node |  | ||||||
| 			return false |  | ||||||
| 	</script> | 	</script> | ||||||
| </mk-autocomplete-suggestion> | </mk-autocomplete-suggestion> | ||||||
|   | |||||||
| @@ -70,58 +70,74 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \api | 		this.mixin('api'); | ||||||
| 		@mixin \is-promise | 		this.mixin('is-promise'); | ||||||
| 		@mixin \stream | 		this.mixin('stream'); | ||||||
|  |  | ||||||
| 		@user = null | 		this.user = null; | ||||||
| 		@user-promise = if @is-promise @opts.user then @opts.user else Promise.resolve @opts.user | 		this.userPromise = this.isPromise(this.opts.user) | ||||||
| 		@init = true | 			? this.opts.user | ||||||
| 		@wait = false | 			: Promise.resolve(this.opts.user); | ||||||
|  | 		this.init = true; | ||||||
|  | 		this.wait = false; | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			@user-promise.then (user) ~> | 			this.userPromise.then(user => { | ||||||
| 				@user = user | 				this.update({ | ||||||
| 				@init = false | 					init: false, | ||||||
| 				@update! | 					user: user | ||||||
| 				@stream.on \follow @on-stream-follow | 				}); | ||||||
| 				@stream.on \unfollow @on-stream-unfollow | 				this.stream.on('follow', this.onStreamFollow); | ||||||
|  | 				this.stream.on('unfollow', this.onStreamUnfollow); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@on \unmount ~> | 		this.on('unmount', () => { | ||||||
| 			@stream.off \follow @on-stream-follow | 			this.stream.off('follow', this.onStreamFollow); | ||||||
| 			@stream.off \unfollow @on-stream-unfollow | 			this.stream.off('unfollow', this.onStreamUnfollow); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@on-stream-follow = (user) ~> | 		this.onStreamFollow = user => { | ||||||
| 			if user.id == @user.id | 			if (user.id == this.user.id) { | ||||||
| 				@user = user | 				this.update({ | ||||||
| 				@update! | 					user: user | ||||||
|  | 				}); | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@on-stream-unfollow = (user) ~> | 		this.onStreamUnfollow = user => { | ||||||
| 			if user.id == @user.id | 			if (user.id == this.user.id) { | ||||||
| 				@user = user | 				this.update({ | ||||||
| 				@update! | 					user: user | ||||||
|  | 				}); | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@onclick = ~> | 		this.onclick = () => { | ||||||
| 			@wait = true | 			this.wait = true; | ||||||
| 			if @user.is_following | 			if (this.user.is_following) { | ||||||
| 				@api \following/delete do | 				this.api('following/delete', { | ||||||
| 					user_id: @user.id | 					user_id: this.user.id | ||||||
| 				.then ~> | 				}).then(() => { | ||||||
| 					@user.is_following = false | 					this.user.is_following = false; | ||||||
| 				.catch (err) -> | 				}).catch(err => { | ||||||
| 					console.error err | 					console.error(err); | ||||||
| 				.then ~> | 				}).then(() => { | ||||||
| 					@wait = false | 					this.wait = false; | ||||||
| 					@update! | 					this.update(); | ||||||
| 			else | 				}); | ||||||
| 				@api \following/create do | 			} else { | ||||||
| 					user_id: @user.id | 				this.api('following/create', { | ||||||
| 				.then ~> | 					user_id: this.user.id | ||||||
| 					@user.is_following = true | 				}).then(() => { | ||||||
| 				.catch (err) -> | 					this.user.is_following = true; | ||||||
| 					console.error err | 				}).catch(err => { | ||||||
| 				.then ~> | 					console.error(err); | ||||||
| 					@wait = false | 				}).then(() => { | ||||||
| 					@update! | 					this.wait = false; | ||||||
|  | 					this.update(); | ||||||
|  | 				}); | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
| 	</script> | 	</script> | ||||||
| </mk-big-follow-button> | </mk-big-follow-button> | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| <mk-contextmenu><yield /> | <mk-contextmenu> | ||||||
|  | 	<yield /> | ||||||
| 	<style> | 	<style> | ||||||
| 		:scope | 		:scope | ||||||
| 			$width = 240px | 			$width = 240px | ||||||
| @@ -94,46 +95,45 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@root.add-event-listener \contextmenu (e) ~> | 		const contains = require('../../common/scripts/contains'); | ||||||
| 			e.prevent-default! |  | ||||||
|  |  | ||||||
| 		@mousedown = (e) ~> | 		this.root.addEventListener('contextmenu', e => { | ||||||
| 			e.prevent-default! | 			e.preventDefault(); | ||||||
| 			if (!contains @root, e.target) and (@root != e.target) | 		}); | ||||||
| 				@close! |  | ||||||
| 			return false |  | ||||||
|  |  | ||||||
| 		@open = (pos) ~> | 		this.mousedown = e => { | ||||||
| 			all = document.query-selector-all 'body *' | 			e.preventDefault(); | ||||||
| 			Array.prototype.for-each.call all, (el) ~> | 			if (!contains(this.root, e.target) && (this.root != e.target)) this.close(); | ||||||
| 				el.add-event-listener \mousedown @mousedown | 			return false; | ||||||
| 			@root.style.display = \block | 		}; | ||||||
| 			@root.style.left = pos.x + \px |  | ||||||
| 			@root.style.top = pos.y + \px |  | ||||||
|  |  | ||||||
| 			Velocity @root, \finish true | 		this.open = pos => { | ||||||
| 			Velocity @root, { opacity: 0 } 0ms | 			document.querySelectorAll('body *').forEach(el => { | ||||||
| 			Velocity @root, { | 				el.addEventListener('mousedown', this.mousedown); | ||||||
|  | 			}); | ||||||
|  |  | ||||||
|  | 			this.root.style.display = 'block'; | ||||||
|  | 			this.root.style.left = pos.x + 'px'; | ||||||
|  | 			this.root.style.top = pos.y + 'px'; | ||||||
|  |  | ||||||
|  | 			Velocity(this.root, 'finish', true); | ||||||
|  | 			Velocity(this.root, { opacity: 0 }, 0); | ||||||
|  | 			Velocity(this.root, { | ||||||
| 				opacity: 1 | 				opacity: 1 | ||||||
| 			} { | 			}, { | ||||||
| 				queue: false | 				queue: false, | ||||||
| 				duration: 100ms | 				duration: 100, | ||||||
| 				easing: \linear | 				easing: 'linear' | ||||||
| 			} | 			}); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@close = ~> | 		this.close = () => { | ||||||
| 			all = document.query-selector-all 'body *' | 			document.querySelectorAll('body *').forEach(el => { | ||||||
| 			Array.prototype.for-each.call all, (el) ~> | 				el.removeEventListener('mousedown', this.mousedown); | ||||||
| 				el.remove-event-listener \mousedown @mousedown | 			}); | ||||||
| 			@trigger \closed |  | ||||||
| 			@unmount! |  | ||||||
|  |  | ||||||
| 		function contains(parent, child) | 			this.trigger('closed'); | ||||||
| 			node = child.parent-node | 			this.unmount(); | ||||||
| 			while (node != null) | 		}; | ||||||
| 				if (node == parent) |  | ||||||
| 					return true |  | ||||||
| 				node = node.parent-node |  | ||||||
| 			return false |  | ||||||
| 	</script> | 	</script> | ||||||
| </mk-contextmenu> | </mk-contextmenu> | ||||||
|   | |||||||
| @@ -158,31 +158,37 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \cropper | 		this.mixin('cropper'); | ||||||
|  |  | ||||||
| 		@image = @opts.file | 		this.image = this.opts.file; | ||||||
| 		@title = @opts.title | 		this.title = this.opts.title; | ||||||
| 		@aspect-ratio = @opts.aspect-ratio | 		this.aspectRatio = this.opts.aspectRatio; | ||||||
| 		@cropper = null | 		this.cropper = null; | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			@img = @refs.window.refs.img | 			this.img = this.refs.window.refs.img; | ||||||
| 			@cropper = new @Cropper @img, do | 			this.cropper = new this.Cropper(this.img, { | ||||||
| 				aspect-ratio: @aspect-ratio | 				aspectRatio: this.aspectRatio, | ||||||
| 				highlight: no | 				highlight: false, | ||||||
| 				view-mode: 1 | 				viewMode: 1 | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@ok = ~> | 		this.ok = () => { | ||||||
| 			@cropper.get-cropped-canvas!.to-blob (blob) ~> | 			this.cropper.getCroppedCanvas().toBlob(blob => { | ||||||
| 				@trigger \cropped blob | 				this.trigger('cropped', blob); | ||||||
| 				@refs.window.close! | 				this.refs.window.close(); | ||||||
|  | 			}); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@skip = ~> | 		this.skip = () => { | ||||||
| 			@trigger \skiped | 			this.trigger('skiped'); | ||||||
| 			@refs.window.close! | 			this.refs.window.close(); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@cancel = ~> | 		this.cancel = () => { | ||||||
| 			@trigger \canceled | 			this.trigger('canceled'); | ||||||
| 			@refs.window.close! | 			this.refs.window.close(); | ||||||
|  | 		}; | ||||||
| 	</script> | 	</script> | ||||||
| </mk-crop-window> | </mk-crop-window> | ||||||
|   | |||||||
| @@ -79,69 +79,72 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@can-through = if opts.can-through? then opts.can-through else true | 		this.canThrough = opts.canThrough != null ? opts.canThrough : true; | ||||||
| 		@opts.buttons.for-each (button) ~> | 		this.opts.buttons.forEach(button => { | ||||||
| 			button._onclick = ~> | 			button._onclick = () => { | ||||||
| 				if button.onclick? | 				if (button.onclick) button.onclick(); | ||||||
| 					button.onclick! | 				this.close(); | ||||||
| 				@close! | 			}; | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			@refs.header.innerHTML = @opts.title | 			this.refs.header.innerHTML = this.opts.title; | ||||||
| 			@refs.body.innerHTML = @opts.text | 			this.refs.body.innerHTML = this.opts.text; | ||||||
|  |  | ||||||
| 			@refs.bg.style.pointer-events = \auto | 			this.refs.bg.style.pointerEvents = 'auto'; | ||||||
| 			Velocity @refs.bg, \finish true | 			Velocity(this.refs.bg, 'finish', true); | ||||||
| 			Velocity @refs.bg, { | 			Velocity(this.refs.bg, { | ||||||
| 				opacity: 1 | 				opacity: 1 | ||||||
| 			} { | 			}, { | ||||||
| 				queue: false | 				queue: false, | ||||||
| 				duration: 100ms | 				duration: 100, | ||||||
| 				easing: \linear | 				easing: 'linear' | ||||||
| 			} | 			}); | ||||||
|  |  | ||||||
| 			Velocity @refs.main, { | 			Velocity(this.refs.main, { | ||||||
| 				opacity: 0 | 				opacity: 0, | ||||||
| 				scale: 1.2 | 				scale: 1.2 | ||||||
| 			} { | 			}, { | ||||||
| 				duration: 0 | 				duration: 0 | ||||||
| 			} | 			}); | ||||||
| 			Velocity @refs.main, { | 			Velocity(this.refs.main, { | ||||||
| 				opacity: 1 | 				opacity: 1, | ||||||
| 				scale: 1 | 				scale: 1 | ||||||
| 			} { | 			}, { | ||||||
| 				duration: 300ms | 				duration: 300, | ||||||
| 				easing: [ 0, 0.5, 0.5, 1 ] | 				easing: [ 0, 0.5, 0.5, 1 ] | ||||||
| 			} | 			}); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@close = ~> | 		this.close = () => { | ||||||
| 			@refs.bg.style.pointer-events = \none | 			this.refs.bg.style.pointerEvents = 'none'; | ||||||
| 			Velocity @refs.bg, \finish true | 			Velocity(this.refs.bg, 'finish', true); | ||||||
| 			Velocity @refs.bg, { | 			Velocity(this.refs.bg, { | ||||||
| 				opacity: 0 | 				opacity: 0 | ||||||
| 			} { | 			}, { | ||||||
| 				queue: false | 				queue: false, | ||||||
| 				duration: 300ms | 				duration: 300, | ||||||
| 				easing: \linear | 				easing: 'linear'  | ||||||
| 			} | 			}); | ||||||
|  |  | ||||||
| 			@refs.main.style.pointer-events = \none | 			this.refs.main.style.pointerEvents = 'none'; | ||||||
| 			Velocity @refs.main, \finish true | 			Velocity(this.refs.main, 'finish', true); | ||||||
| 			Velocity @refs.main, { | 			Velocity(this.refs.main, { | ||||||
| 				opacity: 0 | 				opacity: 0, | ||||||
| 				scale: 0.8 | 				scale: 0.8 | ||||||
| 			} { | 			}, { | ||||||
| 				queue: false | 				queue: false, | ||||||
| 				duration: 300ms | 				duration: 300, | ||||||
| 				easing: [ 0.5, -0.5, 1, 0.5 ] | 				easing: [ 0.5, -0.5, 1, 0.5 ], | ||||||
| 				complete: ~> | 				complete: () => this.unmount() | ||||||
| 					@unmount! | 			}); | ||||||
| 			} | 		}; | ||||||
|  |  | ||||||
| 		@bg-click = ~> | 		this.bgClick = () => { | ||||||
| 			if @can-through | 			if (this.canThrough) { | ||||||
| 				if @opts.on-through? | 				if (this.opts.onThrough) this.opts.onThrough(); | ||||||
| 					@opts.on-through! | 				this.close(); | ||||||
| 				@close! | 			} | ||||||
|  | 		}; | ||||||
| 	</script> | 	</script> | ||||||
| </mk-dialog> | </mk-dialog> | ||||||
|   | |||||||
| @@ -47,21 +47,22 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \api | 		this.mixin('api'); | ||||||
| 		@mixin \i | 		this.mixin('i'); | ||||||
|  |  | ||||||
| 		@close = (e) ~> | 		this.close = e => { | ||||||
| 			e.prevent-default! | 			e.preventDefault(); | ||||||
| 			e.stop-propagation! | 			e.stopPropagation(); | ||||||
|  |  | ||||||
| 			@I.data.no_donation = true | 			this.I.data.no_donation = true; | ||||||
| 			@I.update! | 			this.I.update(); | ||||||
| 			@api \i/appdata/set do | 			this.api('i/appdata/set', { | ||||||
| 				data: JSON.stringify do | 				data: JSON.stringify({ | ||||||
| 					no_donation: @I.data.no_donation | 					no_donation: this.I.data.no_donation | ||||||
|  | 				}) | ||||||
|  | 			}); | ||||||
|  |  | ||||||
| 			@unmount! | 			this.unmount(); | ||||||
|  | 		}; | ||||||
| 			@parent.parent.set-root-layout! |  | ||||||
| 	</script> | 	</script> | ||||||
| </mk-donation> | </mk-donation> | ||||||
|   | |||||||
| @@ -13,26 +13,32 @@ | |||||||
| 		</ul> | 		</ul> | ||||||
| 	</mk-contextmenu> | 	</mk-contextmenu> | ||||||
| 	<script> | 	<script> | ||||||
| 		@browser = @opts.browser | 		this.browser = this.opts.browser; | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			@refs.ctx.on \closed ~> | 			this.refs.ctx.on('closed', () => { | ||||||
| 				@trigger \closed | 				this.trigger('closed'); | ||||||
| 				@unmount! | 				this.unmount(); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@open = (pos) ~> | 		this.open = pos => { | ||||||
| 			@refs.ctx.open pos | 			this.refs.ctx.open(pos); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@create-folder = ~> | 		this.createFolder = () => { | ||||||
| 			@browser.create-folder! | 			this.browser.createFolder(); | ||||||
| 			@refs.ctx.close! | 			this.refs.ctx.close(); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@upload = ~> | 		this.upload = () => { | ||||||
| 			@browser.select-local-file! | 			this.browser.selectLocalFile(); | ||||||
| 			@refs.ctx.close! | 			this.refs.ctx.close(); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@url-upload = ~> | 		this.urlUpload = () => { | ||||||
| 			@browser.url-upload! | 			this.browser.urlUpload(); | ||||||
| 			@refs.ctx.close! | 			this.refs.ctx.close(); | ||||||
|  | 		}; | ||||||
| 	</script> | 	</script> | ||||||
| </mk-drive-browser-base-contextmenu> | </mk-drive-browser-base-contextmenu> | ||||||
|   | |||||||
| @@ -28,19 +28,24 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \api | 		this.mixin('api'); | ||||||
|  |  | ||||||
| 		@folder = if @opts.folder? then @opts.folder else null | 		this.folder = this.opts.folder ? this.opts.folder : null; | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			@refs.window.on \closed ~> | 			this.refs.window.on('closed', () => { | ||||||
| 				@unmount! | 				this.unmount(); | ||||||
|  | 			}); | ||||||
|  |  | ||||||
| 			@api \drive .then (info) ~> | 			this.api('drive').then(info => { | ||||||
| 				@update do | 				this.update({ | ||||||
| 					usage: info.usage / info.capacity * 100 | 					usage: info.usage / info.capacity * 100 | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@close = ~> | 		this.close = () => { | ||||||
| 			@refs.window.close! | 			this.refs.window.close(); | ||||||
|  | 		}; | ||||||
| 	</script> | 	</script> | ||||||
| </mk-drive-browser-window> | </mk-drive-browser-window> | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ | |||||||
| 		</div> | 		</div> | ||||||
| 		<input class="search" type="search" placeholder=" 検索"/> | 		<input class="search" type="search" placeholder=" 検索"/> | ||||||
| 	</nav> | 	</nav> | ||||||
| 	<div class="main { uploading: uploads.length > 0, loading: loading }" ref="main" onmousedown={ onmousedown } ondragover={ ondragover } ondragenter={ ondragenter } ondragleave={ ondragleave } ondrop={ ondrop } oncontextmenu={ oncontextmenu }> | 	<div class="main { uploading: uploads.length > 0, fetching: fetching }" ref="main" onmousedown={ onmousedown } ondragover={ ondragover } ondragenter={ ondragenter } ondragleave={ ondragleave } ondrop={ ondrop } oncontextmenu={ oncontextmenu }> | ||||||
| 		<div class="selection" ref="selection"></div> | 		<div class="selection" ref="selection"></div> | ||||||
| 		<div class="contents" ref="contents"> | 		<div class="contents" ref="contents"> | ||||||
| 			<div class="folders" ref="foldersContainer" if={ folders.length > 0 }> | 			<div class="folders" ref="foldersContainer" if={ folders.length > 0 }> | ||||||
| @@ -23,13 +23,13 @@ | |||||||
| 				</virtual> | 				</virtual> | ||||||
| 				<button if={ moreFiles }>もっと読み込む</button> | 				<button if={ moreFiles }>もっと読み込む</button> | ||||||
| 			</div> | 			</div> | ||||||
| 			<div class="empty" if={ files.length == 0 && folders.length == 0 && !loading }> | 			<div class="empty" if={ files.length == 0 && folders.length == 0 && !fetching }> | ||||||
| 				<p if={ draghover }>ドロップですか?いいですよ、ボクはカワイイですからね</p> | 				<p if={ draghover }>ドロップですか?いいですよ、ボクはカワイイですからね</p> | ||||||
| 				<p if={ !draghover && folder == null }><strong>ドライブには何もありません。</strong><br/>右クリックして「ファイルをアップロード」を選んだり、ファイルをドラッグ&ドロップすることでもアップロードできます。</p> | 				<p if={ !draghover && folder == null }><strong>ドライブには何もありません。</strong><br/>右クリックして「ファイルをアップロード」を選んだり、ファイルをドラッグ&ドロップすることでもアップロードできます。</p> | ||||||
| 				<p if={ !draghover && folder != null }>このフォルダーは空です</p> | 				<p if={ !draghover && folder != null }>このフォルダーは空です</p> | ||||||
| 			</div> | 			</div> | ||||||
| 		</div> | 		</div> | ||||||
| 		<div class="loading" if={ loading }> | 		<div class="loading" if={ fetching }> | ||||||
| 			<div class="spinner"> | 			<div class="spinner"> | ||||||
| 				<div class="dot1"></div> | 				<div class="dot1"></div> | ||||||
| 				<div class="dot2"></div> | 				<div class="dot2"></div> | ||||||
| @@ -133,7 +133,7 @@ | |||||||
| 				&, * | 				&, * | ||||||
| 					user-select none | 					user-select none | ||||||
|  |  | ||||||
| 				&.loading | 				&.fetching | ||||||
| 					cursor wait !important | 					cursor wait !important | ||||||
|  |  | ||||||
| 					* | 					* | ||||||
| @@ -184,7 +184,7 @@ | |||||||
| 						> p | 						> p | ||||||
| 							margin 0 | 							margin 0 | ||||||
|  |  | ||||||
| 				> .loading | 				> .fetching | ||||||
| 					.spinner | 					.spinner | ||||||
| 						margin 100px auto | 						margin 100px auto | ||||||
| 						width 40px | 						width 40px | ||||||
| @@ -238,418 +238,439 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \api | 		const contains = require('../../../common/scripts/contains'); | ||||||
| 		@mixin \dialog |  | ||||||
| 		@mixin \input-dialog | 		this.mixin('api'); | ||||||
| 		@mixin \stream | 		this.mixin('dialog'); | ||||||
|  | 		this.mixin('input-dialog'); | ||||||
| 		@files = [] | 		this.mixin('stream'); | ||||||
| 		@folders = [] |  | ||||||
| 		@hierarchy-folders = [] | 		this.files = []; | ||||||
|  | 		this.folders = []; | ||||||
| 		@uploads = [] | 		this.hierarchyFolders = []; | ||||||
|  |  | ||||||
| 		# 現在の階層(フォルダ) | 		this.uploads = []; | ||||||
| 		# * null でルートを表す |  | ||||||
| 		@folder = null | 		// 現在の階層(フォルダ) | ||||||
|  | 		// * null でルートを表す | ||||||
| 		@multiple = if @opts.multiple? then @opts.multiple else false | 		this.folder = null; | ||||||
|  |  | ||||||
| 		# ドロップされようとしているか | 		this.multiple = this.opts.multiple != null ? this.opts.multiple : false; | ||||||
| 		@draghover = false |  | ||||||
|  | 		// ドロップされようとしているか | ||||||
| 		# 自信の所有するアイテムがドラッグをスタートさせたか | 		this.draghover = false; | ||||||
| 		# (自分自身の階層にドロップできないようにするためのフラグ) |  | ||||||
| 		@is-drag-source = false | 		// 自信の所有するアイテムがドラッグをスタートさせたか | ||||||
|  | 		// (自分自身の階層にドロップできないようにするためのフラグ) | ||||||
| 		@on \mount ~> | 		this.isDragSource = false; | ||||||
| 			@refs.uploader.on \uploaded (file) ~> |  | ||||||
| 				@add-file file, true | 		this.on('mount', () => { | ||||||
|  | 			this.refs.uploader.on('uploaded', file => { | ||||||
| 			@refs.uploader.on \change-uploads (uploads) ~> | 				this.addFile(file, true); | ||||||
| 				@uploads = uploads | 			}); | ||||||
| 				@update! |  | ||||||
|  | 			this.refs.uploader.on('change-uploads', uploads => { | ||||||
| 			@stream.on \drive_file_created @on-stream-drive-file-created | 				this.update({ | ||||||
| 			@stream.on \drive_file_updated @on-stream-drive-file-updated | 					uploads: uploads | ||||||
| 			@stream.on \drive_folder_created @on-stream-drive-folder-created | 				}); | ||||||
| 			@stream.on \drive_folder_updated @on-stream-drive-folder-updated | 			}); | ||||||
|  |  | ||||||
| 			# Riotのバグでnullを渡しても""になる | 			this.stream.on('drive_file_created', this.onStreamDriveFileCreated); | ||||||
| 			# https://github.com/riot/riot/issues/2080 | 			this.stream.on('drive_file_updated', this.onStreamDriveFileUpdated); | ||||||
| 			#if @opts.folder? | 			this.stream.on('drive_folder_created', this.onStreamDriveFolderCreated); | ||||||
| 			if @opts.folder? and @opts.folder != '' | 			this.stream.on('drive_folder_updated', this.onStreamDriveFolderUpdated); | ||||||
| 				@move @opts.folder |  | ||||||
| 			else | 			// Riotのバグでnullを渡しても""になる | ||||||
| 				@load! | 			// https://github.com/riot/riot/issues/2080 | ||||||
|  | 			//if (this.opts.folder) | ||||||
| 		@on \unmount ~> | 			if (this.opts.folder && this.opts.folder != '') { | ||||||
| 			@stream.off \drive_file_created @on-stream-drive-file-created | 				this.move(this.opts.folder); | ||||||
| 			@stream.off \drive_file_updated @on-stream-drive-file-updated | 			} else { | ||||||
| 			@stream.off \drive_folder_created @on-stream-drive-folder-created | 				this.load(); | ||||||
| 			@stream.off \drive_folder_updated @on-stream-drive-folder-updated | 			} | ||||||
|  | 		}); | ||||||
| 		@on-stream-drive-file-created = (file) ~> |  | ||||||
| 			@add-file file, true | 		this.on('unmount', () => { | ||||||
|  | 			this.stream.off('drive_file_created', this.onStreamDriveFileCreated); | ||||||
| 		@on-stream-drive-file-updated = (file) ~> | 			this.stream.off('drive_file_updated', this.onStreamDriveFileUpdated); | ||||||
| 			current = if @folder? then @folder.id else null | 			this.stream.off('drive_folder_created', this.onStreamDriveFolderCreated); | ||||||
| 			if current != file.folder_id | 			this.stream.off('drive_folder_updated', this.onStreamDriveFolderUpdated); | ||||||
| 				@remove-file file | 		}); | ||||||
| 			else |  | ||||||
| 				@add-file file, true | 		this.onStreamDriveFileCreated = file => { | ||||||
|  | 			this.addFile(file, true); | ||||||
| 		@on-stream-drive-folder-created = (folder) ~> | 		}; | ||||||
| 			@add-folder folder, true |  | ||||||
|  | 		this.onStreamDriveFileUpdated = file => { | ||||||
| 		@on-stream-drive-folder-updated = (folder) ~> | 			const current = this.folder ? this.folder.id : null; | ||||||
| 			current = if @folder? then @folder.id else null | 			if (current != file.folder_id) { | ||||||
| 			if current != folder.parent_id | 				this.removeFile(file); | ||||||
| 				@remove-folder folder | 			} else { | ||||||
| 			else | 				this.addFile(file, true); | ||||||
| 				@add-folder folder, true | 			} | ||||||
|  | 		}; | ||||||
| 		@onmousedown = (e) ~> |  | ||||||
| 			if (contains @refs.folders-container, e.target) or (contains @refs.files-container, e.target) | 		this.onStreamDriveFolderCreated = folder => { | ||||||
| 				return true | 			this.addFolder(folder, true); | ||||||
|  | 		}; | ||||||
| 			rect = @refs.main.get-bounding-client-rect! |  | ||||||
|  | 		this.onStreamDriveFolderUpdated = folder => { | ||||||
| 			left = e.page-x + @refs.main.scroll-left - rect.left - window.page-x-offset | 			const current = this.folder ? this.folder.id : null; | ||||||
| 			top = e.page-y + @refs.main.scroll-top - rect.top - window.page-y-offset | 			if (current != folder.parent_id) { | ||||||
|  | 				this.removeFolder(folder); | ||||||
| 			move = (e) ~> | 			} else { | ||||||
| 				@refs.selection.style.display = \block | 				this.addFolder(folder, true); | ||||||
|  | 			} | ||||||
| 				cursor-x = e.page-x + @refs.main.scroll-left - rect.left - window.page-x-offset | 		}; | ||||||
| 				cursor-y = e.page-y + @refs.main.scroll-top - rect.top - window.page-y-offset |  | ||||||
| 				w = cursor-x - left | 		this.onmousedown = e => { | ||||||
| 				h = cursor-y - top | 			if (contains(this.refs.foldersContainer, e.target) || contains(this.refs.filesContainer, e.target)) return true; | ||||||
|  |  | ||||||
| 				if w > 0 | 			const rect = this.refs.main.getBoundingClientRect(); | ||||||
| 					@refs.selection.style.width = w + \px |  | ||||||
| 					@refs.selection.style.left = left + \px | 			const left = e.pageX + this.refs.main.scrollLeft - rect.left - window.pageXOffset | ||||||
| 				else | 			const top = e.pageY + this.refs.main.scrollTop - rect.top - window.pageYOffset | ||||||
| 					@refs.selection.style.width = -w + \px |  | ||||||
| 					@refs.selection.style.left = cursor-x + \px | 			const move = e => { | ||||||
|  | 				this.refs.selection.style.display = 'block'; | ||||||
| 				if h > 0 |  | ||||||
| 					@refs.selection.style.height = h + \px | 				const cursorX = e.pageX + this.refs.main.scrollLeft - rect.left - window.pageXOffset; | ||||||
| 					@refs.selection.style.top = top + \px | 				const cursorY = e.pageY + this.refs.main.scrollTop - rect.top - window.pageYOffset; | ||||||
| 				else | 				const w = cursorX - left; | ||||||
| 					@refs.selection.style.height = -h + \px | 				const h = cursorY - top; | ||||||
| 					@refs.selection.style.top = cursor-y + \px |  | ||||||
|  | 				if (w > 0) { | ||||||
| 			up = (e) ~> | 					this.refs.selection.style.width = w + 'px'; | ||||||
| 				document.document-element.remove-event-listener \mousemove move | 					this.refs.selection.style.left = left + 'px'; | ||||||
| 				document.document-element.remove-event-listener \mouseup up | 				} else { | ||||||
|  | 					this.refs.selection.style.width = -w + 'px'; | ||||||
| 				@refs.selection.style.display = \none | 					this.refs.selection.style.left = cursorX + 'px'; | ||||||
|  | 				} | ||||||
| 			document.document-element.add-event-listener \mousemove move |  | ||||||
| 			document.document-element.add-event-listener \mouseup up | 				if (h > 0) { | ||||||
|  | 					this.refs.selection.style.height = h + 'px'; | ||||||
| 		@path-oncontextmenu = (e) ~> | 					this.refs.selection.style.top = top + 'px'; | ||||||
| 			e.prevent-default! | 				} else { | ||||||
| 			e.stop-immediate-propagation! | 					this.refs.selection.style.height = -h + 'px'; | ||||||
| 			return false | 					this.refs.selection.style.top = cursorY + 'px'; | ||||||
|  | 				} | ||||||
| 		@ondragover = (e) ~> | 			}; | ||||||
| 			e.prevent-default! |  | ||||||
| 			e.stop-propagation! | 			up = e => { | ||||||
|  | 				document.documentElement.removeEventListener('mousemove', move); | ||||||
| 			# ドラッグ元が自分自身の所有するアイテムかどうか | 				document.documentElement.removeEventListener('mouseup', up); | ||||||
| 			if !@is-drag-source |  | ||||||
| 				# ドラッグされてきたものがファイルだったら | 				this.refs.selection.style.display = 'none'; | ||||||
| 				if e.data-transfer.effect-allowed == \all | 			}; | ||||||
| 					e.data-transfer.drop-effect = \copy |  | ||||||
| 				else | 			document.documentElement.addEventListener('mousemove', move); | ||||||
| 					e.data-transfer.drop-effect = \move | 			document.documentElement.addEventListener('mouseup', up); | ||||||
| 				@draghover = true | 		}; | ||||||
| 			else |  | ||||||
| 				# 自分自身にはドロップさせない | 		this.pathOncontextmenu = e => { | ||||||
| 				e.data-transfer.drop-effect = \none | 			e.preventDefault(); | ||||||
| 			return false | 			e.stopImmediatePropagation(); | ||||||
|  | 			return false; | ||||||
| 		@ondragenter = (e) ~> | 		}; | ||||||
| 			e.prevent-default! |  | ||||||
| 			if !@is-drag-source | 		this.ondragover = e => { | ||||||
| 				@draghover = true | 			e.preventDefault(); | ||||||
|  | 			e.stopPropagation(); | ||||||
| 		@ondragleave = (e) ~> |  | ||||||
| 			@draghover = false | 			// ドラッグ元が自分自身の所有するアイテムかどうか | ||||||
|  | 			if (!this.isDragSource) { | ||||||
| 		@ondrop = (e) ~> | 				// ドラッグされてきたものがファイルだったら | ||||||
| 			e.prevent-default! | 				e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; | ||||||
| 			e.stop-propagation! | 				this.draghover = true; | ||||||
|  | 			} else { | ||||||
| 			@draghover = false | 				// 自分自身にはドロップさせない | ||||||
|  | 				e.dataTransfer.dropEffect = 'none'; | ||||||
| 			# ドロップされてきたものがファイルだったら | 				return false; | ||||||
| 			if e.data-transfer.files.length > 0 | 			} | ||||||
| 				Array.prototype.for-each.call e.data-transfer.files, (file) ~> | 		}; | ||||||
| 					@upload file, @folder |  | ||||||
| 				return false | 		this.ondragenter = e => { | ||||||
|  | 			e.preventDefault(); | ||||||
| 			# データ取得 | 			if (!this.isDragSource) this.draghover = true; | ||||||
| 			data = e.data-transfer.get-data 'text' | 		}; | ||||||
| 			if !data? |  | ||||||
| 				return false | 		this.ondragleave = e => { | ||||||
|  | 			this.draghover = false; | ||||||
| 			# パース | 		}; | ||||||
| 			obj = JSON.parse data |  | ||||||
|  | 		this.ondrop = e => { | ||||||
| 			# (ドライブの)ファイルだったら | 			e.preventDefault(); | ||||||
| 			if obj.type == \file | 			e.stopPropagation(); | ||||||
| 				file = obj.id |  | ||||||
| 				if (@files.some (f) ~> f.id == file) | 			this.draghover = false; | ||||||
| 					return false |  | ||||||
| 				@remove-file file | 			// ドロップされてきたものがファイルだったら | ||||||
| 				@api \drive/files/update do | 			if (e.dataTransfer.files.length > 0) { | ||||||
| 					file_id: file | 				e.dataTransfer.files.forEach(file => { | ||||||
| 					folder_id: if @folder? then @folder.id else null | 					this.upload(file, this.folder); | ||||||
| 				.then ~> | 				}); | ||||||
| 					# something | 				return false; | ||||||
| 				.catch (err, text-status) ~> | 			} | ||||||
| 					console.error err |  | ||||||
|  | 			// データ取得 | ||||||
| 			# (ドライブの)フォルダーだったら | 			const data = e.dataTransfer.getData('text'); | ||||||
| 			else if obj.type == \folder | 			if (data == null) return false; | ||||||
| 				folder = obj.id |  | ||||||
| 				# 移動先が自分自身ならreject | 			// パース | ||||||
| 				if @folder? and folder == @folder.id | 			// TODO: JSONじゃなかったら中断 | ||||||
| 					return false | 			const obj = JSON.parse(data); | ||||||
| 				if (@folders.some (f) ~> f.id == folder) |  | ||||||
| 					return false | 			// (ドライブの)ファイルだったら | ||||||
| 				@remove-folder folder | 			if (obj.type == 'file') { | ||||||
| 				@api \drive/folders/update do | 				const file = obj.id; | ||||||
| 					folder_id: folder | 				if (this.files.some(f => f.id == file)) return false; | ||||||
| 					parent_id: if @folder? then @folder.id else null | 				this.removeFile(file); | ||||||
| 				.then ~> | 				this.api('drive/files/update', { | ||||||
| 					# something | 					file_id: file, | ||||||
| 				.catch (err) ~> | 					folder_id: this.folder ? this.folder.id : null | ||||||
| 					if err == 'detected-circular-definition' | 				}); | ||||||
| 						@dialog do | 			// (ドライブの)フォルダーだったら | ||||||
| 							'<i class="fa fa-exclamation-triangle"></i>操作を完了できません' | 			} else if (obj.type == 'folder') { | ||||||
| 							'移動先のフォルダーは、移動するフォルダーのサブフォルダーです。' | 				const folder = obj.id; | ||||||
| 							[ | 				// 移動先が自分自身ならreject | ||||||
| 								text: \OK | 				if (this.folder && folder == this.folder.id) return false; | ||||||
| 							] | 				if (this.folders.some(f => f.id == folder)) return false; | ||||||
|  | 				this.removeFolder(folder); | ||||||
| 			return false | 				this.api('drive/folders/update', { | ||||||
|  | 					folder_id: folder, | ||||||
| 		@oncontextmenu = (e) ~> | 					parent_id: this.folder ? this.folder.id : null | ||||||
| 			e.prevent-default! | 				}).then(() => { | ||||||
| 			e.stop-immediate-propagation! | 					// something | ||||||
|  | 				}).catch(err => { | ||||||
| 			ctx = document.body.append-child document.create-element \mk-drive-browser-base-contextmenu | 					switch (err) { | ||||||
| 			ctx = riot.mount ctx, do | 						case 'detected-circular-definition': | ||||||
| 				browser: @ | 							this.dialog('<i class="fa fa-exclamation-triangle"></i>操作を完了できません', | ||||||
| 			ctx = ctx.0 | 								'移動先のフォルダーは、移動するフォルダーのサブフォルダーです。', [{ | ||||||
| 			ctx.open do | 								text: 'OK' | ||||||
| 				x: e.page-x - window.page-x-offset | 							}]); | ||||||
| 				y: e.page-y - window.page-y-offset | 							break; | ||||||
|  | 						default: | ||||||
| 			return false | 							alert('不明なエラー' + err); | ||||||
|  | 					} | ||||||
| 		@select-local-file = ~> | 				}); | ||||||
| 			@refs.file-input.click! | 			} | ||||||
|  |  | ||||||
| 		@url-upload = ~> | 			return false; | ||||||
| 			url <~ @input-dialog do | 		}; | ||||||
| 				'URLアップロード' |  | ||||||
| 				'アップロードしたいファイルのURL' | 		this.oncontextmenu = e => { | ||||||
| 				null | 			e.preventDefault(); | ||||||
|  | 			e.stopImmediatePropagation(); | ||||||
| 			if url? and url != '' |  | ||||||
| 				@api \drive/files/upload_from_url do | 			const ctx = riot.mount(document.body.appendChild(document.createElement('mk-drive-browser-base-contextmenu')), { | ||||||
| 					url: url | 				browser: this | ||||||
| 					folder_id: if @folder? then @folder.id else undefined | 			})[0]; | ||||||
|  | 			ctx.open({ | ||||||
| 				@dialog do | 				x: e.pageX - window.pageXOffset, | ||||||
| 					'<i class="fa fa-check"></i>アップロードをリクエストしました' | 				y: e.pageY - window.pageYOffset | ||||||
| 					'アップロードが完了するまで時間がかかる場合があります。' | 			}); | ||||||
| 					[ |  | ||||||
| 						text: \OK | 			return false; | ||||||
| 					] | 		}; | ||||||
|  |  | ||||||
| 		@create-folder = ~> | 		this.selectLocalFile = () => { | ||||||
| 			name <~ @input-dialog do | 			this.refs.fileInput.click(); | ||||||
| 				'フォルダー作成' | 		}; | ||||||
| 				'フォルダー名' |  | ||||||
| 				null | 		this.urlUpload = () => { | ||||||
|  | 			this.inputDialog('URLアップロード', 'アップロードしたいファイルのURL', null, url => { | ||||||
| 			@api \drive/folders/create do | 				this.api('drive/files/upload_from_url', { | ||||||
| 				name: name | 					url: url, | ||||||
| 				folder_id: if @folder? then @folder.id else undefined | 					folder_id: this.folder ? this.folder.id : undefined | ||||||
| 			.then (folder) ~> | 				}); | ||||||
| 				@add-folder folder, true |  | ||||||
| 				@update! | 				this.dialog('<i class="fa fa-check"></i>アップロードをリクエストしました', | ||||||
| 			.catch (err) ~> | 					'アップロードが完了するまで時間がかかる場合があります。', [{ | ||||||
| 				console.error err | 					text: 'OK' | ||||||
|  | 				}]); | ||||||
| 		@change-file-input = ~> | 			}); | ||||||
| 			files = @refs.file-input.files | 		}; | ||||||
| 			for i from 0 to files.length - 1 |  | ||||||
| 				file = files.item i | 		this.createFolder = () => { | ||||||
| 				@upload file, @folder | 			this.inputDialog('フォルダー作成', 'フォルダー名', null, name => { | ||||||
|  | 				this.api('drive/folders/create', { | ||||||
| 		@upload = (file, folder) ~> | 					name: name, | ||||||
| 			if folder? and typeof folder == \object | 					folder_id: this.folder ? this.folder.id : undefined | ||||||
| 				folder = folder.id | 				}).then(folder => { | ||||||
| 			@refs.uploader.upload file, folder | 					this.addFolder(folder, true); | ||||||
|  | 					this.update(); | ||||||
| 		@get-selection = ~> | 				}); | ||||||
| 			@files.filter (file) -> file._selected | 			}); | ||||||
|  | 		}; | ||||||
| 		@new-window = (folder-id) ~> |  | ||||||
| 			browser = document.body.append-child document.create-element \mk-drive-browser-window | 		this.changeFileInput = () => { | ||||||
| 			riot.mount browser, do | 			this.refs.fileInput.files.forEach(file => { | ||||||
| 				folder: folder-id | 				this.upload(file, this.folder); | ||||||
|  | 			}); | ||||||
| 		@move = (target-folder) ~> | 		}; | ||||||
| 			if target-folder? and typeof target-folder == \object |  | ||||||
| 				target-folder = target-folder.id | 		this.upload = (file, folder) => { | ||||||
|  | 			if (folder && typeof folder == 'object') folder = folder.id; | ||||||
| 			if target-folder == null | 			this.refs.uploader.upload(file, folder); | ||||||
| 				@go-root! | 		}; | ||||||
| 				return |  | ||||||
|  | 		this.getSelection = () => { | ||||||
| 			@loading = true | 			this.files.filter(file => file._selected); | ||||||
| 			@update! | 		}; | ||||||
|  |  | ||||||
| 			@api \drive/folders/show do | 		this.newWindow = folderId => { | ||||||
| 				folder_id: target-folder | 			riot.mount(document.body.appendChild(document.createElement('mk-drive-browser-window')), { | ||||||
| 			.then (folder) ~> | 				folder: folderId | ||||||
| 				@folder = folder | 			}); | ||||||
| 				@hierarchy-folders = [] | 		}; | ||||||
|  |  | ||||||
| 				x = (f) ~> | 		this.move = target => { | ||||||
| 					@hierarchy-folders.unshift f | 			if (target == null) { | ||||||
| 					if f.parent? | 				this.goRoot(); | ||||||
| 						x f.parent | 				return; | ||||||
|  | 			} else if (typeof target == 'object') { | ||||||
| 				if folder.parent? | 				target = target.id; | ||||||
| 					x folder.parent | 			} | ||||||
|  |  | ||||||
| 				@update! | 			this.update({ | ||||||
| 				@load! | 				fetching: true | ||||||
| 			.catch (err, text-status) -> | 			}); | ||||||
| 				console.error err |  | ||||||
|  | 			this.api('drive/folders/show', { | ||||||
| 		@add-folder = (folder, unshift = false) ~> | 				folder_id: target | ||||||
| 			current = if @folder? then @folder.id else null | 			}).then(folder => { | ||||||
| 			if current != folder.parent_id | 				this.folder = folder; | ||||||
| 				return | 				this.hierarchyFolders = []; | ||||||
|  |  | ||||||
| 			if (@folders.some (f) ~> f.id == folder.id) | 				const dive = folder => { | ||||||
| 				exist = (@folders.map (f) -> f.id).index-of folder.id | 					this.hierarchyFolders.unshift(folder); | ||||||
| 				@folders[exist] = folder | 					if (folder.parent) dive(folder.parent); | ||||||
| 				@update! | 				}; | ||||||
| 				return |  | ||||||
|  | 				if (folder.parent) dive(folder.parent); | ||||||
| 			if unshift |  | ||||||
| 				@folders.unshift folder | 				this.update(); | ||||||
| 			else | 				this.load(); | ||||||
| 				@folders.push folder | 			}); | ||||||
|  | 		}; | ||||||
| 			@update! |  | ||||||
|  | 		this.addFolder = (folder, unshift = false) => { | ||||||
| 		@add-file = (file, unshift = false) ~> | 			const current = this.folder ? this.folder.id : null; | ||||||
| 			current = if @folder? then @folder.id else null | 			if (current != folder.parent_id) return; | ||||||
| 			if current != file.folder_id |  | ||||||
| 				return | 			if (this.folders.some(f => f.id == folder.id)) { | ||||||
|  | 				const exist = this.folders.map(f => f.id).indexOf(folder.id); | ||||||
| 			if (@files.some (f) ~> f.id == file.id) | 				this.folders[exist] = folder; | ||||||
| 				exist = (@files.map (f) -> f.id).index-of file.id | 				this.update(); | ||||||
| 				@files[exist] = file | 				return; | ||||||
| 				@update! | 			} | ||||||
| 				return |  | ||||||
|  | 			if (unshift) { | ||||||
| 			if unshift | 				this.folders.unshift(folder); | ||||||
| 				@files.unshift file | 			} else { | ||||||
| 			else | 				this.folders.push(folder); | ||||||
| 				@files.push file | 			} | ||||||
|  |  | ||||||
| 			@update! | 			this.update(); | ||||||
|  | 		}; | ||||||
| 		@remove-folder = (folder) ~> |  | ||||||
| 			if typeof folder == \object | 		this.addFile = (file, unshift = false) => { | ||||||
| 				folder = folder.id | 			const current = this.folder ? this.folder.id : null; | ||||||
| 			@folders = @folders.filter (f) -> f.id != folder | 			if (current != file.folder_id) return; | ||||||
| 			@update! |  | ||||||
|  | 			if (this.files.some(f => f.id == file.id)) { | ||||||
| 		@remove-file = (file) ~> | 				const exist = this.files.map(f => f.id).indexOf(file.id); | ||||||
| 			if typeof file == \object | 				this.files[exist] = file; | ||||||
| 				file = file.id | 				this.update(); | ||||||
| 			@files = @files.filter (f) -> f.id != file | 				return; | ||||||
| 			@update! | 			} | ||||||
|  |  | ||||||
| 		@go-root = ~> | 			if (unshift) { | ||||||
| 			if @folder != null | 				this.files.unshift(file); | ||||||
| 				@folder = null | 			} else { | ||||||
| 				@hierarchy-folders = [] | 				this.files.push(file); | ||||||
| 				@update! | 			} | ||||||
| 				@load! |  | ||||||
|  | 			this.update(); | ||||||
| 		@load = ~> | 		}; | ||||||
| 			@folders = [] |  | ||||||
| 			@files = [] | 		this.removeFolder = folder => { | ||||||
| 			@more-folders = false | 			if (typeof folder == 'object') folder = folder.id; | ||||||
| 			@more-files = false | 			this.folders = this.folders.filter(f => f.id != folder); | ||||||
| 			@loading = true | 			this.update(); | ||||||
| 			@update! | 		}; | ||||||
|  |  | ||||||
| 			load-folders = null | 		this.removeFile = file => { | ||||||
| 			load-files = null | 			if (typeof file == 'object') file = file.id; | ||||||
|  | 			this.files = this.files.filter(f => f.id != file); | ||||||
| 			folders-max = 30 | 			this.update(); | ||||||
| 			files-max = 30 | 		}; | ||||||
|  |  | ||||||
| 			# フォルダ一覧取得 | 		this.goRoot = () => { | ||||||
| 			@api \drive/folders do | 			// 既にrootにいるなら何もしない | ||||||
| 				folder_id: if @folder? then @folder.id else null | 			if (this.folder == null) return; | ||||||
| 				limit: folders-max + 1 |  | ||||||
| 			.then (folders) ~> | 			this.update({ | ||||||
| 				if folders.length == folders-max + 1 | 				folder: null, | ||||||
| 					@more-folders = true | 				hierarchyFolders: [] | ||||||
| 					folders.pop! | 			}); | ||||||
| 				load-folders := folders | 			this.load(); | ||||||
| 				complete! | 		}; | ||||||
| 			.catch (err, text-status) ~> |  | ||||||
| 				console.error err | 		this.load = () => { | ||||||
|  | 			this.update({ | ||||||
| 			# ファイル一覧取得 | 				folders: [], | ||||||
| 			@api \drive/files do | 				files: [], | ||||||
| 				folder_id: if @folder? then @folder.id else null | 				moreFolders: false, | ||||||
| 				limit: files-max + 1 | 				moreFiles: false, | ||||||
| 			.then (files) ~> | 				fetching: true | ||||||
| 				if files.length == files-max + 1 | 			}); | ||||||
| 					@more-files = true |  | ||||||
| 					files.pop! | 			let fetchedFolders = null; | ||||||
| 				load-files := files | 			let fetchedFiles = null; | ||||||
| 				complete! |  | ||||||
| 			.catch (err, text-status) ~> | 			const foldersMax = 30; | ||||||
| 				console.error err | 			const filesMax = 30; | ||||||
|  |  | ||||||
| 			flag = false | 			// フォルダ一覧取得 | ||||||
| 			complete = ~> | 			this.api('drive/folders', { | ||||||
| 				if flag | 				folder_id: this.folder ? this.folder.id : null, | ||||||
| 					load-folders.for-each (folder) ~> | 				limit: foldersMax + 1 | ||||||
| 						@add-folder folder | 			}).then(folders => { | ||||||
| 					load-files.for-each (file) ~> | 				if (folders.length == foldersMax + 1) { | ||||||
| 						@add-file file | 					this.moreFolders = true; | ||||||
| 					@loading = false | 					folders.pop(); | ||||||
| 					@update! | 				} | ||||||
| 				else | 				fetchedFolders = folders; | ||||||
| 					flag := true | 				complete(); | ||||||
|  | 			}); | ||||||
| 		function contains(parent, child) |  | ||||||
| 			node = child.parent-node | 			// ファイル一覧取得 | ||||||
| 			while node? | 			this.api('drive/files', { | ||||||
| 				if node == parent | 				folder_id: this.folder ? this.folder.id : null, | ||||||
| 					return true | 				limit: filesMax + 1 | ||||||
| 				node = node.parent-node | 			}).then(files => { | ||||||
| 			return false | 				if (files.length == filesMax + 1) { | ||||||
|  | 					this.moreFiles = true; | ||||||
|  | 					files.pop(); | ||||||
|  | 				} | ||||||
|  | 				fetchedFiles = files; | ||||||
|  | 				complete(); | ||||||
|  | 			}); | ||||||
|  |  | ||||||
|  | 			let flag = false; | ||||||
|  | 			const complete = () => { | ||||||
|  | 				if (flag) { | ||||||
|  | 					fetchedFolders.forEach(this.addFolder); | ||||||
|  | 					fetchedFiles.forEach(this.addFile); | ||||||
|  | 					this.update({ | ||||||
|  | 						fetching: false | ||||||
|  | 					}); | ||||||
|  | 				} else { | ||||||
|  | 					flag = true; | ||||||
|  | 				} | ||||||
|  | 			}; | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 	</script> | 	</script> | ||||||
| </mk-drive-browser> | </mk-drive-browser> | ||||||
|   | |||||||
| @@ -22,9 +22,6 @@ | |||||||
| 					<li onclick={ parent.setBanner }> | 					<li onclick={ parent.setBanner }> | ||||||
| 						<p>バナーに設定</p> | 						<p>バナーに設定</p> | ||||||
| 					</li> | 					</li> | ||||||
| 					<li onclick={ parent.setWallpaper }> |  | ||||||
| 						<p>壁紙に設定</p> |  | ||||||
| 					</li> |  | ||||||
| 				</ul> | 				</ul> | ||||||
| 			</li> | 			</li> | ||||||
| 			<li class="has-child"> | 			<li class="has-child"> | ||||||
| @@ -38,60 +35,58 @@ | |||||||
| 		</ul> | 		</ul> | ||||||
| 	</mk-contextmenu> | 	</mk-contextmenu> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \api | 		this.mixin('api'); | ||||||
| 		@mixin \i | 		this.mixin('i'); | ||||||
| 		@mixin \update-avatar | 		this.mixin('update-avatar'); | ||||||
| 		@mixin \update-banner | 		this.mixin('update-banner'); | ||||||
| 		@mixin \update-wallpaper | 		this.mixin('input-dialog'); | ||||||
| 		@mixin \input-dialog | 		this.mixin('NotImplementedException'); | ||||||
| 		@mixin \NotImplementedException |  | ||||||
|  |  | ||||||
| 		@browser = @opts.browser | 		this.browser = this.opts.browser; | ||||||
| 		@file = @opts.file | 		this.file = this.opts.file; | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			@refs.ctx.on \closed ~> | 			this.refs.ctx.on('closed', () => { | ||||||
| 				@trigger \closed | 				this.trigger('closed'); | ||||||
| 				@unmount! | 				this.unmount(); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@open = (pos) ~> | 		this.open = pos => { | ||||||
| 			@refs.ctx.open pos | 			this.refs.ctx.open(pos); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@rename = ~> | 		this.rename = () => { | ||||||
| 			@refs.ctx.close! | 			this.refs.ctx.close(); | ||||||
|  |  | ||||||
| 			name <~ @input-dialog do | 			this.inputDialog('ファイル名の変更', '新しいファイル名を入力してください', this.file.name, name => { | ||||||
| 				'ファイル名の変更' | 				this.api('drive/files/update', { | ||||||
| 				'新しいファイル名を入力してください' | 					file_id: this.file.id, | ||||||
| 				@file.name | 					name: name | ||||||
|  | 				}) | ||||||
|  | 			}); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 			@api \drive/files/update do | 		this.copyUrl = () => { | ||||||
| 				file_id: @file.id | 			this.NotImplementedException(); | ||||||
| 				name: name | 		}; | ||||||
| 			.then ~> |  | ||||||
| 				# something |  | ||||||
| 			.catch (err) ~> |  | ||||||
| 				console.error err |  | ||||||
|  |  | ||||||
| 		@copy-url = ~> | 		this.download = () => { | ||||||
| 			@NotImplementedException! | 			this.refs.ctx.close(); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@download = ~> | 		this.setAvatar = () => { | ||||||
| 			@refs.ctx.close! | 			this.refs.ctx.close(); | ||||||
|  | 			this.updateAvatar(this.I, null, this.file); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@set-avatar = ~> | 		this.setBanner = () => { | ||||||
| 			@refs.ctx.close! | 			this.refs.ctx.close(); | ||||||
| 			@update-avatar @I, null, @file | 			this.updateBanner(this.I, null, this.file); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@set-banner = ~> | 		this.addApp = () => { | ||||||
| 			@refs.ctx.close! | 			this.NotImplementedException(); | ||||||
| 			@update-banner @I, null, @file | 		}; | ||||||
|  |  | ||||||
| 		@set-wallpaper = ~> |  | ||||||
| 			@refs.ctx.close! |  | ||||||
| 			@update-wallpaper @I, null, @file |  | ||||||
|  |  | ||||||
| 		@add-app = ~> |  | ||||||
| 			@NotImplementedException! |  | ||||||
| 	</script> | 	</script> | ||||||
| </mk-drive-browser-file-contextmenu> | </mk-drive-browser-file-contextmenu> | ||||||
|   | |||||||
| @@ -144,66 +144,76 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@bytes-to-size = require '../../../common/scripts/bytes-to-size.js' | 		this.bytesToSize = require('../../../common/scripts/bytes-to-size'); | ||||||
|  |  | ||||||
| 		@mixin \i | 		this.mixin('i'); | ||||||
|  |  | ||||||
| 		@file = @opts.file | 		this.file = this.opts.file; | ||||||
| 		@browser = @parent | 		this.browser = this.parent; | ||||||
|  |  | ||||||
| 		@title = @file.name + '\n' + @file.type + ' ' + (@bytes-to-size @file.datasize) | 		this.title = `${this.file.name}\n${this.file.type} ${this.bytesToSize(this.file.datasize)}`; | ||||||
|  |  | ||||||
| 		@is-contextmenu-showing = false | 		this.isContextmenuShowing = false; | ||||||
|  |  | ||||||
| 		@onclick = ~> | 		this.onclick = () => { | ||||||
| 			if @browser.multiple | 			if (this.browser.multiple) { | ||||||
| 				if @file._selected? | 				if (this.file._selected != null) { | ||||||
| 					@file._selected = !@file._selected | 					this.file._selected = !this.file._selected; | ||||||
| 				else | 				} else { | ||||||
| 					@file._selected = true | 					this.file._selected = true; | ||||||
| 				@browser.trigger \change-selection @browser.get-selection! | 				} | ||||||
| 			else | 				this.browser.trigger('change-selection', this.browser.getSelection()); | ||||||
| 				if @file._selected | 			} else { | ||||||
| 					@browser.trigger \selected @file | 				if (this.file._selected) { | ||||||
| 				else | 					this.browser.trigger('selected', this.file); | ||||||
| 					@browser.files.for-each (file) ~> | 				} else { | ||||||
| 						file._selected = false | 					this.browser.files.forEach(file => file._selected = false); | ||||||
| 					@file._selected = true | 					this.file._selected = true; | ||||||
| 					@browser.trigger \change-selection @file | 					this.browser.trigger('change-selection', this.file); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@oncontextmenu = (e) ~> | 		this.oncontextmenu = e => { | ||||||
| 			e.prevent-default! | 			e.preventDefault(); | ||||||
| 			e.stop-immediate-propagation! | 			e.stopImmediatePropagation(); | ||||||
|  |  | ||||||
| 			@is-contextmenu-showing = true | 			this.update({ | ||||||
| 			@update! | 				isContextmenuShowing: true | ||||||
| 			ctx = document.body.append-child document.create-element \mk-drive-browser-file-contextmenu | 			}); | ||||||
| 			ctx = riot.mount ctx, do | 			const ctx = riot.mount(document.body.appendChild(document.createElement('mk-drive-browser-file-contextmenu')), { | ||||||
| 				browser: @browser | 				browser: this.browser, | ||||||
| 				file: @file | 				file: this.file | ||||||
| 			ctx = ctx.0 | 			})[0]; | ||||||
| 			ctx.open do | 			ctx.open({ | ||||||
| 				x: e.page-x - window.page-x-offset | 				x: e.pageX - window.pageXOffset, | ||||||
| 				y: e.page-y - window.page-y-offset | 				y: e.pageY - window.pageYOffset | ||||||
| 			ctx.on \closed ~> | 			}); | ||||||
| 				@is-contextmenu-showing = false | 			ctx.on('closed', () => { | ||||||
| 				@update! | 				this.update({ | ||||||
| 			return false | 					isContextmenuShowing: false | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
|  | 			return false; | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@ondragstart = (e) ~> | 		this.ondragstart = e => { | ||||||
| 			e.data-transfer.effect-allowed = \move | 			e.dataTransfer.effectAllowed = 'move'; | ||||||
| 			e.data-transfer.set-data 'text' JSON.stringify do | 			e.dataTransfer.setData('text', JSON.stringify({ | ||||||
| 				type: \file | 				type: 'file', | ||||||
| 				id: @file.id | 				id: this.file.id, | ||||||
| 				file: @file | 				file: this.file | ||||||
| 			@is-dragging = true | 			})); | ||||||
|  | 			this.isDragging = true; | ||||||
|  |  | ||||||
| 			# 親ブラウザに対して、ドラッグが開始されたフラグを立てる | 			// 親ブラウザに対して、ドラッグが開始されたフラグを立てる | ||||||
| 			# (=あなたの子供が、ドラッグを開始しましたよ) | 			// (=あなたの子供が、ドラッグを開始しましたよ) | ||||||
| 			@browser.is-drag-source = true | 			this.browser.isDragSource = true; | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@ondragend = (e) ~> | 		this.ondragend = e => { | ||||||
| 			@is-dragging = false | 			this.isDragging = false; | ||||||
| 			@browser.is-drag-source = false | 			this.browser.isDragSource = false; | ||||||
|  | 		}; | ||||||
| 	</script> | 	</script> | ||||||
| </mk-drive-browser-file> | </mk-drive-browser-file> | ||||||
|   | |||||||
| @@ -18,49 +18,45 @@ | |||||||
| 		</ul> | 		</ul> | ||||||
| 	</mk-contextmenu> | 	</mk-contextmenu> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \api | 		this.mixin('api'); | ||||||
| 		@mixin \input-dialog | 		this.mixin('input-dialog'); | ||||||
|  |  | ||||||
| 		@browser = @opts.browser | 		this.browser = this.opts.browser; | ||||||
| 		@folder = @opts.folder | 		this.folder = this.opts.folder; | ||||||
|  |  | ||||||
| 		@open = (pos) ~> | 		this.open = pos => { | ||||||
| 			@refs.ctx.open pos | 			this.refs.ctx.open(pos); | ||||||
|  |  | ||||||
| 			@refs.ctx.on \closed ~> | 			this.refs.ctx.on('closed', () => { | ||||||
| 				@trigger \closed | 				this.trigger('closed'); | ||||||
| 				@unmount! | 				this.unmount(); | ||||||
|  | 			}); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@move = ~> | 		this.move = () => { | ||||||
| 			@browser.move @folder.id | 			this.browser.move(this.folder.id); | ||||||
| 			@refs.ctx.close! | 			this.refs.ctx.close(); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@new-window = ~> | 		this.newWindow = () => { | ||||||
| 			@browser.new-window @folder.id | 			this.browser.newWindow(this.folder.id); | ||||||
| 			@refs.ctx.close! | 			this.refs.ctx.close(); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@create-folder = ~> | 		this.createFolder = () => { | ||||||
| 			@browser.create-folder! | 			this.browser.createFolder(); | ||||||
| 			@refs.ctx.close! | 			this.refs.ctx.close(); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@upload = ~> | 		this.rename = () => { | ||||||
| 			@browser.select-lcoal-file! | 			this.refs.ctx.close(); | ||||||
| 			@refs.ctx.close! |  | ||||||
|  |  | ||||||
| 		@rename = ~> | 			this.inputialog('フォルダ名の変更', '新しいフォルダ名を入力してください', this.folder.name, name => { | ||||||
| 			@refs.ctx.close! | 				this.api('drive/folders/update', { | ||||||
|  | 					folder_id: this.folder.id, | ||||||
| 			name <~ @input-dialog do | 					name: name | ||||||
| 				'フォルダ名の変更' | 				}); | ||||||
| 				'新しいフォルダ名を入力してください' | 			}); | ||||||
| 				@folder.name | 		}; | ||||||
|  |  | ||||||
| 			@api \drive/folders/update do |  | ||||||
| 				folder_id: @folder.id |  | ||||||
| 				name: name |  | ||||||
| 			.then ~> |  | ||||||
| 				# something |  | ||||||
| 			.catch (err) ~> |  | ||||||
| 				console.error err |  | ||||||
| 	</script> | 	</script> | ||||||
| </mk-drive-browser-folder-contextmenu> | </mk-drive-browser-folder-contextmenu> | ||||||
|   | |||||||
| @@ -50,135 +50,152 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \api | 		this.mixin('api'); | ||||||
| 		@mixin \dialog | 		this.mixin('dialog'); | ||||||
|  |  | ||||||
| 		@folder = @opts.folder | 		this.folder = this.opts.folder; | ||||||
| 		@browser = @parent | 		this.browser = this.parent; | ||||||
|  |  | ||||||
| 		@title = @folder.name | 		this.title = this.folder.name; | ||||||
| 		@hover = false | 		this.hover = false; | ||||||
| 		@draghover = false | 		this.draghover = false; | ||||||
| 		@is-contextmenu-showing = false | 		this.isContextmenuShowing = false; | ||||||
|  |  | ||||||
| 		@onclick = ~> | 		this.onclick = () => { | ||||||
| 			@browser.move @folder | 			this.browser.move(this.folder); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@onmouseover = ~> | 		this.onmouseover = () => { | ||||||
| 			@hover = true | 			this.hover = true; | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@onmouseout = ~> | 		this.onmouseout = () => { | ||||||
| 			@hover = false | 			this.hover = false | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@ondragover = (e) ~> | 		this.ondragover = e => { | ||||||
| 			e.prevent-default! | 			e.preventDefault(); | ||||||
| 			e.stop-propagation! | 			e.stopPropagation(); | ||||||
|  |  | ||||||
| 			# 自分自身がドラッグされていない場合 | 			// 自分自身がドラッグされていない場合 | ||||||
| 			if !@is-dragging | 			if (!this.isDragging) { | ||||||
| 				# ドラッグされてきたものがファイルだったら | 				// ドラッグされてきたものがファイルだったら | ||||||
| 				if e.data-transfer.effect-allowed == \all | 				if (e.dataTransfer.effectAllowed === 'all') { | ||||||
| 					e.data-transfer.drop-effect = \copy | 					e.dataTransfer.dropEffect = 'copy'; | ||||||
| 				else | 				} else { | ||||||
| 					e.data-transfer.drop-effect = \move | 					e.dataTransfer.dropEffect = 'move'; | ||||||
| 			else | 				} | ||||||
| 				# 自分自身にはドロップさせない | 			} else { | ||||||
| 				e.data-transfer.drop-effect = \none | 				// 自分自身にはドロップさせない | ||||||
| 			return false | 				e.dataTransfer.dropEffect = 'none'; | ||||||
|  | 			} | ||||||
|  | 			return false; | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@ondragenter = ~> | 		this.ondragenter = () => { | ||||||
| 			if !@is-dragging | 			if (!this.isDragging) this.draghover = true; | ||||||
| 				@draghover = true | 		}; | ||||||
|  |  | ||||||
| 		@ondragleave = ~> | 		this.ondragleave = () => { | ||||||
| 			@draghover = false | 			this.draghover = false; | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@ondrop = (e) ~> | 		this.ondrop = e => { | ||||||
| 			e.stop-propagation! | 			e.stopPropagation(); | ||||||
| 			@draghover = false | 			this.draghover = false; | ||||||
|  |  | ||||||
| 			# ファイルだったら | 			// ファイルだったら | ||||||
| 			if e.data-transfer.files.length > 0 | 			if (e.dataTransfer.files.length > 0) { | ||||||
| 				Array.prototype.for-each.call e.data-transfer.files, (file) ~> | 				e.dataTransfer.files.forEach(file => { | ||||||
| 					@browser.upload file, @folder | 					this.browser.upload(file, this.folder); | ||||||
| 				return false | 				}); | ||||||
|  | 				return false; | ||||||
|  | 			}; | ||||||
|  |  | ||||||
| 			# データ取得 | 			// データ取得 | ||||||
| 			data = e.data-transfer.get-data 'text' | 			const data = e.dataTransfer.getData('text'); | ||||||
| 			if !data? | 			if (data == null) return false; | ||||||
| 				return false |  | ||||||
|  |  | ||||||
| 			# パース | 			// パース | ||||||
| 			obj = JSON.parse data | 			// TODO: Validate JSON | ||||||
|  | 			const obj = JSON.parse(data); | ||||||
|  |  | ||||||
| 			# (ドライブの)ファイルだったら | 			// (ドライブの)ファイルだったら | ||||||
| 			if obj.type == \file | 			if (obj.type == 'file') { | ||||||
| 				file = obj.id | 				const file = obj.id; | ||||||
| 				@browser.remove-file file | 				this.browser.removeFile(file); | ||||||
| 				@api \drive/files/update do | 				this.api('drive/files/update', { | ||||||
| 					file_id: file | 					file_id: file, | ||||||
| 					folder_id: @folder.id | 					folder_id: this.folder.id | ||||||
| 				.then ~> | 				}); | ||||||
| 					# something | 			// (ドライブの)フォルダーだったら | ||||||
| 				.catch (err, text-status) ~> | 			} else if (obj.type == 'folder') { | ||||||
| 					console.error err | 				const folder = obj.id; | ||||||
|  | 				// 移動先が自分自身ならreject | ||||||
|  | 				if (folder == this.folder.id) return false; | ||||||
|  | 				this.browser.removeFolder(folder); | ||||||
|  | 				this.api('drive/folders/update', { | ||||||
|  | 					folder_id: folder, | ||||||
|  | 					parent_id: this.folder.id | ||||||
|  | 				}).then(() => { | ||||||
|  | 					// something | ||||||
|  | 				}).catch(err => { | ||||||
|  | 					switch (err) { | ||||||
|  | 						case 'detected-circular-definition': | ||||||
|  | 							this.dialog('<i class="fa fa-exclamation-triangle"></i>操作を完了できません', | ||||||
|  | 								'移動先のフォルダーは、移動するフォルダーのサブフォルダーです。', [{ | ||||||
|  | 								text: 'OK' | ||||||
|  | 							}]); | ||||||
|  | 							break; | ||||||
|  | 						default: | ||||||
|  | 							alert('不明なエラー' + err); | ||||||
|  | 					} | ||||||
|  | 				}); | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			# (ドライブの)フォルダーだったら | 			return false; | ||||||
| 			else if obj.type == \folder | 		}; | ||||||
| 				folder = obj.id |  | ||||||
| 				# 移動先が自分自身ならreject |  | ||||||
| 				if folder == @folder.id |  | ||||||
| 					return false |  | ||||||
| 				@browser.remove-folder folder |  | ||||||
| 				@api \drive/folders/update do |  | ||||||
| 					folder_id: folder |  | ||||||
| 					parent_id: @folder.id |  | ||||||
| 				.then ~> |  | ||||||
| 					# something |  | ||||||
| 				.catch (err) ~> |  | ||||||
| 					if err == 'detected-circular-definition' |  | ||||||
| 						@dialog do |  | ||||||
| 							'<i class="fa fa-exclamation-triangle"></i>操作を完了できません' |  | ||||||
| 							'移動先のフォルダーは、移動するフォルダーのサブフォルダーです。' |  | ||||||
| 							[ |  | ||||||
| 								text: \OK |  | ||||||
| 							] |  | ||||||
|  |  | ||||||
| 			return false | 		this.ondragstart = e => { | ||||||
|  | 			e.dataTransfer.effectAllowed = 'move'; | ||||||
|  | 			e.dataTransfer.setData('text', JSON.stringify({ | ||||||
|  | 				type: 'folder', | ||||||
|  | 				id: this.folder.id | ||||||
|  | 			})); | ||||||
|  | 			this.isDragging = true; | ||||||
|  |  | ||||||
| 		@ondragstart = (e) ~> | 			// 親ブラウザに対して、ドラッグが開始されたフラグを立てる | ||||||
| 			e.data-transfer.effect-allowed = \move | 			// (=あなたの子供が、ドラッグを開始しましたよ) | ||||||
| 			e.data-transfer.set-data 'text' JSON.stringify do | 			this.browser.isDragSource = true; | ||||||
| 				type: \folder | 		}; | ||||||
| 				id: @folder.id |  | ||||||
| 			@is-dragging = true |  | ||||||
|  |  | ||||||
| 			# 親ブラウザに対して、ドラッグが開始されたフラグを立てる | 		this.ondragend = e => { | ||||||
| 			# (=あなたの子供が、ドラッグを開始しましたよ) | 			this.isDragging = false; | ||||||
| 			@browser.is-drag-source = true | 			this.browser.isDragSource = false; | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@ondragend = (e) ~> | 		this.oncontextmenu = e => { | ||||||
| 			@is-dragging = false | 			e.preventDefault(); | ||||||
| 			@browser.is-drag-source = false | 			e.stopImmediatePropagation(); | ||||||
|  |  | ||||||
| 		@oncontextmenu = (e) ~> | 			this.update({ | ||||||
| 			e.prevent-default! | 				isContextmenuShowing: true | ||||||
| 			e.stop-immediate-propagation! | 			}); | ||||||
|  | 			const ctx = riot.mount(document.body.appendChild(document.createElement('mk-drive-browser-folder-contextmenu')), { | ||||||
|  | 				browser: this.browser, | ||||||
|  | 				folder: this.folder | ||||||
|  | 			})[0]; | ||||||
|  | 			ctx.open({ | ||||||
|  | 				x: e.pageX - window.pageXOffset, | ||||||
|  | 				y: e.pageY - window.pageYOffset | ||||||
|  | 			}); | ||||||
|  | 			ctx.on('closed', () => { | ||||||
|  | 				this.update({ | ||||||
|  | 					isContextmenuShowing: false | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
|  |  | ||||||
| 			@is-contextmenu-showing = true | 			return false; | ||||||
| 			@update! | 		}; | ||||||
| 			ctx = document.body.append-child document.create-element \mk-drive-browser-folder-contextmenu |  | ||||||
| 			ctx = riot.mount ctx, do |  | ||||||
| 				browser: @browser |  | ||||||
| 				folder: @folder |  | ||||||
| 			ctx = ctx.0 |  | ||||||
| 			ctx.open do |  | ||||||
| 				x: e.page-x - window.page-x-offset |  | ||||||
| 				y: e.page-y - window.page-y-offset |  | ||||||
| 			ctx.on \closed ~> |  | ||||||
| 				@is-contextmenu-showing = false |  | ||||||
| 				@update! |  | ||||||
|  |  | ||||||
| 			return false |  | ||||||
| 	</script> | 	</script> | ||||||
| </mk-drive-browser-folder> | </mk-drive-browser-folder> | ||||||
|   | |||||||
| @@ -6,92 +6,93 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \api | 		this.mixin('api'); | ||||||
|  |  | ||||||
| 		# Riotのバグでnullを渡しても""になる | 		// Riotのバグでnullを渡しても""になる | ||||||
| 		# https://github.com/riot/riot/issues/2080 | 		// https://github.com/riot/riot/issues/2080 | ||||||
| 		#@folder = @opts.folder | 		//this.folder = this.opts.folder | ||||||
| 		@folder = if @opts.folder? and @opts.folder != '' then @opts.folder else null | 		this.folder = this.opts.folder && this.opts.folder != '' ? this.opts.folder : null; | ||||||
| 		@browser = @parent | 		this.browser = this.parent; | ||||||
|  |  | ||||||
| 		@hover = false | 		this.hover = false; | ||||||
|  |  | ||||||
| 		@onclick = ~> | 		this.onclick = () => { | ||||||
| 			@browser.move @folder | 			this.browser.move(this.folder); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@onmouseover = ~> | 		this.onmouseover = () => { | ||||||
| 			@hover = true | 			this.hover = true | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@onmouseout = ~> | 		this.onmouseout = () => { | ||||||
| 			@hover = false | 			this.hover = false | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@ondragover = (e) ~> | 		this.ondragover = e => { | ||||||
| 			e.prevent-default! | 			e.preventDefault(); | ||||||
| 			e.stop-propagation! | 			e.stopPropagation(); | ||||||
|  |  | ||||||
| 			# このフォルダがルートかつカレントディレクトリならドロップ禁止 | 			// このフォルダがルートかつカレントディレクトリならドロップ禁止 | ||||||
| 			if @folder == null and @browser.folder == null | 			if (this.folder == null && this.browser.folder == null) { | ||||||
| 				e.data-transfer.drop-effect = \none | 				e.dataTransfer.dropEffect = 'none'; | ||||||
| 			# ドラッグされてきたものがファイルだったら | 			// ドラッグされてきたものがファイルだったら | ||||||
| 			else if e.data-transfer.effect-allowed == \all | 			} else if (e.dataTransfer.effectAllowed == 'all') { | ||||||
| 				e.data-transfer.drop-effect = \copy | 				e.dataTransfer.dropEffect = 'copy'; | ||||||
| 			else | 			} else { | ||||||
| 				e.data-transfer.drop-effect = \move | 				e.dataTransfer.dropEffect = 'move'; | ||||||
| 			return false | 			} | ||||||
|  | 			return false; | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@ondragenter = ~> | 		this.ondragenter = () => { | ||||||
| 			if @folder != null or @browser.folder != null | 			if (this.folder || this.browser.folder) this.draghover = true; | ||||||
| 				@draghover = true | 		}; | ||||||
|  |  | ||||||
| 		@ondragleave = ~> | 		this.ondragleave = () => { | ||||||
| 			if @folder != null or @browser.folder != null | 			if (this.folder || this.browser.folder) this.draghover = false; | ||||||
| 				@draghover = false | 		}; | ||||||
|  |  | ||||||
| 		@ondrop = (e) ~> | 		this.ondrop = e => { | ||||||
| 			e.stop-propagation! | 			e.stopPropagation(); | ||||||
| 			@draghover = false | 			this.draghover = false; | ||||||
|  |  | ||||||
| 			# ファイルだったら | 			// ファイルだったら | ||||||
| 			if e.data-transfer.files.length > 0 | 			if (e.dataTransfer.files.length > 0) { | ||||||
| 				Array.prototype.for-each.call e.data-transfer.files, (file) ~> | 				e.dataTransfer.files.forEach(file => { | ||||||
| 					@browser.upload file, @folder | 					this.browser.upload(file, this.folder); | ||||||
| 				return false | 				}); | ||||||
|  | 				return false; | ||||||
|  | 			}; | ||||||
|  |  | ||||||
| 			# データ取得 | 			// データ取得 | ||||||
| 			data = e.data-transfer.get-data 'text' | 			const data = e.dataTransfer.getData('text'); | ||||||
| 			if !data? | 			if (data == null) return false; | ||||||
| 				return false |  | ||||||
|  |  | ||||||
| 			# パース | 			// パース | ||||||
| 			obj = JSON.parse data | 			// TODO: Validate JSON | ||||||
|  | 			const obj = JSON.parse(data); | ||||||
|  |  | ||||||
| 			# (ドライブの)ファイルだったら | 			// (ドライブの)ファイルだったら | ||||||
| 			if obj.type == \file | 			if (obj.type == 'file') { | ||||||
| 				file = obj.id | 				const file = obj.id; | ||||||
| 				@browser.remove-file file | 				this.browser.removeFile(file); | ||||||
| 				@api \drive/files/update do | 				this.api('drive/files/update', { | ||||||
| 					file_id: file | 					file_id: file, | ||||||
| 					folder_id: if @folder? then @folder.id else null | 					folder_id: this.folder ? this.folder.id : null | ||||||
| 				.then ~> | 				}); | ||||||
| 					# something | 			// (ドライブの)フォルダーだったら | ||||||
| 				.catch (err, text-status) ~> | 			} else if (obj.type == 'folder') { | ||||||
| 					console.error err | 				const folder = obj.id; | ||||||
|  | 				// 移動先が自分自身ならreject | ||||||
|  | 				if (this.folder && folder == this.folder.id) return false; | ||||||
|  | 				this.browser.removeFolder(folder); | ||||||
|  | 				this.api('drive/folders/update', { | ||||||
|  | 					folder_id: folder, | ||||||
|  | 					parent_id: this.folder ? this.folder.id : null | ||||||
|  | 				}); | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			# (ドライブの)フォルダーだったら | 			return false; | ||||||
| 			else if obj.type == \folder | 		}; | ||||||
| 				folder = obj.id |  | ||||||
| 				# 移動先が自分自身ならreject |  | ||||||
| 				if @folder? and folder == @folder.id |  | ||||||
| 					return false |  | ||||||
| 				@browser.remove-folder folder |  | ||||||
| 				@api \drive/folders/update do |  | ||||||
| 					folder_id: folder |  | ||||||
| 					parent_id: if @folder? then @folder.id else null |  | ||||||
| 				.then ~> |  | ||||||
| 					# something |  | ||||||
| 				.catch (err, text-status) ~> |  | ||||||
| 					console.error err |  | ||||||
|  |  | ||||||
| 			return false |  | ||||||
| 	</script> | 	</script> | ||||||
| </mk-drive-browser-nav-folder> | </mk-drive-browser-nav-folder> | ||||||
|   | |||||||
| @@ -33,9 +33,5 @@ | |||||||
| 				40% | 				40% | ||||||
| 					transform scale(1) | 					transform scale(1) | ||||||
|  |  | ||||||
| 			 |  | ||||||
|  |  | ||||||
| 			 |  | ||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| </mk-ellipsis-icon> | </mk-ellipsis-icon> | ||||||
|   | |||||||
| @@ -67,58 +67,74 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \api | 		this.mixin('api'); | ||||||
| 		@mixin \is-promise | 		this.mixin('is-promise'); | ||||||
| 		@mixin \stream | 		this.mixin('stream'); | ||||||
|  |  | ||||||
| 		@user = null | 		this.user = null; | ||||||
| 		@user-promise = if @is-promise @opts.user then @opts.user else Promise.resolve @opts.user | 		this.userPromise = this.isPromise(this.opts.user) | ||||||
| 		@init = true | 			? this.opts.user | ||||||
| 		@wait = false | 			: Promise.resolve(this.opts.user); | ||||||
|  | 		this.init = true; | ||||||
|  | 		this.wait = false; | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			@user-promise.then (user) ~> | 			this.userPromise.then(user => { | ||||||
| 				@user = user | 				this.update({ | ||||||
| 				@init = false | 					init: false, | ||||||
| 				@update! | 					user: user | ||||||
| 				@stream.on \follow @on-stream-follow | 				}); | ||||||
| 				@stream.on \unfollow @on-stream-unfollow | 				this.stream.on('follow', this.onStreamFollow); | ||||||
|  | 				this.stream.on('unfollow', this.onStreamUnfollow); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@on \unmount ~> | 		this.on('unmount', () => { | ||||||
| 			@stream.off \follow @on-stream-follow | 			this.stream.off('follow', this.onStreamFollow); | ||||||
| 			@stream.off \unfollow @on-stream-unfollow | 			this.stream.off('unfollow', this.onStreamUnfollow); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@on-stream-follow = (user) ~> | 		this.onStreamFollow = user => { | ||||||
| 			if user.id == @user.id | 			if (user.id == this.user.id) { | ||||||
| 				@user = user | 				this.update({ | ||||||
| 				@update! | 					user: user | ||||||
|  | 				}); | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@on-stream-unfollow = (user) ~> | 		this.onStreamUnfollow = user => { | ||||||
| 			if user.id == @user.id | 			if (user.id == this.user.id) { | ||||||
| 				@user = user | 				this.update({ | ||||||
| 				@update! | 					user: user | ||||||
|  | 				}); | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@onclick = ~> | 		this.onclick = () => { | ||||||
| 			@wait = true | 			this.wait = true; | ||||||
| 			if @user.is_following | 			if (this.user.is_following) { | ||||||
| 				@api \following/delete do | 				this.api('following/delete', { | ||||||
| 					user_id: @user.id | 					user_id: this.user.id | ||||||
| 				.then ~> | 				}).then(() => { | ||||||
| 					@user.is_following = false | 					this.user.is_following = false; | ||||||
| 				.catch (err) -> | 				}).catch(err => { | ||||||
| 					console.error err | 					console.error(err); | ||||||
| 				.then ~> | 				}).then(() => { | ||||||
| 					@wait = false | 					this.wait = false; | ||||||
| 					@update! | 					this.update(); | ||||||
| 			else | 				}); | ||||||
| 				@api \following/create do | 			} else { | ||||||
| 					user_id: @user.id | 				this.api('following/create', { | ||||||
| 				.then ~> | 					user_id: this.user.id | ||||||
| 					@user.is_following = true | 				}).then(() => { | ||||||
| 				.catch (err) -> | 					this.user.is_following = true; | ||||||
| 					console.error err | 				}).catch(err => { | ||||||
| 				.then ~> | 					console.error(err); | ||||||
| 					@wait = false | 				}).then(() => { | ||||||
| 					@update! | 					this.wait = false; | ||||||
|  | 					this.update(); | ||||||
|  | 				}); | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
| 	</script> | 	</script> | ||||||
| </mk-follow-button> | </mk-follow-button> | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| <mk-following-setuper> | <mk-following-setuper> | ||||||
| 	<p class="title">気になるユーザーをフォロー:</p> | 	<p class="title">気になるユーザーをフォロー:</p> | ||||||
| 	<div class="users" if={ !loading && users.length > 0 }> | 	<div class="users" if={ !fetching && users.length > 0 }> | ||||||
| 		<div class="user" each={ users }><a class="avatar-anchor" href={ CONFIG.url + '/' + username }><img class="avatar" src={ avatar_url + '?thumbnail&size=42' } alt="" data-user-preview={ id }/></a> | 		<div class="user" each={ users }><a class="avatar-anchor" href={ CONFIG.url + '/' + username }><img class="avatar" src={ avatar_url + '?thumbnail&size=42' } alt="" data-user-preview={ id }/></a> | ||||||
| 			<div class="body"><a class="name" href={ CONFIG.url + '/' + username } target="_blank" data-user-preview={ id }>{ name }</a> | 			<div class="body"><a class="name" href={ CONFIG.url + '/' + username } target="_blank" data-user-preview={ id }>{ name }</a> | ||||||
| 				<p class="username">@{ username }</p> | 				<p class="username">@{ username }</p> | ||||||
| @@ -8,8 +8,8 @@ | |||||||
| 			<mk-follow-button user={ this }></mk-follow-button> | 			<mk-follow-button user={ this }></mk-follow-button> | ||||||
| 		</div> | 		</div> | ||||||
| 	</div> | 	</div> | ||||||
| 	<p class="empty" if={ !loading && users.length == 0 }>おすすめのユーザーは見つかりませんでした。</p> | 	<p class="empty" if={ !fetching && users.length == 0 }>おすすめのユーザーは見つかりませんでした。</p> | ||||||
| 	<p class="loading" if={ loading }><i class="fa fa-spinner fa-pulse fa-fw"></i>読み込んでいます | 	<p class="fetching" if={ fetching }><i class="fa fa-spinner fa-pulse fa-fw"></i>読み込んでいます | ||||||
| 		<mk-ellipsis></mk-ellipsis> | 		<mk-ellipsis></mk-ellipsis> | ||||||
| 	</p><a class="refresh" onclick={ refresh }>もっと見る</a> | 	</p><a class="refresh" onclick={ refresh }>もっと見る</a> | ||||||
| 	<button class="close" onclick={ close } title="閉じる"><i class="fa fa-times"></i></button> | 	<button class="close" onclick={ close } title="閉じる"><i class="fa fa-times"></i></button> | ||||||
| @@ -81,7 +81,7 @@ | |||||||
| 				text-align center | 				text-align center | ||||||
| 				color #aaa | 				color #aaa | ||||||
|  |  | ||||||
| 			> .loading | 			> .fetching | ||||||
| 				margin 0 | 				margin 0 | ||||||
| 				padding 16px | 				padding 16px | ||||||
| 				text-align center | 				text-align center | ||||||
| @@ -123,41 +123,49 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \api | 		this.mixin('api'); | ||||||
| 		@mixin \user-preview | 		this.mixin('user-preview'); | ||||||
|  |  | ||||||
| 		@users = null | 		this.users = null; | ||||||
| 		@loading = true | 		this.fetching = true; | ||||||
|  |  | ||||||
| 		@limit = 6users | 		this.limit = 6; | ||||||
| 		@page = 0 | 		this.page = 0; | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			@load! | 			this.fetch(); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@load = ~> | 		this.fetch = () => { | ||||||
| 			@loading = true | 			this.update({ | ||||||
| 			@users = null | 				fetching: true, | ||||||
| 			@update! | 				users: null | ||||||
|  | 			}); | ||||||
|  |  | ||||||
| 			@api \users/recommendation do | 			this.api('users/recommendation', { | ||||||
| 				limit: @limit | 				limit: this.limit, | ||||||
| 				offset: @limit * @page | 				offset: this.limit * this.page | ||||||
| 			.then (users) ~> | 			}).then(users => { | ||||||
| 				@loading = false | 				this.fetching = false | ||||||
| 				@users = users | 				this.users = users | ||||||
| 				@update! | 				this.update({ | ||||||
| 			.catch (err, text-status) -> | 					fetching: false, | ||||||
| 				console.error err | 					users: users | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@refresh = ~> | 		this.refresh = () => { | ||||||
| 			if @users.length < @limit | 			if (this.users.length < this.limit) { | ||||||
| 				@page = 0 | 				this.page = 0; | ||||||
| 			else | 			} else { | ||||||
| 				@page++ | 				this.page++; | ||||||
| 			@load! | 			} | ||||||
|  | 			this.fetch(); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@close = ~> | 		this.close = () => { | ||||||
| 			@unmount! | 			this.unmount(); | ||||||
|  | 		}; | ||||||
| 	</script> | 	</script> | ||||||
| </mk-following-setuper> | </mk-following-setuper> | ||||||
|   | |||||||
| @@ -1,14 +0,0 @@ | |||||||
| <mk-go-top> |  | ||||||
| 	<button class="hidden" title="一番上へ"><i class="fa fa-angle-up"></i></button> |  | ||||||
| 	<script> |  | ||||||
| 		window.add-event-listener \load @on-scroll |  | ||||||
| 		window.add-event-listener \scroll @on-scroll |  | ||||||
| 		window.add-event-listener \resize @on-scroll |  | ||||||
|  |  | ||||||
| 		@on-scroll = ~> |  | ||||||
| 			if $ window .scroll-top! > 500px |  | ||||||
| 				@remove-class \hidden |  | ||||||
| 			else |  | ||||||
| 				@add-class \hidden |  | ||||||
| 	</script> |  | ||||||
| </mk-go-top> |  | ||||||
| @@ -106,43 +106,45 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@draw = ~> | 		this.draw = () => { | ||||||
| 			now = new Date! | 			const now = new Date(); | ||||||
| 			nd = now.get-date! | 			const nd = now.getDate(); | ||||||
| 			nm = now.get-month! | 			const nm = now.getMonth(); | ||||||
| 			ny = now.get-full-year! | 			const ny = now.getFullYear(); | ||||||
|  |  | ||||||
| 			@year = ny | 			this.year = ny; | ||||||
| 			@month = nm + 1 | 			this.month = nm + 1; | ||||||
| 			@day = nd | 			this.day = nd; | ||||||
| 			@week-day = [\日 \月 \火 \水 \木 \金 \土][now.get-day!] | 			this.weekDay = ['日', '月', '火', '水', '木', '金', '土'][now.getDay()]; | ||||||
|  |  | ||||||
| 			@day-numer   = (now - (new Date ny, nm, nd)) | 			this.dayNumer   = now - new Date(ny, nm, nd); | ||||||
| 			@day-denom   = 1000ms * 60s * 60m * 24h | 			this.dayDenom   = 1000/*ms*/ * 60/*s*/ * 60/*m*/ * 24/*h*/; | ||||||
| 			@month-numer = (now - (new Date ny, nm, 1)) | 			this.monthNumer = now - new Date(ny, nm, 1); | ||||||
| 			@month-denom = (new Date ny, nm + 1,  1) - (new Date ny, nm, 1) | 			this.monthDenom = new Date(ny, nm + 1, 1) - new Date(ny, nm, 1); | ||||||
| 			@year-numer  = (now - (new Date ny, 0, 1)) | 			this.yearNumer  = now - new Date(ny, 0, 1); | ||||||
| 			@year-denom  = (new Date ny + 1, 0,  1) - (new Date ny, 0, 1) | 			this.yearDenom  = new Date(ny + 1, 0, 1) - new Date(ny, 0, 1); | ||||||
|  |  | ||||||
| 			@day-p   = @day-numer   / @day-denom   * 100 | 			this.dayP   = this.dayNumer   / this.dayDenom   * 100; | ||||||
| 			@month-p = @month-numer / @month-denom * 100 | 			this.monthP = this.monthNumer / this.monthDenom * 100; | ||||||
| 			@year-p  = @year-numer  / @year-denom  * 100 | 			this.yearP  = this.yearNumer  / this.yearDenom  * 100; | ||||||
|  |  | ||||||
| 			@is-holiday = | 			this.isHoliday = now.getDay() == 0 || now.getDay() == 6; | ||||||
| 				(now.get-day! == 0 or now.get-day! == 6) |  | ||||||
|  |  | ||||||
| 			@special = | 			this.special =  | ||||||
| 				| nm == 0 and nd == 1 => \on-new-years-day | 				nm == 0 && nd == 1 ? 'on-new-years-day' : | ||||||
| 				| _ => false | 				false; | ||||||
|  |  | ||||||
| 			@update! | 			this.update(); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@draw! | 		this.draw(); | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			@clock = set-interval @draw, 1000ms | 			this.clock = setInterval(this.draw, 1000); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@on \unmount ~> | 		this.on('unmount', () => { | ||||||
| 			clear-interval @clock | 			clearInterval(this.clock); | ||||||
|  | 		}); | ||||||
| 	</script> | 	</script> | ||||||
| </mk-calendar-home-widget> | </mk-calendar-home-widget> | ||||||
|   | |||||||
| @@ -32,5 +32,5 @@ | |||||||
| 					color #999 | 					color #999 | ||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script>@mixin \user-preview</script> | 	<script>this.mixin('user-preview');</script> | ||||||
| </mk-donation-home-widget> | </mk-donation-home-widget> | ||||||
|   | |||||||
| @@ -46,67 +46,73 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \i | 		this.mixin('i'); | ||||||
| 		@mixin \api | 		this.mixin('api'); | ||||||
|  |  | ||||||
| 		@is-loading = true | 		this.isLoading = true; | ||||||
| 		@is-empty = false | 		this.isEmpty = false; | ||||||
| 		@more-loading = false | 		this.moreLoading = false; | ||||||
| 		@mode = \all | 		this.mode = 'all'; | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			document.add-event-listener \keydown @on-document-keydown | 			document.addEventListener('keydown', this.onDocumentKeydown); | ||||||
| 			window.add-event-listener \scroll @on-scroll | 			window.addEventListener('scroll', this.onScroll); | ||||||
|  |  | ||||||
| 			@fetch ~> | 			this.fetch(() => this.trigger('loaded')); | ||||||
| 				@trigger \loaded | 		}); | ||||||
|  |  | ||||||
| 		@on \unmount ~> | 		this.on('unmount', () => { | ||||||
| 			document.remove-event-listener \keydown @on-document-keydown | 			document.removeEventListener('keydown', this.onDocumentKeydown); | ||||||
| 			window.remove-event-listener \scroll @on-scroll | 			window.removeEventListener('scroll', this.onScroll); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@on-document-keydown = (e) ~> | 		this.onDocumentKeydown = e => { | ||||||
| 			tag = e.target.tag-name.to-lower-case! | 			if (e.target.tagName != 'INPUT' && tag != 'TEXTAREA') { | ||||||
| 			if tag != \input and tag != \textarea | 				if (e.which == 84) { // t | ||||||
| 				if e.which == 84 # t | 					this.refs.timeline.focus(); | ||||||
| 					@refs.timeline.focus! | 				} | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@fetch = (cb) ~> | 		this.fetch = cb => { | ||||||
| 			@api \posts/mentions do | 			this.api('posts/mentions', { | ||||||
| 				following: @mode == \following | 				following: this.mode == 'following' | ||||||
| 			.then (posts) ~> | 			}).then(posts => { | ||||||
| 				@is-loading = false | 				this.update({ | ||||||
| 				@is-empty = posts.length == 0 | 					isLoading: false, | ||||||
| 				@update! | 					isEmpty: posts.length == 0 | ||||||
| 				@refs.timeline.set-posts posts | 				}); | ||||||
| 				if cb? then cb! | 				this.refs.timeline.setPosts(posts); | ||||||
| 			.catch (err) ~> | 				if (cb) cb(); | ||||||
| 				console.error err | 			}); | ||||||
| 				if cb? then cb! | 		}; | ||||||
|  |  | ||||||
| 		@more = ~> | 		this.more = () => { | ||||||
| 			if @more-loading or @is-loading or @refs.timeline.posts.length == 0 | 			if (this.moreLoading || this.isLoading || this.refs.timeline.posts.length == 0) return; | ||||||
| 				return | 			this.update({ | ||||||
| 			@more-loading = true | 				moreLoading: true | ||||||
| 			@update! | 			}); | ||||||
| 			@api \posts/mentions do | 			this.api('posts/mentions', { | ||||||
| 				following: @mode == \following | 				following: this.mode == 'following', | ||||||
| 				max_id: @refs.timeline.tail!.id | 				max_id: this.refs.timeline.tail().id | ||||||
| 			.then (posts) ~> | 			}).then(posts => { | ||||||
| 				@more-loading = false | 				this.update({ | ||||||
| 				@update! | 					moreLoading: false | ||||||
| 				@refs.timeline.prepend-posts posts | 				}); | ||||||
| 			.catch (err) ~> | 				this.refs.timeline.prependPosts(posts); | ||||||
| 				console.error err | 			}); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@on-scroll = ~> | 		this.onScroll = () => { | ||||||
| 			current = window.scroll-y + window.inner-height | 			const current = window.scrollY + window.innerHeight; | ||||||
| 			if current > document.body.offset-height - 8 | 			if (current > document.body.offsetHeight - 8) this.more(); | ||||||
| 				@more! | 		}; | ||||||
|  |  | ||||||
| 		@set-mode = (mode) ~> | 		this.setMode = mode => { | ||||||
| 			@update do | 			this.update({ | ||||||
| 				mode: mode | 				mode: mode | ||||||
| 			@fetch! | 			}); | ||||||
|  | 			this.fetch(); | ||||||
|  | 		}; | ||||||
| 	</script> | 	</script> | ||||||
| </mk-mentions-home-widget> | </mk-mentions-home-widget> | ||||||
|   | |||||||
| @@ -13,9 +13,5 @@ | |||||||
| 			i | 			i | ||||||
| 				color #ccc | 				color #ccc | ||||||
|  |  | ||||||
| 			 |  | ||||||
|  |  | ||||||
| 			 |  | ||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| </mk-nav-home-widget> | </mk-nav-home-widget> | ||||||
|   | |||||||
| @@ -43,8 +43,9 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@settings = ~> | 		this.settings = () => { | ||||||
| 			w = riot.mount document.body.append-child document.create-element \mk-settings-window .0 | 			const w = riot.mount(document.body.appendChild(document.createElement('mk-settings-window')))[0]; | ||||||
| 			w.switch \notification | 			w.switch('notification'); | ||||||
|  | 		}; | ||||||
| 	</script> | 	</script> | ||||||
| </mk-notifications-home-widget> | </mk-notifications-home-widget> | ||||||
|   | |||||||
| @@ -57,31 +57,36 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \api | 		this.mixin('api'); | ||||||
| 		@mixin \stream | 		this.mixin('stream'); | ||||||
|  |  | ||||||
| 		@images = [] | 		this.images = []; | ||||||
| 		@initializing = true | 		this.initializing = true; | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			@stream.on \drive_file_created @on-stream-drive-file-created | 			this.stream.on('drive_file_created', this.onStreamDriveFileCreated); | ||||||
|  |  | ||||||
| 			@api \drive/stream do | 			this.api('drive/stream', { | ||||||
| 				type: 'image/*' | 				type: 'image/*', | ||||||
| 				limit: 9images | 				limit: 9 | ||||||
| 			.then (images) ~> | 			}).then(images => { | ||||||
| 				@initializing = false | 				this.update({ | ||||||
| 				@images = images | 					initializing: false, | ||||||
| 				@update! | 					images: images | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@on \unmount ~> | 		this.on('unmount', () => { | ||||||
| 			@stream.off \drive_file_created @on-stream-drive-file-created | 			this.stream.off('drive_file_created', this.onStreamDriveFileCreated); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@on-stream-drive-file-created = (file) ~> | 		this.onStreamDriveFileCreated = file => { | ||||||
| 			if /^image\/.+$/.test file.type | 			if (/^image\/.+$/.test(file.type)) { | ||||||
| 				@images.unshift file | 				this.images.unshift(file); | ||||||
| 				if @images.length > 9 | 				if (this.images.length > 9) this.images.pop(); | ||||||
| 					@images.pop! | 				this.update(); | ||||||
| 				@update! | 			} | ||||||
|  | 		}; | ||||||
| 	</script> | 	</script> | ||||||
| </mk-photo-stream-home-widget> | </mk-photo-stream-home-widget> | ||||||
|   | |||||||
| @@ -41,15 +41,17 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \i | 		this.mixin('i'); | ||||||
| 		@mixin \user-preview | 		this.mixin('user-preview'); | ||||||
| 		@mixin \update-avatar | 		this.mixin('update-avatar'); | ||||||
| 		@mixin \update-banner | 		this.mixin('update-banner'); | ||||||
|  |  | ||||||
| 		@set-avatar = ~> | 		this.setAvatar = () => { | ||||||
| 			@update-avatar @I | 			this.updateAvatar(this.I); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@set-banner = ~> | 		this.setBanner = () => { | ||||||
| 			@update-banner @I | 			this.updateBanner(this.I); | ||||||
|  | 		}; | ||||||
| 	</script> | 	</script> | ||||||
| </mk-profile-home-widget> | </mk-profile-home-widget> | ||||||
|   | |||||||
| @@ -64,31 +64,35 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \api | 		this.mixin('api'); | ||||||
| 		@mixin \NotImplementedException | 		this.mixin('NotImplementedException'); | ||||||
|  |  | ||||||
| 		@url = 'http://news.yahoo.co.jp/pickup/rss.xml' | 		this.url = 'http://news.yahoo.co.jp/pickup/rss.xml'; | ||||||
| 		@items = [] | 		this.items = []; | ||||||
| 		@initializing = true | 		this.initializing = true; | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			@fetch! | 			this.fetch(); | ||||||
| 			@clock = set-interval @fetch, 60000ms | 			this.clock = setInterval(this.fetch, 60000); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@on \unmount ~> | 		this.on('unmount', () => { | ||||||
| 			clear-interval @clock | 			clearInterval(this.clock); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@fetch = ~> | 		this.fetch = () => { | ||||||
| 			@api CONFIG.url + '/api:rss' do | 			this.api(CONFIG.url + '/api:rss', { | ||||||
| 				url: @url | 				url: this.url | ||||||
| 			.then (feed) ~> | 			}).then(feed => { | ||||||
| 				@items = feed.rss.channel.item | 				this.update({ | ||||||
| 				@initializing = false | 					initializing: false, | ||||||
| 				@update! | 					items: feed.rss.channel.item | ||||||
| 			.catch (err) -> | 				}); | ||||||
| 				console.error err | 			}); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@settings = ~> | 		this.settings = () => { | ||||||
| 			@NotImplementedException! | 			this.NotImplementedException(); | ||||||
|  | 		}; | ||||||
| 	</script> | 	</script> | ||||||
| </mk-rss-reader-home-widget> | </mk-rss-reader-home-widget> | ||||||
|   | |||||||
| @@ -32,80 +32,87 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \i | 		this.mixin('i'); | ||||||
| 		@mixin \api | 		this.mixin('api'); | ||||||
| 		@mixin \stream | 		this.mixin('stream'); | ||||||
|  |  | ||||||
| 		@is-loading = true | 		this.isLoading = true; | ||||||
| 		@is-empty = false | 		this.isEmpty = false; | ||||||
| 		@more-loading = false | 		this.moreLoading = false; | ||||||
| 		@no-following = @I.following_count == 0 | 		this.noFollowing = this.I.following_count == 0; | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			@stream.on \post @on-stream-post | 			this.stream.on('post', this.onStreamPost); | ||||||
| 			@stream.on \follow @on-stream-follow | 			this.stream.on('follow', this.onStreamFollow); | ||||||
| 			@stream.on \unfollow @on-stream-unfollow | 			this.stream.on('unfollow', this.onStreamUnfollow); | ||||||
|  |  | ||||||
| 			document.add-event-listener \keydown @on-document-keydown | 			document.addEventListener('keydown', this.onDocumentKeydown); | ||||||
| 			window.add-event-listener \scroll @on-scroll | 			window.addEventListener('scroll', this.onScroll); | ||||||
|  |  | ||||||
| 			@load ~> | 			this.load(() => this.trigger('loaded')); | ||||||
| 				@trigger \loaded | 		}); | ||||||
|  |  | ||||||
| 		@on \unmount ~> | 		this.on('unmount', () => { | ||||||
| 			@stream.off \post @on-stream-post | 			this.stream.off('post', this.onStreamPost); | ||||||
| 			@stream.off \follow @on-stream-follow | 			this.stream.off('follow', this.onStreamFollow); | ||||||
| 			@stream.off \unfollow @on-stream-unfollow | 			this.stream.off('unfollow', this.onStreamUnfollow); | ||||||
|  |  | ||||||
| 			document.remove-event-listener \keydown @on-document-keydown | 			document.removeEventListener('keydown', this.onDocumentKeydown); | ||||||
| 			window.remove-event-listener \scroll @on-scroll | 			window.removeEventListener('scroll', this.onScroll); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@on-document-keydown = (e) ~> | 		this.onDocumentKeydown = e => { | ||||||
| 			tag = e.target.tag-name.to-lower-case! | 			if (e.target.tagName != 'INPUT' && e.target.tagName != 'TEXTAREA') { | ||||||
| 			if tag != \input and tag != \textarea | 				if (e.which == 84) { // t | ||||||
| 				if e.which == 84 # t | 					this.refs.timeline.focus(); | ||||||
| 					@refs.timeline.focus! | 				} | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@load = (cb) ~> | 		this.load = (cb) => { | ||||||
| 			@api \posts/timeline | 			this.api('posts/timeline').then(posts => { | ||||||
| 			.then (posts) ~> | 				this.update({ | ||||||
| 				@is-loading = false | 					isLoading: false, | ||||||
| 				@is-empty = posts.length == 0 | 					isEmpty: posts.length == 0 | ||||||
| 				@update! | 				}); | ||||||
| 				@refs.timeline.set-posts posts | 				this.refs.timeline.setPosts(posts); | ||||||
| 				if cb? then cb! | 				if (cb) cb(); | ||||||
| 			.catch (err) ~> | 			}); | ||||||
| 				console.error err | 		}; | ||||||
| 				if cb? then cb! |  | ||||||
|  |  | ||||||
| 		@more = ~> | 		this.more = () => { | ||||||
| 			if @more-loading or @is-loading or @refs.timeline.posts.length == 0 | 			if (this.moreLoading || this.isLoading || this.refs.timeline.posts.length == 0) return; | ||||||
| 				return | 			this.update({ | ||||||
| 			@more-loading = true | 				moreLoading: true | ||||||
| 			@update! | 			}); | ||||||
| 			@api \posts/timeline do | 			this.api('posts/timeline', { | ||||||
| 				max_id: @refs.timeline.tail!.id | 				max_id: this.refs.timeline.tail().id | ||||||
| 			.then (posts) ~> | 			}).then(posts => { | ||||||
| 				@more-loading = false | 				this.update({ | ||||||
| 				@update! | 					moreLoading: false | ||||||
| 				@refs.timeline.prepend-posts posts | 				}); | ||||||
| 			.catch (err) ~> | 				this.refs.timeline.prependPosts(posts); | ||||||
| 				console.error err | 			}); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@on-stream-post = (post) ~> | 		this.onStreamPost = post => { | ||||||
| 			@is-empty = false | 			this.update({ | ||||||
| 			@update! | 				isEmpty: false | ||||||
| 			@refs.timeline.add-post post | 			}); | ||||||
|  | 			this.refs.timeline.addPost(post); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@on-stream-follow = ~> | 		this.onStreamFollow = () => { | ||||||
| 			@load! | 			this.load(); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@on-stream-unfollow = ~> | 		this.onStreamUnfollow = () => { | ||||||
| 			@load! | 			this.load(); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@on-scroll = ~> | 		this.onScroll = () => { | ||||||
| 			current = window.scroll-y + window.inner-height | 			const current = window.scrollY + window.innerHeight; | ||||||
| 			if current > document.body.offset-height - 8 | 			if (current > document.body.offsetHeight - 8) this.more(); | ||||||
| 				@more! | 		}; | ||||||
| 	</script> | 	</script> | ||||||
| </mk-timeline-home-widget> | </mk-timeline-home-widget> | ||||||
|   | |||||||
| @@ -29,43 +29,46 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@tips = [ | 		this.tips = [ | ||||||
| 			'<kbd>t</kbd>でタイムラインにフォーカスできます' | 			'<kbd>t</kbd>でタイムラインにフォーカスできます', | ||||||
| 			'<kbd>p</kbd>または<kbd>n</kbd>で投稿フォームを開きます' | 			'<kbd>p</kbd>または<kbd>n</kbd>で投稿フォームを開きます', | ||||||
| 			'投稿フォームにはファイルをドラッグ&ドロップできます' | 			'投稿フォームにはファイルをドラッグ&ドロップできます', | ||||||
| 			'投稿フォームにクリップボードにある画像データをペーストできます' | 			'投稿フォームにクリップボードにある画像データをペーストできます', | ||||||
| 			'ドライブにファイルをドラッグ&ドロップしてアップロードできます' | 			'ドライブにファイルをドラッグ&ドロップしてアップロードできます', | ||||||
| 			'ドライブでファイルをドラッグしてフォルダ移動できます' | 			'ドライブでファイルをドラッグしてフォルダ移動できます', | ||||||
| 			'ドライブでフォルダをドラッグしてフォルダ移動できます' | 			'ドライブでフォルダをドラッグしてフォルダ移動できます', | ||||||
| 			'ホームをカスタマイズできます(準備中)' | 			'ホームをカスタマイズできます(準備中)', | ||||||
| 			'MisskeyはMIT Licenseです' | 			'MisskeyはMIT Licenseです' | ||||||
| 		] | 		] | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			@set! | 			this.set(); | ||||||
| 			@clock = set-interval @change, 20000ms | 			this.clock = setInterval(this.change, 20000); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@on \unmount ~> | 		this.on('unmount', () => { | ||||||
| 			clear-interval @clock | 			clearInterval(this.clock); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@set = ~> | 		this.set = () => { | ||||||
| 			@refs.text.innerHTML = @tips[Math.floor Math.random! * @tips.length] | 			this.refs.text.innerHTML = this.tips[Math.floor(Math.random() * this.tips.length)]; | ||||||
| 			@update! | 		}; | ||||||
|  |  | ||||||
| 		@change = ~> | 		this.change = () => { | ||||||
| 			Velocity @refs.tip, { | 			Velocity(this.refs.tip, { | ||||||
| 				opacity: 0 | 				opacity: 0 | ||||||
| 			} { | 			}, { | ||||||
| 				duration: 500ms | 				duration: 500, | ||||||
| 				easing: \linear | 				easing: 'linear', | ||||||
| 				complete: @set | 				complete: this.set | ||||||
| 			} | 			}); | ||||||
|  |  | ||||||
| 			Velocity @refs.tip, { | 			Velocity(this.refs.tip, { | ||||||
| 				opacity: 1 | 				opacity: 1 | ||||||
| 			} { | 			}, { | ||||||
| 				duration: 500ms | 				duration: 500, | ||||||
| 				easing: \linear | 				easing: 'linear' | ||||||
| 			} | 			}); | ||||||
|  | 		}; | ||||||
| 	</script> | 	</script> | ||||||
| </mk-tips-home-widget> | </mk-tips-home-widget> | ||||||
|   | |||||||
| @@ -109,44 +109,42 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \api | 		this.mixin('api'); | ||||||
| 		@mixin \user-preview | 		this.mixin('user-preview'); | ||||||
|  |  | ||||||
| 		@users = null | 		this.users = null; | ||||||
| 		@loading = true | 		this.loading = true; | ||||||
|  |  | ||||||
| 		@limit = 3users | 		this.limit = 3; | ||||||
| 		@page = 0 | 		this.page = 0; | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			@fetch! | 			this.fetch(); | ||||||
| 			@clock = set-interval ~> | 		}); | ||||||
| 				if @users.length < @limit |  | ||||||
| 					@fetch true |  | ||||||
| 			, 60000ms |  | ||||||
|  |  | ||||||
| 		@on \unmount ~> | 		this.fetch = () => { | ||||||
| 			clear-interval @clock | 			this.update({ | ||||||
|  | 				loading: true, | ||||||
|  | 				users: null | ||||||
|  | 			}); | ||||||
|  | 			this.api('users/recommendation', { | ||||||
|  | 				limit: this.limit, | ||||||
|  | 				offset: this.limit * this.page | ||||||
|  | 			}).then(users => { | ||||||
|  | 				this.update({ | ||||||
|  | 					loading: false, | ||||||
|  | 					users: users | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@fetch = (quiet = false) ~> | 		this.refresh = () => { | ||||||
| 			@loading = true | 			if (this.users.length < this.limit) { | ||||||
| 			@users = null | 				this.page = 0; | ||||||
| 			if not quiet then @update! | 			} else { | ||||||
| 			@api \users/recommendation do | 				this.page++; | ||||||
| 				limit: @limit | 			} | ||||||
| 				offset: @limit * @page | 			this.fetch(); | ||||||
| 			.then (users) ~> | 		}; | ||||||
| 				@loading = false |  | ||||||
| 				@users = users |  | ||||||
| 				@update! |  | ||||||
| 			.catch (err, text-status) -> |  | ||||||
| 				console.error err |  | ||||||
|  |  | ||||||
| 		@refresh = ~> |  | ||||||
| 			if @users.length < @limit |  | ||||||
| 				@page = 0 |  | ||||||
| 			else |  | ||||||
| 				@page++ |  | ||||||
| 			@fetch! |  | ||||||
| 	</script> | 	</script> | ||||||
| </mk-user-recommendation-home-widget> | </mk-user-recommendation-home-widget> | ||||||
|   | |||||||
| @@ -58,33 +58,40 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \i | 		this.mixin('i'); | ||||||
| 		@mode = @opts.mode || \timeline |  | ||||||
|  |  | ||||||
| 		# https://github.com/riot/riot/issues/2080 | 		this.mode = this.opts.mode || 'timeline'; | ||||||
| 		if @mode == '' then @mode = \timeline | 		// https://github.com/riot/riot/issues/2080 | ||||||
|  | 		if (this.mode == '') this.mode = 'timeline'; | ||||||
|  |  | ||||||
| 		@home = [] | 		this.home = []; | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			@refs.tl.on \loaded ~> | 			this.refs.tl.on('loaded', () => { | ||||||
| 				@trigger \loaded | 				this.trigger('loaded'); | ||||||
|  | 			}); | ||||||
|  |  | ||||||
| 			@I.data.home.for-each (widget) ~> | 			this.I.data.home.forEach(widget => { | ||||||
| 				try | 				try { | ||||||
| 					el = document.create-element \mk- + widget.name + \-home-widget | 					const el = document.createElement(`mk-${widget.name}-home-widget`); | ||||||
| 					switch widget.place | 					switch (widget.place) { | ||||||
| 						| \left => @refs.left.append-child el | 						case 'left': this.refs.left.appendChild(el); break; | ||||||
| 						| \right => @refs.right.append-child el | 						case 'right': this.refs.right.appendChild(el); break; | ||||||
| 					@home.push (riot.mount el, do | 					} | ||||||
| 						id: widget.id | 					this.home.push(riot.mount(el, { | ||||||
|  | 						id: widget.id, | ||||||
| 						data: widget.data | 						data: widget.data | ||||||
| 					.0) | 					})[0]); | ||||||
| 				catch e | 				} catch (e) { | ||||||
| 					# noop | 					// noop | ||||||
|  | 				} | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@on \unmount ~> | 		this.on('unmount', () => { | ||||||
| 			@home.for-each (widget) ~> | 			this.home.forEach(widget => { | ||||||
| 				widget.unmount! | 				widget.unmount(); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
| 	</script> | 	</script> | ||||||
| </mk-home> | </mk-home> | ||||||
|   | |||||||
| @@ -35,41 +35,26 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@image = @opts.image | 		this.image = this.opts.image; | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			Velocity @root, { | 			Velocity(this.root, { | ||||||
| 				opacity: 1 | 				opacity: 1 | ||||||
| 			} { | 			}, { | ||||||
| 				duration: 100ms | 				duration: 100, | ||||||
| 				easing: \linear | 				easing: 'linear' | ||||||
| 			} | 			}); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 			#Velocity @img, { | 		this.close = () => { | ||||||
| 			#	scale: 1 | 			Velocity(this.root, { | ||||||
| 			#	opacity: 1 |  | ||||||
| 			#} { |  | ||||||
| 			#	duration: 200ms |  | ||||||
| 			#	easing: \ease-out |  | ||||||
| 			#} |  | ||||||
|  |  | ||||||
| 		@close = ~> |  | ||||||
| 			Velocity @root, { |  | ||||||
| 				opacity: 0 | 				opacity: 0 | ||||||
| 			} { | 			}, { | ||||||
| 				duration: 100ms | 				duration: 100, | ||||||
| 				easing: \linear | 				easing: 'linear', | ||||||
| 				complete: ~> @unmount! | 				complete: () => this.unmount() | ||||||
| 			} | 			}); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 			#Velocity @img, { |  | ||||||
| 			#	scale: 0.9 |  | ||||||
| 			#	opacity: 0 |  | ||||||
| 			#} { |  | ||||||
| 			#	duration: 200ms |  | ||||||
| 			#	easing: \ease-in |  | ||||||
| 			#	complete: ~> |  | ||||||
| 			#		@unmount! |  | ||||||
| 			#} |  | ||||||
| 	</script> | 	</script> | ||||||
| </mk-image-dialog> | </mk-image-dialog> | ||||||
|   | |||||||
| @@ -26,20 +26,22 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@images = @opts.images | 		this.images = this.opts.images; | ||||||
| 		@image = @images.0 | 		this.image = this.images[0]; | ||||||
|  |  | ||||||
| 		@mousemove = (e) ~> | 		this.mousemove = e => { | ||||||
| 			rect = @refs.view.get-bounding-client-rect! | 			const rect = this.refs.view.getBoundingClientRect(); | ||||||
| 			mouse-x = e.client-x - rect.left | 			const mouseX = e.clientX - rect.left; | ||||||
| 			mouse-y = e.client-y - rect.top | 			const mouseY = e.clientY - rect.top; | ||||||
| 			xp = mouse-x / @refs.view.offset-width * 100 | 			const xp = mouseX / this.refs.view.offsetWidth * 100; | ||||||
| 			yp = mouse-y / @refs.view.offset-height * 100 | 			const yp = mouseY / this.refs.view.offsetHeight * 100; | ||||||
| 			@refs.view.style.background-position = xp + '% ' + yp + '%' | 			this.refs.view.style.backgroundPosition = xp + '% ' + yp + '%'; | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@click = ~> | 		this.click = () => { | ||||||
| 			dialog = document.body.append-child document.create-element \mk-image-dialog | 			riot.mount(document.body.appendChild(document.createElement('mk-image-dialog')), { | ||||||
| 			riot.mount dialog, do | 				image: this.image | ||||||
| 				image: @image | 			}); | ||||||
|  | 		}; | ||||||
| 	</script> | 	</script> | ||||||
| </mk-images-viewer> | </mk-images-viewer> | ||||||
|   | |||||||
| @@ -16,7 +16,6 @@ require('./crop-window.tag'); | |||||||
| require('./settings.tag'); | require('./settings.tag'); | ||||||
| require('./settings-window.tag'); | require('./settings-window.tag'); | ||||||
| require('./analog-clock.tag'); | require('./analog-clock.tag'); | ||||||
| require('./go-top.tag'); |  | ||||||
| require('./ui-header.tag'); | require('./ui-header.tag'); | ||||||
| require('./ui-header-account.tag'); | require('./ui-header-account.tag'); | ||||||
| require('./ui-header-notifications.tag'); | require('./ui-header-notifications.tag'); | ||||||
| @@ -42,7 +41,6 @@ require('./home-widgets/notifications.tag'); | |||||||
| require('./home-widgets/rss-reader.tag'); | require('./home-widgets/rss-reader.tag'); | ||||||
| require('./home-widgets/photo-stream.tag'); | require('./home-widgets/photo-stream.tag'); | ||||||
| require('./home-widgets/broadcast.tag'); | require('./home-widgets/broadcast.tag'); | ||||||
| require('./stream-indicator.tag'); |  | ||||||
| require('./timeline.tag'); | require('./timeline.tag'); | ||||||
| require('./messaging/window.tag'); | require('./messaging/window.tag'); | ||||||
| require('./messaging/room-window.tag'); | require('./messaging/room-window.tag'); | ||||||
|   | |||||||
| @@ -1,13 +1,17 @@ | |||||||
| <mk-input-dialog> | <mk-input-dialog> | ||||||
| 	<mk-window ref="window" is-modal={ true } width={ '500px' }><yield to="header"><i class="fa fa-i-cursor"></i>{ parent.title }</yield> | 	<mk-window ref="window" is-modal={ true } width={ '500px' }> | ||||||
| <yield to="content"> | 		<yield to="header"> | ||||||
| 		<div class="body"> | 			<i class="fa fa-i-cursor"></i>{ parent.title } | ||||||
| 			<input ref="text" oninput={ parent.update } onkeydown={ parent.onKeydown } placeholder={ parent.placeholder }/> | 		</yield> | ||||||
| 		</div> | 		<yield to="content"> | ||||||
| 		<div class="action"> | 			<div class="body"> | ||||||
| 			<button class="cancel" onclick={ parent.cancel }>キャンセル</button> | 				<input ref="text" oninput={ parent.update } onkeydown={ parent.onKeydown } placeholder={ parent.placeholder }/> | ||||||
| 			<button class="ok" disabled={ !parent.allowEmpty && refs.text.value.length == 0 } onclick={ parent.ok }>決定</button> | 			</div> | ||||||
| 		</div></yield> | 			<div class="action"> | ||||||
|  | 				<button class="cancel" onclick={ parent.cancel }>キャンセル</button> | ||||||
|  | 				<button class="ok" disabled={ !parent.allowEmpty && refs.text.value.length == 0 } onclick={ parent.ok }>決定</button> | ||||||
|  | 			</div> | ||||||
|  | 		</yield> | ||||||
| 	</mk-window> | 	</mk-window> | ||||||
| 	<style> | 	<style> | ||||||
| 		:scope | 		:scope | ||||||
| @@ -116,42 +120,48 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@done = false | 		this.done = false; | ||||||
|  |  | ||||||
| 		@title = @opts.title | 		this.title = this.opts.title; | ||||||
| 		@placeholder = @opts.placeholder | 		this.placeholder = this.opts.placeholder; | ||||||
| 		@default = @opts.default | 		this.default = this.opts.default; | ||||||
| 		@allow-empty = if @opts.allow-empty? then @opts.allow-empty else true | 		this.allowEmpty = this.opts.allowEmpty != null ? this.opts.allowEmpty : true; | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			@text = @refs.window.refs.text | 			this.text = this.refs.window.refs.text; | ||||||
| 			if @default? | 			if (this.default) this.text.value = this.default; | ||||||
| 				@text.value = @default | 			this.text.focus(); | ||||||
| 			@text.focus! |  | ||||||
|  |  | ||||||
| 			@refs.window.on \closing ~> | 			this.refs.window.on('closing', () => { | ||||||
| 				if @done | 				if (this.done) { | ||||||
| 					@opts.on-ok @text.value | 					this.opts.onOk(this.text.value); | ||||||
| 				else | 				} else { | ||||||
| 					if @opts.on-cancel? | 					if (this.opts.onCancel) this.opts.onCancel(); | ||||||
| 						@opts.on-cancel! | 				} | ||||||
|  | 			}); | ||||||
|  |  | ||||||
| 			@refs.window.on \closed ~> | 			this.refs.window.on('closed', () => { | ||||||
| 				@unmount! | 				this.unmount(); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@cancel = ~> | 		this.cancel = () => { | ||||||
| 			@done = false | 			this.done = false; | ||||||
| 			@refs.window.close! | 			this.refs.window.close(); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@ok = ~> | 		this.ok = () => { | ||||||
| 			if not @allow-empty and @text.value == '' then return | 			if (!this.allowEmpty && this.text.value == '') return; | ||||||
| 			@done = true | 			this.done = true; | ||||||
| 			@refs.window.close! | 			this.refs.window.close(); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@on-keydown = (e) ~> | 		this.onKeydown = e => { | ||||||
| 			if e.which == 13 # Enter | 			if (e.which == 13) { // Enter | ||||||
| 				e.prevent-default! | 				e.preventDefault(); | ||||||
| 				e.stop-propagation! | 				e.stopPropagation(); | ||||||
| 				@ok! | 				this.ok(); | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
| 	</script> | 	</script> | ||||||
| </mk-input-dialog> | </mk-input-dialog> | ||||||
|   | |||||||
| @@ -93,5 +93,5 @@ | |||||||
| 				right 16px | 				right 16px | ||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script>@user = @opts.user</script> | 	<script>this.user = this.opts.user</script> | ||||||
| </mk-list-user> | </mk-list-user> | ||||||
|   | |||||||
| @@ -19,10 +19,12 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@user = @opts.user | 		this.user = this.opts.user; | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			@refs.window.on \closed ~> | 			this.refs.window.on('closed', () => { | ||||||
| 				@unmount! | 				this.unmount(); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
| 	</script> | 	</script> | ||||||
| </mk-messaging-room-window> | </mk-messaging-room-window> | ||||||
|   | |||||||
| @@ -19,13 +19,16 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			@refs.window.on \closed ~> | 			this.refs.window.on('closed', () => { | ||||||
| 				@unmount! | 				this.unmount(); | ||||||
|  | 			}); | ||||||
|  |  | ||||||
| 			@refs.window.refs.index.on \navigate-user (user) ~> | 			this.refs.window.refs.index.on('navigate-user', user => { | ||||||
| 				w = document.body.append-child document.create-element \mk-messaging-room-window | 				riot.mount(document.body.appendChild(document.createElement('mk-messaging-room-window')), { | ||||||
| 				riot.mount w, do |  | ||||||
| 					user: user | 					user: user | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
| 	</script> | 	</script> | ||||||
| </mk-messaging-window> | </mk-messaging-window> | ||||||
|   | |||||||
| @@ -177,37 +177,41 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \api | 		this.mixin('api'); | ||||||
| 		@mixin \stream | 		this.mixin('stream'); | ||||||
| 		@mixin \user-preview | 		this.mixin('user-preview'); | ||||||
| 		@mixin \get-post-summary | 		this.mixin('get-post-summary'); | ||||||
|  |  | ||||||
| 		@notifications = [] | 		this.notifications = []; | ||||||
| 		@loading = true | 		this.loading = true; | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			@api \i/notifications | 			this.api('i/notifications').then(notifications => { | ||||||
| 			.then (notifications) ~> | 				this.update({ | ||||||
| 				@notifications = notifications | 					loading: false, | ||||||
| 				@loading = false | 					notifications: notifications | ||||||
| 				@update! | 				}); | ||||||
| 			.catch (err, text-status) -> | 			}); | ||||||
| 				console.error err |  | ||||||
|  |  | ||||||
| 			@stream.on \notification @on-notification | 			this.stream.on('notification', this.onNotification); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@on \unmount ~> | 		this.on('unmount', () => { | ||||||
| 			@stream.off \notification @on-notification | 			this.stream.off('notification', this.onNotification); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@on-notification = (notification) ~> | 		this.onNotification = notification => { | ||||||
| 			@notifications.unshift notification | 			this.notifications.unshift(notification); | ||||||
| 			@update! | 			this.update(); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@on \update ~> | 		this.on('update', () => { | ||||||
| 			@notifications.for-each (notification) ~> | 			this.notifications.forEach(notification => { | ||||||
| 				date = (new Date notification.created_at).get-date! | 				const date = new Date(notification.created_at).getDate(); | ||||||
| 				month = (new Date notification.created_at).get-month! + 1 | 				const month = new Date(notification.created_at).getMonth() + 1; | ||||||
| 				notification._date = date | 				notification._date = date; | ||||||
| 				notification._datetext = month + '月 ' + date + '日' | 				notification._datetext = `${month}月 ${date}日`; | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
| 	</script> | 	</script> | ||||||
| </mk-notifications> | </mk-notifications> | ||||||
|   | |||||||
| @@ -63,18 +63,24 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mode = \signin | 		this.mode = 'signin'; | ||||||
|  |  | ||||||
| 		@signup = ~> | 		this.signup = () => { | ||||||
| 			@mode = \signup | 			this.update({ | ||||||
| 			@update! | 				mode: 'signup' | ||||||
|  | 			}); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@signin = ~> | 		this.signin = () => { | ||||||
| 			@mode = \signin | 			this.update({ | ||||||
| 			@update! | 				mode: 'signin' | ||||||
|  | 			}); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@introduction = ~> | 		this.introduction = () => { | ||||||
| 			@mode = \introduction | 			this.update({ | ||||||
| 			@update! | 				mode: 'introduction' | ||||||
|  | 			}); | ||||||
|  | 		}; | ||||||
| 	</script> | 	</script> | ||||||
| </mk-entrance> | </mk-entrance> | ||||||
|   | |||||||
| @@ -119,12 +119,16 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			@refs.signin.on \user (user) ~> | 			this.refs.signin.on('user', user => { | ||||||
| 				@update do | 				this.update({ | ||||||
| 					user: user | 					user: user | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@introduction = ~> | 		this.introduction = () => { | ||||||
| 			@parent.introduction! | 			this.parent.introduction(); | ||||||
|  | 		}; | ||||||
| 	</script> | 	</script> | ||||||
| </mk-entrance-signin> | </mk-entrance-signin> | ||||||
|   | |||||||
| @@ -43,9 +43,5 @@ | |||||||
| 				> i | 				> i | ||||||
| 					padding 14px | 					padding 14px | ||||||
|  |  | ||||||
| 			 |  | ||||||
|  |  | ||||||
| 			 |  | ||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| </mk-entrance-signup> | </mk-entrance-signup> | ||||||
|   | |||||||
| @@ -5,43 +5,45 @@ | |||||||
| 	<style> | 	<style> | ||||||
| 		:scope | 		:scope | ||||||
| 			display block | 			display block | ||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \i | 		this.mixin('i'); | ||||||
| 		@mixin \api | 		this.mixin('api'); | ||||||
| 		@mixin \ui-progress | 		this.mixin('ui-progress'); | ||||||
| 		@mixin \stream | 		this.mixin('stream'); | ||||||
| 		@mixin \get-post-summary | 		this.mixin('get-post-summary'); | ||||||
|  |  | ||||||
| 		@unread-count = 0 | 		this.unreadCount = 0; | ||||||
|  |  | ||||||
| 		@page = switch @opts.mode | 		this.page = this.opts.mode || 'timeline'; | ||||||
| 			| \timelie => \home |  | ||||||
| 			| \mentions => \mentions |  | ||||||
| 			| _ => \home |  | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			@refs.ui.refs.home.on \loaded ~> | 			this.refs.ui.refs.home.on('loaded', () => { | ||||||
| 				@Progress.done! | 				this.Progress.done(); | ||||||
|  | 			}); | ||||||
|  | 			document.title = 'Misskey'; | ||||||
|  | 			this.Progress.start(); | ||||||
|  | 			this.stream.on('post', this.onStreamPost); | ||||||
|  | 			document.addEventListener('visibilitychange', this.windowOnVisibilitychange, false); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 			document.title = 'Misskey' | 		this.on('unmount', () => { | ||||||
| 			@Progress.start! | 			this.stream.off('post', this.onStreamPost); | ||||||
| 			@stream.on \post @on-stream-post | 			document.removeEventListener('visibilitychange', this.windowOnVisibilitychange); | ||||||
| 			document.add-event-listener \visibilitychange @window-on-visibilitychange, false | 		}); | ||||||
|  |  | ||||||
| 		@on \unmount ~> | 		this.onStreamPost = post => { | ||||||
| 			@stream.off \post @on-stream-post | 			if (document.hidden && post.user_id != this.I.id) { | ||||||
| 			document.remove-event-listener \visibilitychange @window-on-visibilitychange | 				this.unreadCount++; | ||||||
|  | 				document.title = `(${this.unreadCount}) ${this.getPostSummary(post)}`; | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@on-stream-post = (post) ~> | 		this.windowOnVisibilitychange = () => { | ||||||
| 			if document.hidden and post.user_id !== @I.id | 			if (!document.hidden) { | ||||||
| 				@unread-count++ | 				this.unreadCount = 0; | ||||||
| 				document.title = '(' + @unread-count + ') ' + @get-post-summary post | 				document.title = 'Misskey'; | ||||||
|  | 			} | ||||||
| 		@window-on-visibilitychange = ~> | 		}; | ||||||
| 			if !document.hidden |  | ||||||
| 				@unread-count = 0 |  | ||||||
| 				document.title = 'Misskey' |  | ||||||
| 	</script> | 	</script> | ||||||
| </mk-home-page> | </mk-home-page> | ||||||
|   | |||||||
| @@ -1,54 +1,11 @@ | |||||||
| <mk-not-found> | <mk-not-found> | ||||||
| 	<mk-ui> | 	<mk-ui> | ||||||
| 		<main> | 		<main> | ||||||
| 			<h1>Not Found</h1><img src="/_/resources/rogge.jpg" alt=""/> | 			<h1>Not Found</h1> | ||||||
| 			<div class="mask"></div> |  | ||||||
| 		</main> | 		</main> | ||||||
| 	</mk-ui> | 	</mk-ui> | ||||||
| 	<style> | 	<style> | ||||||
| 		:scope | 		:scope | ||||||
| 			display block | 			display block | ||||||
|  |  | ||||||
| 			main |  | ||||||
| 				display block |  | ||||||
| 				width 600px |  | ||||||
| 				margin 32px auto |  | ||||||
|  |  | ||||||
| 				> img |  | ||||||
| 					display block |  | ||||||
| 					width 600px |  | ||||||
| 					height 459px |  | ||||||
| 					pointer-events none |  | ||||||
| 					user-select none |  | ||||||
| 					border-radius 16px |  | ||||||
| 					box-shadow 0 0 16px rgba(0, 0, 0, 0.1) |  | ||||||
|  |  | ||||||
| 				> h1 |  | ||||||
| 					display block |  | ||||||
| 					margin 0 |  | ||||||
| 					padding 0 |  | ||||||
| 					position absolute |  | ||||||
| 					top 260px |  | ||||||
| 					left 225px |  | ||||||
| 					transform rotate(-12deg) |  | ||||||
| 					z-index 2 |  | ||||||
| 					color #444 |  | ||||||
| 					font-size 24px |  | ||||||
| 					line-height 20px |  | ||||||
|  |  | ||||||
| 				> .mask |  | ||||||
| 					position absolute |  | ||||||
| 					top 262px |  | ||||||
| 					left 217px |  | ||||||
| 					width 126px |  | ||||||
| 					height 18px |  | ||||||
| 					transform rotate(-12deg) |  | ||||||
| 					background #D6D5DA |  | ||||||
| 					border-radius 2px 6px 7px 6px |  | ||||||
|  |  | ||||||
| 			 |  | ||||||
|  |  | ||||||
| 			 |  | ||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| </mk-not-found> | </mk-not-found> | ||||||
|   | |||||||
| @@ -16,17 +16,20 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \ui-progress | 		this.mixin('ui-progress'); | ||||||
|  |  | ||||||
| 		@post = @opts.post | 		this.post = this.opts.post; | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			@Progress.start! | 			this.Progress.start(); | ||||||
|  |  | ||||||
| 			@refs.ui.refs.detail.on \post-fetched ~> | 			this.refs.ui.refs.detail.on('post-fetched', () => { | ||||||
| 				@Progress.set 0.5 | 				this.Progress.set(0.5); | ||||||
|  | 			}); | ||||||
|  |  | ||||||
| 			@refs.ui.refs.detail.on \loaded ~> | 			this.refs.ui.refs.detail.on('loaded', () => { | ||||||
| 				@Progress.done! | 				this.Progress.done(); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
| 	</script> | 	</script> | ||||||
| </mk-post-page> | </mk-post-page> | ||||||
|   | |||||||
| @@ -5,15 +5,16 @@ | |||||||
| 	<style> | 	<style> | ||||||
| 		:scope | 		:scope | ||||||
| 			display block | 			display block | ||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \ui-progress | 		this.mixin('ui-progress'); | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			@Progress.start! | 			this.Progress.start(); | ||||||
|  |  | ||||||
| 			@refs.ui.refs.search.on \loaded ~> | 			this.refs.ui.refs.search.on('loaded', () => { | ||||||
| 				@Progress.done! | 				this.Progress.done(); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
| 	</script> | 	</script> | ||||||
| </mk-search-page> | </mk-search-page> | ||||||
|   | |||||||
| @@ -5,21 +5,23 @@ | |||||||
| 	<style> | 	<style> | ||||||
| 		:scope | 		:scope | ||||||
| 			display block | 			display block | ||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \ui-progress | 		this.mixin('ui-progress'); | ||||||
|  |  | ||||||
| 		@user = @opts.user | 		this.user = this.opts.user; | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			@Progress.start! | 			this.Progress.start(); | ||||||
|  |  | ||||||
| 			@refs.ui.refs.user.on \user-fetched (user) ~> | 			this.refs.ui.refs.user.on('user-fetched', user => { | ||||||
| 				@Progress.set 0.5 | 				this.Progress.set(0.5); | ||||||
| 				document.title = user.name + ' | Misskey' | 				document.title = user.name + ' | Misskey' | ||||||
|  | 			}); | ||||||
|  |  | ||||||
| 			@refs.ui.refs.user.on \loaded ~> | 			this.refs.ui.refs.user.on('loaded', () => { | ||||||
| 				@Progress.done! | 				this.Progress.done(); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
| 	</script> | 	</script> | ||||||
| </mk-user-page> | </mk-user-page> | ||||||
|   | |||||||
| @@ -103,38 +103,45 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \api | 		this.mixin('api'); | ||||||
| 		@mixin \text | 		this.mixin('text'); | ||||||
| 		@mixin \date-stringify | 		this.mixin('date-stringify'); | ||||||
| 		@mixin \user-preview | 		this.mixin('user-preview'); | ||||||
|  |  | ||||||
| 		@post = @opts.post | 		this.post = this.opts.post; | ||||||
|  |  | ||||||
| 		@url = CONFIG.url + '/' + @post.user.username + '/' + @post.id | 		this.url = CONFIG.url + '/' + this.post.user.username + '/' + this.post.id; | ||||||
|  |  | ||||||
| 		@title = @date-stringify @post.created_at | 		this.title = this.dateStringify(this.post.created_at); | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			if @post.text? | 			if (this.p.text) { | ||||||
| 				tokens = @analyze @post.text | 				const tokens = this.analyze(this.p.text); | ||||||
| 				@refs.text.innerHTML = @compile tokens |  | ||||||
|  |  | ||||||
| 				@refs.text.children.for-each (e) ~> | 				this.refs.text.innerHTML = this.refs.text.innerHTML.replace('<p class="dummy"></p>', this.compile(tokens)); | ||||||
| 					if e.tag-name == \MK-URL |  | ||||||
| 						riot.mount e |  | ||||||
|  |  | ||||||
| 		@like = ~> | 				this.refs.text.children.forEach(e => { | ||||||
| 			if @post.is_liked | 					if (e.tagName == 'MK-URL') riot.mount(e); | ||||||
| 				@api \posts/likes/delete do | 				}); | ||||||
| 					post_id: @post.id | 			} | ||||||
| 				.then ~> | 		}); | ||||||
| 					@post.is_liked = false |  | ||||||
| 					@update! | 		this.like = () => { | ||||||
| 			else | 			if (this.post.is_liked) { | ||||||
| 				@api \posts/likes/create do | 				this.api('posts/likes/delete', { | ||||||
| 					post_id: @post.id | 					post_id: this.post.id | ||||||
| 				.then ~> | 				}).then(() => { | ||||||
| 					@post.is_liked = true | 					this.post.is_liked = false; | ||||||
| 					@update! | 					this.update(); | ||||||
|  | 				}); | ||||||
|  | 			} else { | ||||||
|  | 				this.api('posts/likes/create', { | ||||||
|  | 					post_id: this.post.id | ||||||
|  | 				}).then(() => { | ||||||
|  | 					this.post.is_liked = true; | ||||||
|  | 					this.update(); | ||||||
|  | 				}); | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
| 	</script> | 	</script> | ||||||
| </mk-post-detail-sub> | </mk-post-detail-sub> | ||||||
|   | |||||||
| @@ -329,108 +329,126 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \api | 		this.mixin('api'); | ||||||
| 		@mixin \text | 		this.mixin('text'); | ||||||
| 		@mixin \user-preview | 		this.mixin('user-preview'); | ||||||
| 		@mixin \date-stringify | 		this.mixin('date-stringify'); | ||||||
| 		@mixin \NotImplementedException | 		this.mixin('NotImplementedException'); | ||||||
|  |  | ||||||
| 		@fetching = true | 		this.fetching = true; | ||||||
| 		@loading-context = false | 		this.loadingContext = false; | ||||||
| 		@content = null | 		this.content = null; | ||||||
| 		@post = null | 		this.post = null; | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
|  | 			this.api('posts/show', { | ||||||
|  | 				post_id: this.opts.post | ||||||
|  | 			}).then(post => { | ||||||
|  | 				const isRepost = post.repost != null; | ||||||
|  | 				const p = isRepost ? post.repost : post; | ||||||
|  | 				this.update({ | ||||||
|  | 					fetching: false, | ||||||
|  | 					post: post, | ||||||
|  | 					isRepost: isRepost, | ||||||
|  | 					p: p, | ||||||
|  | 					title: this.dateStringify(p.created_at) | ||||||
|  | 				}); | ||||||
|  |  | ||||||
| 			@api \posts/show do | 				this.trigger('loaded'); | ||||||
| 				post_id: @opts.post |  | ||||||
| 			.then (post) ~> |  | ||||||
| 				@fetching = false |  | ||||||
| 				@post = post |  | ||||||
| 				@trigger \loaded |  | ||||||
|  |  | ||||||
| 				@is-repost = @post.repost? | 				if (this.p.text) { | ||||||
| 				@p = if @is-repost then @post.repost else @post | 					const tokens = this.analyze(this.p.text); | ||||||
|  |  | ||||||
| 				@title = @date-stringify @p.created_at | 					this.refs.text.innerHTML = this.compile(tokens); | ||||||
|  |  | ||||||
| 				@update! | 					this.refs.text.children.forEach(e => { | ||||||
|  | 						if (e.tagName == 'MK-URL') riot.mount(e); | ||||||
|  | 					}); | ||||||
|  |  | ||||||
| 				if @p.text? | 					// URLをプレビュー | ||||||
| 					tokens = @analyze @p.text |  | ||||||
| 					@refs.text.innerHTML = @compile tokens |  | ||||||
|  |  | ||||||
| 					@refs.text.children.for-each (e) ~> |  | ||||||
| 						if e.tag-name == \MK-URL |  | ||||||
| 							riot.mount e |  | ||||||
|  |  | ||||||
| 					# URLをプレビュー |  | ||||||
| 					tokens | 					tokens | ||||||
| 						.filter (t) -> t.type == \link | 					.filter(t => t.type == 'link') | ||||||
| 						.map (t) ~> | 					.map(t => { | ||||||
| 							@preview = @refs.text.append-child document.create-element \mk-url-preview | 						riot.mount(this.refs.text.appendChild(document.createElement('mk-url-preview')), { | ||||||
| 							riot.mount @preview, do | 							url: t.content | ||||||
| 								url: t.content | 						}); | ||||||
|  | 					}); | ||||||
|  | 				} | ||||||
|  |  | ||||||
| 				# Get likes | 				// Get likes | ||||||
| 				@api \posts/likes do | 				this.api('posts/likes', { | ||||||
| 					post_id: @p.id | 					post_id: this.p.id, | ||||||
| 					limit: 8 | 					limit: 8 | ||||||
| 				.then (likes) ~> | 				}).then(likes => { | ||||||
| 					@likes = likes | 					this.update({ | ||||||
| 					@update! | 						likes: likes | ||||||
|  | 					}); | ||||||
|  | 				}); | ||||||
|  |  | ||||||
| 				# Get reposts | 				// Get reposts | ||||||
| 				@api \posts/reposts do | 				this.api('posts/reposts', { | ||||||
| 					post_id: @p.id | 					post_id: this.p.id, | ||||||
| 					limit: 8 | 					limit: 8 | ||||||
| 				.then (reposts) ~> | 				}).then(reposts => { | ||||||
| 					@reposts = reposts | 					this.update({ | ||||||
| 					@update! | 						reposts: reposts | ||||||
|  | 					}); | ||||||
|  | 				}); | ||||||
|  |  | ||||||
| 				# Get replies | 				// Get replies | ||||||
| 				@api \posts/replies do | 				this.api('posts/replies', { | ||||||
| 					post_id: @p.id | 					post_id: this.p.id, | ||||||
| 					limit: 8 | 					limit: 8 | ||||||
| 				.then (replies) ~> | 				}).then(replies => { | ||||||
| 					@replies = replies | 					this.update({ | ||||||
| 					@update! | 						replies: replies | ||||||
|  | 					}); | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 				@update! | 		this.reply = () => { | ||||||
|  | 			riot.mount(document.body.appendChild(document.createElement('mk-post-form-window')), { | ||||||
|  | 				reply: this.p | ||||||
|  | 			}); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@reply = ~> | 		this.repost = () => { | ||||||
| 			form = document.body.append-child document.create-element \mk-post-form-window | 			riot.mount(document.body.appendChild(document.createElement('mk-repost-form-window')), { | ||||||
| 			riot.mount form, do | 				post: this.p | ||||||
| 				reply: @p | 			}); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@repost = ~> | 		this.like = () => { | ||||||
| 			form = document.body.append-child document.create-element \mk-repost-form-window | 			if (this.p.is_liked) { | ||||||
| 			riot.mount form, do | 				this.api('posts/likes/delete', { | ||||||
| 				post: @p | 					post_id: this.p.id | ||||||
|  | 				}).then(() => { | ||||||
|  | 					this.p.is_liked = false; | ||||||
|  | 					this.update(); | ||||||
|  | 				}); | ||||||
|  | 			} else { | ||||||
|  | 				this.api('posts/likes/create', { | ||||||
|  | 					post_id: this.p.id | ||||||
|  | 				}).then(() => { | ||||||
|  | 					this.p.is_liked = true; | ||||||
|  | 					this.update(); | ||||||
|  | 				}); | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@like = ~> | 		this.loadContext = () => { | ||||||
| 			if @p.is_liked | 			this.loadingContext = true; | ||||||
| 				@api \posts/likes/delete do |  | ||||||
| 					post_id: @p.id |  | ||||||
| 				.then ~> |  | ||||||
| 					@p.is_liked = false |  | ||||||
| 					@update! |  | ||||||
| 			else |  | ||||||
| 				@api \posts/likes/create do |  | ||||||
| 					post_id: @p.id |  | ||||||
| 				.then ~> |  | ||||||
| 					@p.is_liked = true |  | ||||||
| 					@update! |  | ||||||
|  |  | ||||||
| 		@load-context = ~> | 			// Fetch context | ||||||
| 			@loading-context = true | 			this.api('posts/context', { | ||||||
|  | 				post_id: this.p.reply_to_id | ||||||
| 			# Get context | 			}).then(context => { | ||||||
| 			@api \posts/context do | 				this.update({ | ||||||
| 				post_id: @p.reply_to_id | 					loadContext: false, | ||||||
| 			.then (context) ~> | 					content: context.reverse() | ||||||
| 				@context = context.reverse! | 				}); | ||||||
| 				@loading-context = false | 			}); | ||||||
| 				@update! | 		}; | ||||||
| 	</script> | 	</script> | ||||||
| </mk-post-detail> | </mk-post-detail> | ||||||
|   | |||||||
| @@ -32,24 +32,31 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@uploading-files = [] | 		this.uploadingFiles = []; | ||||||
| 		@files = [] | 		this.files = []; | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			@refs.window.refs.form.focus! | 			this.refs.window.refs.form.focus(); | ||||||
|  |  | ||||||
| 			@refs.window.on \closed ~> | 			this.refs.window.on('closed', () => { | ||||||
| 				@unmount! | 				this.unmount(); | ||||||
|  | 			}); | ||||||
|  |  | ||||||
| 			@refs.window.refs.form.on \post ~> | 			this.refs.window.refs.form.on('post', () => { | ||||||
| 				@refs.window.close! | 				this.refs.window.close(); | ||||||
|  | 			}); | ||||||
|  |  | ||||||
| 			@refs.window.refs.form.on \change-uploading-files (files) ~> | 			this.refs.window.refs.form.on('change-uploading-files', files => { | ||||||
| 				@uploading-files = files | 				this.update({ | ||||||
| 				@update! | 					uploadingFiles: files | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
|  |  | ||||||
| 			@refs.window.refs.form.on \change-files (files) ~> | 			this.refs.window.refs.form.on('change-files', files => { | ||||||
| 				@files = files | 				this.update({ | ||||||
| 				@update! | 					files: files | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
| 	</script> | 	</script> | ||||||
| </mk-post-form-window> | </mk-post-form-window> | ||||||
|   | |||||||
| @@ -305,161 +305,160 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		get-cat = require '../../common/scripts/get-cat' | 		const getCat = require('../../common/scripts/get-cat'); | ||||||
|  |  | ||||||
| 		@mixin \api | 		this.mixin('api'); | ||||||
| 		@mixin \notify | 		this.mixin('notify'); | ||||||
| 		@mixin \autocomplete | 		this.mixin('autocomplete'); | ||||||
|  |  | ||||||
| 		@wait = false | 		this.wait = false; | ||||||
| 		@uploadings = [] | 		this.uploadings = []; | ||||||
| 		@files = [] | 		this.files = []; | ||||||
| 		@autocomplete = null | 		this.autocomplete = null; | ||||||
| 		@poll = false | 		this.poll = false; | ||||||
|  |  | ||||||
| 		@in-reply-to-post = @opts.reply | 		this.inReplyToPost = this.opts.reply; | ||||||
|  |  | ||||||
| 		# https://github.com/riot/riot/issues/2080 | 		// https://github.com/riot/riot/issues/2080 | ||||||
| 		if @in-reply-to-post == '' then @in-reply-to-post = null | 		if (this.inReplyToPost == '') this.inReplyToPost = null; | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			@refs.uploader.on \uploaded (file) ~> | 			this.refs.uploader.on('uploaded', file => { | ||||||
| 				@add-file file | 				this.addFile(file); | ||||||
|  | 			}); | ||||||
|  |  | ||||||
| 			@refs.uploader.on \change-uploads (uploads) ~> | 			this.refs.uploader.on('change-uploads', uploads => { | ||||||
| 				@trigger \change-uploading-files uploads | 				this.trigger('change-uploading-files', uploads); | ||||||
|  | 			}); | ||||||
|  |  | ||||||
| 			@autocomplete = new @Autocomplete @refs.text | 			this.autocomplete = new this.Autocomplete(this.refs.text); | ||||||
| 			@autocomplete.attach! | 			this.autocomplete.attach(); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@on \unmount ~> | 		this.on('unmount', () => { | ||||||
| 			@autocomplete.detach! | 			this.autocomplete.detach(); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@focus = ~> | 		this.focus = () => { | ||||||
| 			@refs.text.focus! | 			this.refs.text.focus(); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@clear = ~> | 		this.clear = () => { | ||||||
| 			@refs.text.value = '' | 			this.refs.text.value = ''; | ||||||
| 			@files = [] | 			this.files = []; | ||||||
| 			@trigger \change-files | 			this.trigger('change-files'); | ||||||
| 			@update! | 			this.update(); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@ondragover = (e) ~> | 		this.ondragover = e => { | ||||||
| 			e.stop-propagation! | 			e.stopPropagation(); | ||||||
| 			@draghover = true | 			this.draghover = true; | ||||||
| 			# ドラッグされてきたものがファイルだったら | 			e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; | ||||||
| 			if e.data-transfer.effect-allowed == \all | 			return false; | ||||||
| 				e.data-transfer.drop-effect = \copy | 		}; | ||||||
| 			else |  | ||||||
| 				e.data-transfer.drop-effect = \move |  | ||||||
| 			return false |  | ||||||
|  |  | ||||||
| 		@ondragenter = (e) ~> | 		this.ondragenter = e => { | ||||||
| 			@draghover = true | 			this.draghover = true; | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@ondragleave = (e) ~> | 		this.ondragleave = e => { | ||||||
| 			@draghover = false | 			this.draghover = false; | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@ondrop = (e) ~> | 		this.ondrop = e => { | ||||||
| 			e.prevent-default! | 			e.preventDefault(); | ||||||
| 			e.stop-propagation! | 			e.stopPropagation(); | ||||||
| 			@draghover = false | 			this.draghover = false; | ||||||
|  |  | ||||||
| 			# ファイルだったら | 			// ファイルだったら | ||||||
| 			if e.data-transfer.files.length > 0 | 			if (e.dataTransfer.files.length > 0) { | ||||||
| 				Array.prototype.for-each.call e.data-transfer.files, (file) ~> | 				e.dataTransfer.files.forEach(this.upload); | ||||||
| 					@upload file | 			} | ||||||
| 				return false |  | ||||||
|  |  | ||||||
| 			# データ取得 | 			return false; | ||||||
| 			data = e.data-transfer.get-data 'text' | 		}; | ||||||
| 			if !data? |  | ||||||
| 				return false |  | ||||||
|  |  | ||||||
| 			try | 		this.onkeydown = e => { | ||||||
| 				# パース | 			if ((e.which == 10 || e.which == 13) && (e.ctrlKey || e.metaKey)) this.post(); | ||||||
| 				obj = JSON.parse data | 		}; | ||||||
|  |  | ||||||
| 				# (ドライブの)ファイルだったら | 		this.onpaste = e => { | ||||||
| 				if obj.type == \file | 			e.clipboardData.items.forEach(item => { | ||||||
| 					@add-file obj.file | 				if (item.kind == 'file') { | ||||||
| 			catch | 					this.upload(item.getAsFile()); | ||||||
| 				# ignore | 				} | ||||||
|  | 			}); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 			return false | 		this.selectFile = () => { | ||||||
|  | 			this.refs.file.click(); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@onkeydown = (e) ~> | 		this.selectFileFromDrive = () => { | ||||||
| 			if (e.which == 10 || e.which == 13) && (e.ctrl-key || e.meta-key) | 			const i = riot.mount(document.body.appendChild(document.createElement('mk-select-file-from-drive-window')), { | ||||||
| 				@post! |  | ||||||
|  |  | ||||||
| 		@onpaste = (e) ~> |  | ||||||
| 			data = e.clipboard-data |  | ||||||
| 			items = data.items |  | ||||||
| 			for i from 0 to items.length - 1 |  | ||||||
| 				item = items[i] |  | ||||||
| 				switch (item.kind) |  | ||||||
| 					| \file => |  | ||||||
| 						@upload item.get-as-file! |  | ||||||
|  |  | ||||||
| 		@select-file = ~> |  | ||||||
| 			@refs.file.click! |  | ||||||
|  |  | ||||||
| 		@select-file-from-drive = ~> |  | ||||||
| 			browser = document.body.append-child document.create-element \mk-select-file-from-drive-window |  | ||||||
| 			i = riot.mount browser, do |  | ||||||
| 				multiple: true | 				multiple: true | ||||||
| 			i[0].one \selected (files) ~> | 			})[0]; | ||||||
| 				files.for-each @add-file | 			i.one('selected', files => { | ||||||
|  | 				files.forEach(this.addFile); | ||||||
|  | 			}); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@change-file = ~> | 		this.changeFile = () => { | ||||||
| 			files = @refs.file.files | 			this.refs.file.files.forEach(this.upload); | ||||||
| 			for i from 0 to files.length - 1 | 		}; | ||||||
| 				file = files.item i |  | ||||||
| 				@upload file |  | ||||||
|  |  | ||||||
| 		@upload = (file) ~> | 		this.upload = file => { | ||||||
| 			@refs.uploader.upload file | 			this.refs.uploader.upload(file); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@add-file = (file) ~> | 		this.addFile = file => { | ||||||
| 			file._remove = ~> | 			file._remove = () => { | ||||||
| 				@files = @files.filter (x) -> x.id != file.id | 				this.files = this.files.filter(x => x.id != file.id); | ||||||
| 				@trigger \change-files @files | 				this.trigger('change-files', this.files); | ||||||
| 				@update! | 				this.update(); | ||||||
|  | 			}; | ||||||
|  |  | ||||||
| 			@files.push file | 			this.files.push(file); | ||||||
| 			@trigger \change-files @files | 			this.trigger('change-files', this.files); | ||||||
| 			@update! | 			this.update(); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@add-poll = ~> | 		this.addPoll = () => { | ||||||
| 			@poll = true | 			this.poll = true; | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@on-poll-destroyed = ~> | 		this.onPollDestroyed = () => { | ||||||
| 			@update do | 			this.update({ | ||||||
| 				poll: false | 				poll: false | ||||||
|  | 			}); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@post = (e) ~> | 		this.post = e => { | ||||||
| 			@wait = true | 			this.wait = true; | ||||||
|  |  | ||||||
| 			files = if @files? and @files.length > 0 | 			const files = this.files && this.files.length > 0 | ||||||
| 				then @files.map (f) -> f.id | 				? this.files.map(f => f.id) | ||||||
| 				else undefined | 				: undefined; | ||||||
|  |  | ||||||
| 			@api \posts/create do | 			this.api('posts/create', { | ||||||
| 				text: @refs.text.value | 				text: this.refs.text.value, | ||||||
| 				media_ids: files | 				media_ids: files, | ||||||
| 				reply_to_id: if @in-reply-to-post? then @in-reply-to-post.id else undefined | 				reply_to_id: this.inReplyToPost ? this.inReplyToPost.id : undefined, | ||||||
| 				poll: if @poll then @refs.poll.get! else undefined | 				poll: this.poll ? this.refs.poll.get() : undefined | ||||||
| 			.then (data) ~> | 			}).then(data => { | ||||||
| 				@trigger \post | 				this.trigger('post'); | ||||||
| 				@notify if @in-reply-to-post? then '返信しました!' else '投稿しました!' | 				this.notify(this.inReplyToPost ? '返信しました!' : '投稿しました!'); | ||||||
| 			.catch (err) ~> | 			}).catch(err => { | ||||||
| 				console.error err | 				this.notify('投稿できませんでした'); | ||||||
| 				@notify '投稿できませんでした' | 			}).then(() => { | ||||||
| 			.then ~> | 				this.update({ | ||||||
| 				@wait = false | 					wait: false | ||||||
| 				@update! | 				}); | ||||||
|  | 			}); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@cat = ~> | 		this.cat = () => { | ||||||
| 			@refs.text.value = @refs.text.value + get-cat! | 			this.refs.text.value += getCat(); | ||||||
|  | 		}; | ||||||
| 	</script> | 	</script> | ||||||
| </mk-post-form> | </mk-post-form> | ||||||
|   | |||||||
| @@ -83,11 +83,11 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \date-stringify | 		this.mixin('date-stringify'); | ||||||
| 		@mixin \user-preview | 		this.mixin('user-preview'); | ||||||
|  |  | ||||||
| 		@post = @opts.post | 		this.post = this.opts.post; | ||||||
|  |  | ||||||
| 		@title = @date-stringify @post.created_at | 		this.title = this.dateStringify(this.post.created_at); | ||||||
| 	</script> | 	</script> | ||||||
| </mk-post-preview> | </mk-post-preview> | ||||||
|   | |||||||
| @@ -75,20 +75,25 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@title = @opts.title | 		this.title = this.opts.title; | ||||||
| 		@value = parse-int @opts.value, 10 | 		this.value = parseInt(this.opts.value, 10); | ||||||
| 		@max = parse-int @opts.max, 10 | 		this.max = parseInt(this.opts.max, 10); | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			@refs.window.on \closed ~> | 			this.refs.window.on('closed', () => { | ||||||
| 				@unmount! | 				this.unmount(); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@update-progress = (value, max) ~> | 		this.updateProgress = (value, max) => { | ||||||
| 			@value = parse-int value, 10 | 			this.update({ | ||||||
| 			@max = parse-int max, 10 | 				value: parseInt(value, 10), | ||||||
| 			@update! | 				max: parseInt(max, 10) | ||||||
|  | 			}); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@close = ~> | 		this.close = () => { | ||||||
| 			@refs.window.close! | 			this.refs.window.close(); | ||||||
|  | 		}; | ||||||
| 	</script> | 	</script> | ||||||
| </mk-progress-dialog> | </mk-progress-dialog> | ||||||
|   | |||||||
| @@ -12,25 +12,32 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@on-document-keydown = (e) ~> | 		this.onDocumentKeydown = e => { | ||||||
| 			tag = e.target.tag-name.to-lower-case! | 			if (e.target.tagName != 'INPUT' && e.target.tagName != 'TEXTAREA') { | ||||||
| 			if tag != \input and tag != \textarea | 				if (e.which == 27) { // Esc | ||||||
| 				if e.which == 27 # Esc | 					this.refs.window.close(); | ||||||
| 					@refs.window.close! | 				} | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			@refs.window.refs.form.on \cancel ~> | 			this.refs.window.refs.form.on('cancel', () => { | ||||||
| 				@refs.window.close! | 				this.refs.window.close(); | ||||||
|  | 			}); | ||||||
|  |  | ||||||
| 			@refs.window.refs.form.on \posted ~> | 			this.refs.window.refs.form.on('posted', () => { | ||||||
| 				@refs.window.close! | 				this.refs.window.close(); | ||||||
|  | 			}); | ||||||
|  |  | ||||||
| 			document.add-event-listener \keydown @on-document-keydown | 			document.addEventListener('keydown', this.onDocumentKeydown); | ||||||
|  |  | ||||||
| 			@refs.window.on \closed ~> | 			this.refs.window.on('closed', () => { | ||||||
| 				@unmount! | 				this.unmount(); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@on \unmount ~> | 		this.on('unmount', () => { | ||||||
| 			document.remove-event-listener \keydown @on-document-keydown | 			document.removeEventListener('keydown', this.onDocumentKeydown); | ||||||
|  | 		}); | ||||||
| 	</script> | 	</script> | ||||||
| </mk-repost-form-window> | </mk-repost-form-window> | ||||||
|   | |||||||
| @@ -114,31 +114,35 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \api | 		this.mixin('api'); | ||||||
| 		@mixin \notify | 		this.mixin('notify'); | ||||||
|  |  | ||||||
| 		@wait = false | 		this.wait = false; | ||||||
| 		@quote = false | 		this.quote = false; | ||||||
|  |  | ||||||
| 		@cancel = ~> | 		this.cancel = () => { | ||||||
| 			@trigger \cancel | 			this.trigger('cancel'); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@ok = ~> | 		this.ok = () => { | ||||||
| 			@wait = true | 			this.wait = true; | ||||||
| 			@api \posts/create do | 			this.api('posts/create', { | ||||||
| 				repost_id: @opts.post.id | 				repost_id: this.opts.post.id, | ||||||
| 				text: if @quote then @refs.text.value else undefined | 				text: this.quote ? this.refs.text.value : undefined | ||||||
| 			.then (data) ~> | 			}).then(data => { | ||||||
| 				@trigger \posted | 				this.trigger('posted'); | ||||||
| 				@notify 'Repostしました!' | 				this.notify('Repostしました!'); | ||||||
| 			.catch (err) ~> | 			}).catch(err => { | ||||||
| 				console.error err | 				this.notify('Repostできませんでした'); | ||||||
| 				@notify 'Repostできませんでした' | 			}).then(() => { | ||||||
| 			.then ~> | 				this.update({ | ||||||
| 				@wait = false | 					wait: false | ||||||
| 				@update! | 				}); | ||||||
|  | 			}); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@onquote = ~> | 		this.onquote = () => { | ||||||
| 			@quote = true | 			this.quote = true; | ||||||
|  | 		}; | ||||||
| 	</script> | 	</script> | ||||||
| </mk-repost-form> | </mk-repost-form> | ||||||
|   | |||||||
| @@ -28,59 +28,64 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \api | 		this.mixin('api'); | ||||||
| 		@mixin \get-post-summary | 		this.mixin('get-post-summary'); | ||||||
|  |  | ||||||
| 		@query = @opts.query | 		this.query = this.opts.query; | ||||||
| 		@is-loading = true | 		this.isLoading = true; | ||||||
| 		@is-empty = false | 		this.isEmpty = false; | ||||||
| 		@more-loading = false | 		this.moreLoading = false; | ||||||
| 		@page = 0 | 		this.page = 0; | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			document.add-event-listener \keydown @on-document-keydown | 			document.addEventListener('keydown', this.onDocumentKeydown); | ||||||
| 			window.add-event-listener \scroll @on-scroll | 			window.addEventListener('scroll', this.onScroll); | ||||||
|  |  | ||||||
| 			@api \posts/search do | 			this.api('posts/search', { | ||||||
| 				query: @query | 				query: this.query | ||||||
| 			.then (posts) ~> | 			}).then(posts => { | ||||||
| 				@is-loading = false | 				this.update({ | ||||||
| 				@is-empty = posts.length == 0 | 					isLoading: false, | ||||||
| 				@update! | 					isEmpty: posts.length == 0 | ||||||
| 				@refs.timeline.set-posts posts | 				}); | ||||||
| 				@trigger \loaded | 				this.refs.timeline.setPosts(posts); | ||||||
| 			.catch (err) ~> | 				this.trigger('loaded'); | ||||||
| 				console.error err | 			}); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@on \unmount ~> | 		this.on('unmount', () => { | ||||||
| 			document.remove-event-listener \keydown @on-document-keydown | 			document.removeEventListener('keydown', this.onDocumentKeydown); | ||||||
| 			window.remove-event-listener \scroll @on-scroll | 			window.removeEventListener('scroll', this.onScroll); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@on-document-keydown = (e) ~> | 		this.onDocumentKeydown = e => { | ||||||
| 			tag = e.target.tag-name.to-lower-case! | 			if (e.target.tagName != 'INPUT' && e.target.tagName != 'TEXTAREA') { | ||||||
| 			if tag != \input and tag != \textarea | 				if (e.which == 84) { // t | ||||||
| 				if e.which == 84 # t | 					this.refs.timeline.focus(); | ||||||
| 					@refs.timeline.focus! | 				} | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@more = ~> | 		this.more = () => { | ||||||
| 			if @more-loading or @is-loading or @timeline.posts.length == 0 | 			if (this.moreLoading || this.isLoading || this.timeline.posts.length == 0) return; | ||||||
| 				return | 			this.update({ | ||||||
| 			@more-loading = true | 				moreLoading: true | ||||||
| 			@update! | 			}); | ||||||
| 			@api \posts/search do | 			this.api('posts/search', { | ||||||
| 				query: @query | 				query: this.query, | ||||||
| 				page: @page + 1 | 				page: this.page + 1 | ||||||
| 			.then (posts) ~> | 			}).then(posts => { | ||||||
| 				@more-loading = false | 				this.update({ | ||||||
| 				@page++ | 					moreLoading: false, | ||||||
| 				@update! | 					page: page + 1 | ||||||
| 				@refs.timeline.prepend-posts posts | 				}); | ||||||
| 			.catch (err) ~> | 				this.refs.timeline.prependPosts(posts); | ||||||
| 				console.error err | 			}); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@on-scroll = ~> | 		this.onScroll = () => { | ||||||
| 			current = window.scroll-y + window.inner-height | 			const current = window.scrollY + window.innerHeight; | ||||||
| 			if current > document.body.offset-height - 16 # 遊び | 			if (current > document.body.offsetHeight - 16) this.more(); | ||||||
| 				@more! | 		}; | ||||||
| 	</script> | 	</script> | ||||||
| </mk-search-posts> | </mk-search-posts> | ||||||
|   | |||||||
| @@ -23,10 +23,12 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@query = @opts.query | 		this.query = this.opts.query; | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			@refs.posts.on \loaded ~> | 			this.refs.posts.on('loaded', () => { | ||||||
| 				@trigger \loaded | 				this.trigger('loaded'); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
| 	</script> | 	</script> | ||||||
| </mk-search> | </mk-search> | ||||||
|   | |||||||
| @@ -131,31 +131,38 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@file = [] | 		this.file = []; | ||||||
|  |  | ||||||
| 		@multiple = if @opts.multiple? then @opts.multiple else false | 		this.multiple = this.opts.multiple != null ? this.opts.multiple : false; | ||||||
| 		@title = @opts.title || '<i class="fa fa-file-o"></i>ファイルを選択' | 		this.title = this.opts.title || '<i class="fa fa-file-o"></i>ファイルを選択'; | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			@refs.window.refs.browser.on \selected (file) ~> | 			this.refs.window.refs.browser.on('selected', file => { | ||||||
| 				@file = file | 				this.file = file; | ||||||
| 				@ok! | 				this.ok(); | ||||||
|  | 			}); | ||||||
|  |  | ||||||
| 			@refs.window.refs.browser.on \change-selection (files) ~> | 			this.refs.window.refs.browser.on('change-selection', files => { | ||||||
| 				@file = files | 				this.file = files; | ||||||
| 				@update! | 				this.update(); | ||||||
|  | 			}); | ||||||
|  |  | ||||||
| 			@refs.window.on \closed ~> | 			this.refs.window.on('closed', () => { | ||||||
| 				@unmount! | 				this.unmount(); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@close = ~> | 		this.close = () => { | ||||||
| 			@refs.window.close! | 			this.refs.window.close(); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@upload = ~> | 		this.upload = () => { | ||||||
| 			@refs.window.refs.browser.select-local-file! | 			this.refs.window.refs.browser.selectLocalFile(); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@ok = ~> | 		this.ok = () => { | ||||||
| 			@trigger \selected @file | 			this.trigger('selected', this.file); | ||||||
| 			@refs.window.close! | 			this.refs.window.close(); | ||||||
|  | 		}; | ||||||
| 	</script> | 	</script> | ||||||
| </mk-select-file-from-drive-window> | </mk-select-file-from-drive-window> | ||||||
|   | |||||||
| @@ -31,15 +31,17 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \i | 		this.mixin('i'); | ||||||
| 		@mixin \update-avatar | 		this.mixin('update-avatar'); | ||||||
|  |  | ||||||
| 		@set = ~> | 		this.set = () => { | ||||||
| 			@update-avatar @I | 			this.updateAvatar(this.I); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@close = (e) ~> | 		this.close = e => { | ||||||
| 			e.prevent-default! | 			e.preventDefault(); | ||||||
| 			e.stop-propagation! | 			e.stopPropagation(); | ||||||
| 			@unmount! | 			this.unmount(); | ||||||
|  | 		}; | ||||||
| 	</script> | 	</script> | ||||||
| </mk-set-avatar-suggestion> | </mk-set-avatar-suggestion> | ||||||
|   | |||||||
| @@ -31,15 +31,17 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \i | 		this.mixin('i'); | ||||||
| 		@mixin \update-banner | 		this.mixin('update-banner'); | ||||||
|  |  | ||||||
| 		@set = ~> | 		this.set = () => { | ||||||
| 			@update-banner @I | 			this.updateBanner(this.I); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@close = (e) ~> | 		this.close = e => { | ||||||
| 			e.prevent-default! | 			e.preventDefault(); | ||||||
| 			e.stop-propagation! | 			e.stopPropagation(); | ||||||
| 			@unmount! | 			this.unmount(); | ||||||
|  | 		}; | ||||||
| 	</script> | 	</script> | ||||||
| </mk-set-banner-suggestion> | </mk-set-banner-suggestion> | ||||||
|   | |||||||
| @@ -15,11 +15,14 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			@refs.window.on \closed ~> | 			this.refs.window.on('closed', () => { | ||||||
| 				@unmount! | 				this.unmount(); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@close = ~> | 		this.close = () => { | ||||||
| 			@refs.window.close! | 			this.refs.window.close(); | ||||||
|  | 		}; | ||||||
| 	</script> | 	</script> | ||||||
| </mk-settings-window> | </mk-settings-window> | ||||||
|   | |||||||
| @@ -47,11 +47,6 @@ | |||||||
| 				<p>読み込みを高速化する</p> | 				<p>読み込みを高速化する</p> | ||||||
| 				<p>API通信時に新鮮なユーザー情報をキャッシュすることでフェッチのオーバーヘッドを無くします。(実験的)</p> | 				<p>API通信時に新鮮なユーザー情報をキャッシュすることでフェッチのオーバーヘッドを無くします。(実験的)</p> | ||||||
| 			</label> | 			</label> | ||||||
| 			<label class="checkbox"> |  | ||||||
| 				<input type="checkbox" checked={ I.data.debug } onclick={ updateDebug }/> |  | ||||||
| 				<p>開発者モード</p> |  | ||||||
| 				<p>デバッグ等の開発者モードを有効にします。</p> |  | ||||||
| 			</label> |  | ||||||
| 			<label class="checkbox"> | 			<label class="checkbox"> | ||||||
| 				<input type="checkbox" checked={ I.data.nya } onclick={ updateNya }/> | 				<input type="checkbox" checked={ I.data.nya } onclick={ updateNya }/> | ||||||
| 				<p><i>な</i>を<i>にゃ</i>に変換する</p> | 				<p><i>な</i>を<i>にゃ</i>に変換する</p> | ||||||
| @@ -198,46 +193,49 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \i | 		this.mixin('i'); | ||||||
| 		@mixin \api | 		this.mixin('api'); | ||||||
| 		@mixin \dialog | 		this.mixin('notify'); | ||||||
| 		@mixin \update-avatar | 		this.mixin('dialog'); | ||||||
|  | 		this.mixin('update-avatar'); | ||||||
|  |  | ||||||
| 		@page = \account | 		this.page = 'account'; | ||||||
|  |  | ||||||
| 		@set-page = (page) ~> | 		this.setPage = page => { | ||||||
| 			@page = page | 			this.page = page; | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@avatar = ~> | 		this.avatar = () => { | ||||||
| 			@update-avatar @I | 			this.updateAvatar(this.I); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@update-account = ~> | 		this.updateAccount = () => { | ||||||
| 			@api \i/update do | 			this.api('i/update', { | ||||||
| 				name: @refs.account-name.value | 				name: this.refs.accountName.value, | ||||||
| 				location: @refs.account-location.value | 				location: this.refs.accountLocation.value, | ||||||
| 				bio: @refs.account-bio.value | 				bio: this.refs.accountBio.value, | ||||||
| 				birthday: @refs.account-birthday.value | 				birthday: this.refs.accountBirthday.value | ||||||
| 			.then (i) ~> | 			}).then(() => { | ||||||
| 				alert \ok | 				this.notify('プロフィールを更新しました'); | ||||||
| 			.catch (err) ~> | 			}); | ||||||
| 				console.error err | 		}; | ||||||
|  |  | ||||||
| 		@update-cache = ~> | 		this.updateCache = () => { | ||||||
| 			@I.data.cache = !@I.data.cache | 			this.I.data.cache = !this.I.data.cache; | ||||||
| 			@api \i/appdata/set do | 			this.api('i/appdata/set', { | ||||||
| 				data: JSON.stringify do | 				data: JSON.stringify({ | ||||||
| 					cache: @I.data.cache | 					cache: this.I.data.cache | ||||||
|  | 				}) | ||||||
|  | 			}); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@update-debug = ~> | 		this.updateNya = () => { | ||||||
| 			@I.data.debug = !@I.data.debug | 			this.I.data.nya = !this.I.data.nya; | ||||||
| 			@api \i/appdata/set do | 			this.api('i/appdata/set', { | ||||||
| 				data: JSON.stringify do | 				data: JSON.stringify({ | ||||||
| 					debug: @I.data.debug | 					nya: this.I.data.nya | ||||||
|  | 				}) | ||||||
| 		@update-nya = ~> | 			}); | ||||||
| 			@I.data.nya = !@I.data.nya | 		}; | ||||||
| 			@api \i/appdata/set do |  | ||||||
| 				data: JSON.stringify do |  | ||||||
| 					nya: @I.data.nya |  | ||||||
| 	</script> | 	</script> | ||||||
| </mk-settings> | </mk-settings> | ||||||
|   | |||||||
| @@ -1,54 +0,0 @@ | |||||||
| <mk-stream-indicator> |  | ||||||
| 	<p if={ state == 'initializing' }><i class="fa fa-spinner fa-spin"></i><span>接続中 |  | ||||||
| 			<mk-ellipsis></mk-ellipsis></span></p> |  | ||||||
| 	<p if={ state == 'reconnecting' }><i class="fa fa-spinner fa-spin"></i><span>切断されました 接続中 |  | ||||||
| 			<mk-ellipsis></mk-ellipsis></span></p> |  | ||||||
| 	<p if={ state == 'connected' }><i class="fa fa-check"></i><span>接続完了</span></p> |  | ||||||
| 	<style> |  | ||||||
| 		:scope |  | ||||||
| 			display block |  | ||||||
| 			pointer-events none |  | ||||||
| 			position fixed |  | ||||||
| 			z-index 16384 |  | ||||||
| 			bottom 8px |  | ||||||
| 			right 8px |  | ||||||
| 			margin 0 |  | ||||||
| 			padding 6px 12px |  | ||||||
| 			font-size 0.9em |  | ||||||
| 			color #fff |  | ||||||
| 			background rgba(0, 0, 0, 0.8) |  | ||||||
|  |  | ||||||
| 			> p |  | ||||||
| 				display block |  | ||||||
| 				margin 0 |  | ||||||
|  |  | ||||||
| 				> i |  | ||||||
| 					margin-right 0.25em |  | ||||||
|  |  | ||||||
| 	</style> |  | ||||||
| 	<script> |  | ||||||
| 		@mixin \stream |  | ||||||
|  |  | ||||||
| 		@on \before-mount ~> |  | ||||||
| 			@state = @get-stream-state! |  | ||||||
|  |  | ||||||
| 			if @state == \connected |  | ||||||
| 				@root.style.opacity = 0 |  | ||||||
|  |  | ||||||
| 		@stream-state-ev.on \connected ~> |  | ||||||
| 			@state = @get-stream-state! |  | ||||||
| 			@update! |  | ||||||
| 			set-timeout ~> |  | ||||||
| 				Velocity @root, { |  | ||||||
| 					opacity: 0 |  | ||||||
| 				} 200ms \linear |  | ||||||
| 			, 1000ms |  | ||||||
|  |  | ||||||
| 		@stream-state-ev.on \closed ~> |  | ||||||
| 			@state = @get-stream-state! |  | ||||||
| 			@update! |  | ||||||
| 			Velocity @root, { |  | ||||||
| 				opacity: 1 |  | ||||||
| 			} 0ms |  | ||||||
| 	</script> |  | ||||||
| </mk-stream-indicator> |  | ||||||
| @@ -1,5 +1,11 @@ | |||||||
| <mk-sub-post-content> | <mk-sub-post-content> | ||||||
| 	<div class="body"><a class="reply" if={ post.reply_to_id }><i class="fa fa-reply"></i></a><span ref="text"></span><a class="quote" if={ post.repost_id } href={ '/post:' + post.repost_id }>RP: ...</a></div> | 	<div class="body"> | ||||||
|  | 		<a class="reply" if={ post.reply_to_id }> | ||||||
|  | 			<i class="fa fa-reply"></i> | ||||||
|  | 		</a> | ||||||
|  | 		<span ref="text"></span> | ||||||
|  | 		<a class="quote" if={ post.repost_id } href={ '/post:' + post.repost_id }>RP: ...</a> | ||||||
|  | 	</div> | ||||||
| 	<details if={ post.media }> | 	<details if={ post.media }> | ||||||
| 		<summary>({ post.media.length }つのメディア)</summary> | 		<summary>({ post.media.length }つのメディア)</summary> | ||||||
| 		<mk-images-viewer images={ post.media }></mk-images-viewer> | 		<mk-images-viewer images={ post.media }></mk-images-viewer> | ||||||
| @@ -28,18 +34,20 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \text | 		this.mixin('text'); | ||||||
| 		@mixin \user-preview | 		this.mixin('user-preview'); | ||||||
|  |  | ||||||
| 		@post = @opts.post | 		this.post = this.opts.post; | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			if @post.text? | 			if (this.post.text) { | ||||||
| 				tokens = @analyze @post.text | 				const tokens = this.analyze(this.post.text); | ||||||
| 				@refs.text.innerHTML = @compile tokens, false | 				this.refs.text.innerHTML = this.compile(tokens, false); | ||||||
|  |  | ||||||
| 				@refs.text.children.for-each (e) ~> | 				this.refs.text.children.forEach(e => { | ||||||
| 					if e.tag-name == \MK-URL | 					if (e.tagName == 'MK-URL') riot.mount(e); | ||||||
| 						riot.mount e | 				}); | ||||||
|  | 			} | ||||||
|  | 		}); | ||||||
| 	</script> | 	</script> | ||||||
| </mk-sub-post-content> | </mk-sub-post-content> | ||||||
|   | |||||||
| @@ -8,15 +8,6 @@ | |||||||
| 			</div> | 			</div> | ||||||
| 		</div> | 		</div> | ||||||
| 	</article> | 	</article> | ||||||
| 	<script> |  | ||||||
| 		@mixin \date-stringify |  | ||||||
| 		@mixin \user-preview |  | ||||||
|  |  | ||||||
| 		@post = @opts.post |  | ||||||
|  |  | ||||||
| 		@title = @date-stringify @post.created_at |  | ||||||
|  |  | ||||||
| 	</script> |  | ||||||
| 	<style> | 	<style> | ||||||
| 		:scope | 		:scope | ||||||
| 			display block | 			display block | ||||||
| @@ -97,4 +88,11 @@ | |||||||
| 								font-size 80% | 								font-size 80% | ||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
|  | 	<script> | ||||||
|  | 		this.mixin('date-stringify'); | ||||||
|  | 		this.mixin('user-preview'); | ||||||
|  |  | ||||||
|  | 		this.post = this.opts.post; | ||||||
|  | 		this.title = this.dateStringify(this.post.created_at); | ||||||
|  | 	</script> | ||||||
| </mk-timeline-post-sub> | </mk-timeline-post-sub> | ||||||
|   | |||||||
| @@ -55,8 +55,13 @@ | |||||||
| 				<button class={ liked: p.is_liked } onclick={ like } title="善哉"><i class="fa fa-thumbs-o-up"></i> | 				<button class={ liked: p.is_liked } onclick={ like } title="善哉"><i class="fa fa-thumbs-o-up"></i> | ||||||
| 					<p class="count" if={ p.likes_count > 0 }>{ p.likes_count }</p> | 					<p class="count" if={ p.likes_count > 0 }>{ p.likes_count }</p> | ||||||
| 				</button> | 				</button> | ||||||
| 				<button onclick={ NotImplementedException }><i class="fa fa-ellipsis-h"></i></button> | 				<button onclick={ NotImplementedException }> | ||||||
| 				<button onclick={ toggleDetail } title="詳細"><i class="fa fa-caret-down" if={ !isDetailOpened }></i><i class="fa fa-caret-up" if={ isDetailOpened }></i></button> | 					<i class="fa fa-ellipsis-h"></i> | ||||||
|  | 				</button> | ||||||
|  | 				<button onclick={ toggleDetail } title="詳細"> | ||||||
|  | 					<i class="fa fa-caret-down" if={ !isDetailOpened }></i> | ||||||
|  | 					<i class="fa fa-caret-up" if={ isDetailOpened }></i> | ||||||
|  | 				</button> | ||||||
| 			</footer> | 			</footer> | ||||||
| 		</div> | 		</div> | ||||||
| 	</article> | 	</article> | ||||||
| @@ -312,96 +317,124 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \api | 		this.mixin('api'); | ||||||
| 		@mixin \text | 		this.mixin('text'); | ||||||
| 		@mixin \date-stringify | 		this.mixin('date-stringify'); | ||||||
| 		@mixin \user-preview | 		this.mixin('user-preview'); | ||||||
| 		@mixin \NotImplementedException | 		this.mixin('NotImplementedException'); | ||||||
|  |  | ||||||
| 		@post = @opts.post | 		this.post = this.opts.post; | ||||||
| 		@is-repost = @post.repost? and !@post.text? | 		this.isRepost = this.post.repost && this.post.text == null; | ||||||
| 		@p = if @is-repost then @post.repost else @post | 		this.p = this.isRepost ? this.post.repost : this.post; | ||||||
|  |  | ||||||
| 		@title = @date-stringify @p.created_at | 		this.title = this.dateStringify(this.p.created_at); | ||||||
|  |  | ||||||
| 		@url = CONFIG.url + '/' + @p.user.username + '/' + @p.id | 		this.url = CONFIG.url + '/' + this.p.user.username + '/' + this.p.id; | ||||||
| 		@is-detail-opened = false | 		this.isDetailOpened = false; | ||||||
|  |  | ||||||
| 		@on \mount ~> | 		this.on('mount', () => { | ||||||
| 			if @p.text? | 			if (this.p.text) { | ||||||
| 				tokens = if @p._highlight? | 				const tokens = this.analyze(this.p.text); | ||||||
| 					then @analyze @p._highlight |  | ||||||
| 					else @analyze @p.text |  | ||||||
|  |  | ||||||
| 				@refs.text.innerHTML = @refs.text.innerHTML.replace '<p class="dummy"></p>' if @p._highlight? | 				this.refs.text.innerHTML = this.refs.text.innerHTML.replace('<p class="dummy"></p>', this.compile(tokens)); | ||||||
| 					then @compile tokens, true, false |  | ||||||
| 					else @compile tokens |  | ||||||
|  |  | ||||||
| 				@refs.text.children.for-each (e) ~> | 				this.refs.text.children.forEach(e => { | ||||||
| 					if e.tag-name == \MK-URL | 					if (e.tagName == 'MK-URL') riot.mount(e); | ||||||
| 						riot.mount e | 				}); | ||||||
|  |  | ||||||
| 				# URLをプレビュー | 				// URLをプレビュー | ||||||
| 				tokens | 				tokens | ||||||
| 					.filter (t) -> t.type == \link | 				.filter(t => t.type == 'link') | ||||||
| 					.map (t) ~> | 				.map(t => { | ||||||
| 						@preview = @refs.text.append-child document.create-element \mk-url-preview | 					riot.mount(this.refs.text.appendChild(document.createElement('mk-url-preview')), { | ||||||
| 						riot.mount @preview, do | 						url: t.content | ||||||
| 							url: t.content | 					}); | ||||||
|  | 				}); | ||||||
|  | 			} | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@reply = ~> | 		this.reply = () => { | ||||||
| 			form = document.body.append-child document.create-element \mk-post-form-window | 			riot.mount(document.body.appendChild(document.createElement('mk-post-form-window')), { | ||||||
| 			riot.mount form, do | 				reply: this.p | ||||||
| 				reply: @p | 			}); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@repost = ~> | 		this.repost = () => { | ||||||
| 			form = document.body.append-child document.create-element \mk-repost-form-window | 			riot.mount(document.body.appendChild(document.createElement('mk-repost-form-window')), { | ||||||
| 			riot.mount form, do | 				post: this.p | ||||||
| 				post: @p | 			}); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@like = ~> | 		this.like = () => { | ||||||
| 			if @p.is_liked | 			if (this.p.is_liked) { | ||||||
| 				@api \posts/likes/delete do | 				this.api('posts/likes/delete', { | ||||||
| 					post_id: @p.id | 					post_id: this.p.id | ||||||
| 				.then ~> | 				}).then(() => { | ||||||
| 					@p.is_liked = false | 					this.p.is_liked = false; | ||||||
| 					@update! | 					this.update(); | ||||||
| 			else | 				}); | ||||||
| 				@api \posts/likes/create do | 			} else { | ||||||
| 					post_id: @p.id | 				this.api('posts/likes/create', { | ||||||
| 				.then ~> | 					post_id: this.p.id | ||||||
| 					@p.is_liked = true | 				}).then(() => { | ||||||
| 					@update! | 					this.p.is_liked = true; | ||||||
|  | 					this.update(); | ||||||
|  | 				}); | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@toggle-detail = ~> | 		this.toggleDetail = () => { | ||||||
| 			@is-detail-opened = !@is-detail-opened | 			this.update({ | ||||||
| 			@update! | 				isDetailOpened: !this.isDetailOpened | ||||||
|  | 			}); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@on-key-down = (e) ~> | 		this.onKeyDown = e => { | ||||||
| 			should-be-cancel = true | 			let shouldBeCancel = true; | ||||||
| 			switch |  | ||||||
| 			| e.which == 38 or e.which == 74 or (e.which == 9 and e.shift-key) => # ↑, j or Shift+Tab |  | ||||||
| 				focus @root, (e) -> e.previous-element-sibling |  | ||||||
| 			| e.which == 40 or e.which == 75 or e.which == 9 => # ↓, k or Tab |  | ||||||
| 				focus @root, (e) -> e.next-element-sibling |  | ||||||
| 			| e.which == 81 or e.which == 69 => # q or e |  | ||||||
| 				@repost! |  | ||||||
| 			| e.which == 70 or e.which == 76 => # f or l |  | ||||||
| 				@like! |  | ||||||
| 			| e.which == 82 => # r |  | ||||||
| 				@reply! |  | ||||||
| 			| _ => |  | ||||||
| 				should-be-cancel = false |  | ||||||
|  |  | ||||||
| 			if should-be-cancel | 			switch (true) { | ||||||
| 				e.prevent-default! | 				case e.which == 38: // [↑] | ||||||
|  | 				case e.which == 74: // [j] | ||||||
|  | 				case e.which == 9 && e.shiftKey: // [Shift] + [Tab] | ||||||
|  | 					focus(this.root, e => e.previousElementSibling); | ||||||
|  | 					break; | ||||||
|  |  | ||||||
| 		function focus(el, fn) | 				case e.which == 40: // [↓] | ||||||
| 			target = fn el | 				case e.which == 75: // [k] | ||||||
| 			if target? | 				case e.which == 9: // [Tab] | ||||||
| 				if target.has-attribute \tabindex | 					focus(this.root, e => e.nextElementSibling); | ||||||
| 					target.focus! | 					break; | ||||||
| 				else |  | ||||||
| 					focus target, fn | 				case e.which == 81: // [q] | ||||||
|  | 				case e.which == 69: // [e] | ||||||
|  | 					this.repost(); | ||||||
|  | 					break; | ||||||
|  |  | ||||||
|  | 				case e.which == 70: // [f] | ||||||
|  | 				case e.which == 76: // [l] | ||||||
|  | 					this.like(); | ||||||
|  | 					break; | ||||||
|  |  | ||||||
|  | 				case e.which == 82: // [r] | ||||||
|  | 					this.reply(); | ||||||
|  | 					break; | ||||||
|  | 				 | ||||||
|  | 				default: | ||||||
|  | 					shouldBeCancel = false; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if (shouldBeCancel) e.preventDefault(); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		function focus(el, fn) { | ||||||
|  | 			const target = fn(el); | ||||||
|  | 			if (target) { | ||||||
|  | 				if (target.hasAttribute('tabindex')) { | ||||||
|  | 					target.focus(); | ||||||
|  | 				} else { | ||||||
|  | 					focus(target, fn); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
| 	</script> | 	</script> | ||||||
| </mk-timeline-post> | </mk-timeline-post> | ||||||
|   | |||||||
| @@ -3,7 +3,9 @@ | |||||||
| 		<mk-timeline-post post={ post }></mk-timeline-post> | 		<mk-timeline-post post={ post }></mk-timeline-post> | ||||||
| 		<p class="date" if={ i != posts.length - 1 && post._date != posts[i + 1]._date }><span><i class="fa fa-angle-up"></i>{ post._datetext }</span><span><i class="fa fa-angle-down"></i>{ posts[i + 1]._datetext }</span></p> | 		<p class="date" if={ i != posts.length - 1 && post._date != posts[i + 1]._date }><span><i class="fa fa-angle-up"></i>{ post._datetext }</span><span><i class="fa fa-angle-down"></i>{ posts[i + 1]._datetext }</span></p> | ||||||
| 	</virtual> | 	</virtual> | ||||||
| 	<footer data-yield="footer"><yield from="footer"/></footer> | 	<footer data-yield="footer"> | ||||||
|  | 		<yield from="footer"/> | ||||||
|  | 	</footer> | ||||||
| 	<style> | 	<style> | ||||||
| 		:scope | 		:scope | ||||||
| 			display block | 			display block | ||||||
| @@ -44,36 +46,47 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@posts = [] | 		this.posts = []; | ||||||
|  |  | ||||||
| 		@set-posts = (posts) ~> | 		this.on('update', () => { | ||||||
| 			@posts = posts | 			this.posts.forEach(post => { | ||||||
| 			@update! | 				const date = new Date(post.created_at).getDate(); | ||||||
|  | 				const month = new Date(post.created_at).getMonth() + 1; | ||||||
|  | 				post._date = date; | ||||||
|  | 				post._datetext = `${month}月 ${date}日`; | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		@prepend-posts = (posts) ~> | 		this.setPosts = posts => { | ||||||
| 			posts.for-each (post) ~> | 			this.update({ | ||||||
| 				@posts.push post | 				posts: posts | ||||||
| 				@update! | 			}); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@add-post = (post) ~> | 		this.prependPosts = posts => { | ||||||
| 			@posts.unshift post | 			posts.forEach(post => { | ||||||
| 			@update! | 				this.posts.push(post); | ||||||
|  | 				this.update(); | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		@clear = ~> | 		this.addPost = post => { | ||||||
| 			@posts = [] | 			this.posts.unshift(post); | ||||||
| 			@update! | 			this.update(); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@focus = ~> | 		this.tail = () => { | ||||||
| 			@root.children.0.focus! | 			return this.posts[this.posts.length - 1]; | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@on \update ~> | 		this.clear = () => { | ||||||
| 			@posts.for-each (post) ~> | 			this.posts = []; | ||||||
| 				date = (new Date post.created_at).get-date! | 			this.update(); | ||||||
| 				month = (new Date post.created_at).get-month! + 1 | 		}; | ||||||
| 				post._date = date |  | ||||||
| 				post._datetext = month + '月 ' + date + '日' | 		this.focus = () => { | ||||||
|  | 			this.root.children[0].focus(); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@tail = ~> |  | ||||||
| 			@posts[@posts.length - 1] |  | ||||||
| 	</script> | 	</script> | ||||||
| </mk-timeline> | </mk-timeline> | ||||||
|   | |||||||
| @@ -159,54 +159,54 @@ | |||||||
|  |  | ||||||
| 	</style> | 	</style> | ||||||
| 	<script> | 	<script> | ||||||
| 		@mixin \i | 		const contains = require('../../common/scripts/contains'); | ||||||
| 		@mixin \signout |  | ||||||
|  |  | ||||||
| 		@is-open = false | 		this.mixin('i'); | ||||||
|  | 		this.mixin('signout'); | ||||||
|  |  | ||||||
| 		@on \before-unmount ~> | 		this.isOpen = false; | ||||||
| 			@close! |  | ||||||
|  |  | ||||||
| 		@toggle = ~> | 		this.on('before-unmount', () => { | ||||||
| 			if @is-open | 			this.close(); | ||||||
| 				@close! | 		}); | ||||||
| 			else |  | ||||||
| 				@open! |  | ||||||
|  |  | ||||||
| 		@open = ~> | 		this.toggle = () => { | ||||||
| 			@is-open = true | 			this.isOpen ? this.close() : this.open(); | ||||||
| 			@update! | 		}; | ||||||
| 			all = document.query-selector-all 'body *' |  | ||||||
| 			Array.prototype.for-each.call all, (el) ~> |  | ||||||
| 				el.add-event-listener \mousedown @mousedown |  | ||||||
|  |  | ||||||
| 		@close = ~> | 		this.open = () => { | ||||||
| 			@is-open = false | 			this.update({ | ||||||
| 			@update! | 				isOpen: true | ||||||
| 			all = document.query-selector-all 'body *' | 			}); | ||||||
| 			Array.prototype.for-each.call all, (el) ~> | 			document.querySelectorAll('body *').forEach(el => { | ||||||
| 				el.remove-event-listener \mousedown @mousedown | 				el.addEventListener('mousedown', this.mousedown); | ||||||
|  | 			}); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@mousedown = (e) ~> | 		this.close = () => { | ||||||
| 			e.prevent-default! | 			this.update({ | ||||||
| 			if (!contains @root, e.target) and (@root != e.target) | 				isOpen: false | ||||||
| 				@close! | 			}); | ||||||
| 			return false | 			document.querySelectorAll('body *').forEach(el => { | ||||||
|  | 				el.removeEventListener('mousedown', this.mousedown); | ||||||
|  | 			}); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@drive = ~> | 		this.mousedown = e => { | ||||||
| 			@close! | 			e.preventDefault(); | ||||||
| 			riot.mount document.body.append-child document.create-element \mk-drive-browser-window | 			if (!contains(this.root, e.target) && this.root != e.target) this.close(); | ||||||
|  | 			return false; | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		@settings = ~> | 		this.drive = () => { | ||||||
| 			@close! | 			this.close(); | ||||||
| 			riot.mount document.body.append-child document.create-element \mk-settings-window | 			riot.mount(document.body.appendChild(document.createElement('mk-drive-browser-window'))); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		this.settings = () => { | ||||||
|  | 			this.close(); | ||||||
|  | 			riot.mount(document.body.appendChild(document.createElement('mk-settings-window'))); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
| 		function contains(parent, child) |  | ||||||
| 			node = child.parent-node |  | ||||||
| 			while node? |  | ||||||
| 				if node == parent |  | ||||||
| 					return true |  | ||||||
| 				node = node.parent-node |  | ||||||
| 			return false |  | ||||||
| 	</script> | 	</script> | ||||||
| </mk-ui-header-account> | </mk-ui-header-account> | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user
	 syuilo⭐️
					syuilo⭐️