Initial commit 🍀
This commit is contained in:
19
src/web/app/mobile/mixins.ls
Normal file
19
src/web/app/mobile/mixins.ls
Normal file
@@ -0,0 +1,19 @@
|
||||
riot = require \riot
|
||||
|
||||
module.exports = (me) ~>
|
||||
if me?
|
||||
(require './scripts/stream.ls') me
|
||||
|
||||
require './scripts/ui.ls'
|
||||
|
||||
riot.mixin \open-post-form do
|
||||
open-post-form: (opts) ->
|
||||
app = document.get-element-by-id \app
|
||||
app.style.display = \none
|
||||
form = document.body.append-child document.create-element \mk-post-form
|
||||
form = riot.mount form, opts .0
|
||||
form.on \cancel recover
|
||||
form.on \post recover
|
||||
|
||||
function recover
|
||||
app.style.display = \block
|
110
src/web/app/mobile/router.ls
Normal file
110
src/web/app/mobile/router.ls
Normal file
@@ -0,0 +1,110 @@
|
||||
# Router
|
||||
#================================
|
||||
|
||||
riot = require \riot
|
||||
route = require \page
|
||||
page = null
|
||||
|
||||
module.exports = (me) ~>
|
||||
|
||||
# Routing
|
||||
#--------------------------------
|
||||
|
||||
route \/ index
|
||||
route \/i/notifications notifications
|
||||
route \/i/drive drive
|
||||
route \/i/drive/folder/:folder drive
|
||||
route \/i/drive/file/:file drive
|
||||
route \/post/new new-post
|
||||
route \/post::post post
|
||||
route \/search::query search
|
||||
route \/:user user.bind null \posts
|
||||
route \/:user/graphs user.bind null \graphs
|
||||
route \/:user/followers user-followers
|
||||
route \/:user/following user-following
|
||||
route \/:user/:post post
|
||||
route \* not-found
|
||||
|
||||
# Handlers
|
||||
#--------------------------------
|
||||
|
||||
# /
|
||||
function index
|
||||
if me? then home! else entrance!
|
||||
|
||||
# ホーム
|
||||
function home
|
||||
mount document.create-element \mk-home-page
|
||||
|
||||
# 玄関
|
||||
function entrance
|
||||
mount document.create-element \mk-entrance
|
||||
|
||||
# 通知
|
||||
function notifications
|
||||
mount document.create-element \mk-notifications-page
|
||||
|
||||
# 新規投稿
|
||||
function new-post
|
||||
mount document.create-element \mk-new-post-page
|
||||
|
||||
# 検索
|
||||
function search ctx
|
||||
document.create-element \mk-search-page
|
||||
..set-attribute \query ctx.params.query
|
||||
.. |> mount
|
||||
|
||||
# ユーザー
|
||||
function user page, ctx
|
||||
document.create-element \mk-user-page
|
||||
..set-attribute \user ctx.params.user
|
||||
..set-attribute \page page
|
||||
.. |> mount
|
||||
|
||||
# フォロー一覧
|
||||
function user-following ctx
|
||||
document.create-element \mk-user-following-page
|
||||
..set-attribute \user ctx.params.user
|
||||
.. |> mount
|
||||
|
||||
# フォロワー一覧
|
||||
function user-followers ctx
|
||||
document.create-element \mk-user-followers-page
|
||||
..set-attribute \user ctx.params.user
|
||||
.. |> mount
|
||||
|
||||
# 投稿詳細ページ
|
||||
function post ctx
|
||||
document.create-element \mk-post-page
|
||||
..set-attribute \post ctx.params.post
|
||||
.. |> mount
|
||||
|
||||
# ドライブ
|
||||
function drive ctx
|
||||
p = document.create-element \mk-drive-page
|
||||
if ctx.params.folder then p.set-attribute \folder ctx.params.folder
|
||||
if ctx.params.file then p.set-attribute \file ctx.params.file
|
||||
mount p
|
||||
|
||||
# not found
|
||||
function not-found
|
||||
mount document.create-element \mk-not-found
|
||||
|
||||
# Register mixin
|
||||
#--------------------------------
|
||||
|
||||
riot.mixin \page do
|
||||
page: route
|
||||
|
||||
# Exec
|
||||
#--------------------------------
|
||||
|
||||
route!
|
||||
|
||||
# Mount
|
||||
#================================
|
||||
|
||||
function mount content
|
||||
if page? then page.unmount!
|
||||
body = document.get-element-by-id \app
|
||||
page := riot.mount body.append-child content .0
|
20
src/web/app/mobile/script.js
Normal file
20
src/web/app/mobile/script.js
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Mobile Client
|
||||
*/
|
||||
|
||||
require('./tags.ls');
|
||||
require('./scripts/sp-slidemenu.js');
|
||||
const boot = require('../boot.ls');
|
||||
const mixins = require('./mixins.ls');
|
||||
const route = require('./router.ls');
|
||||
|
||||
/**
|
||||
* Boot
|
||||
*/
|
||||
boot(me => {
|
||||
// Register mixins
|
||||
mixins(me);
|
||||
|
||||
// Start routing
|
||||
route(me);
|
||||
});
|
839
src/web/app/mobile/scripts/sp-slidemenu.js
Normal file
839
src/web/app/mobile/scripts/sp-slidemenu.js
Normal file
@@ -0,0 +1,839 @@
|
||||
/**
|
||||
* sp-slidemenu.js
|
||||
*
|
||||
* @version 0.1.0
|
||||
* @url https://github.com/be-hase/sp-slidemenu
|
||||
*
|
||||
* Copyright 2013 be-hase.com, Inc.
|
||||
* Licensed under the MIT License:
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
*/
|
||||
|
||||
/**
|
||||
* CUSTOMIZED BY SYUILO
|
||||
*/
|
||||
|
||||
; (function(window, document, undefined) {
|
||||
"use strict";
|
||||
var div, PREFIX, support, gestureStart, EVENTS, ANIME_SPEED, SLIDE_STATUS, SCROLL_STATUS, THRESHOLD, EVENT_MOE_TIME, rclass, ITEM_CLICK_CLASS_NAME;
|
||||
div = document.createElement('div');
|
||||
PREFIX = ['webkit', 'moz', 'o', 'ms'];
|
||||
support = SpSlidemenu.support = {};
|
||||
support.transform3d = hasProp([
|
||||
'perspectiveProperty',
|
||||
'WebkitPerspective',
|
||||
'MozPerspective',
|
||||
'OPerspective',
|
||||
'msPerspective'
|
||||
]);
|
||||
support.transform = hasProp([
|
||||
'transformProperty',
|
||||
'WebkitTransform',
|
||||
'MozTransform',
|
||||
'OTransform',
|
||||
'msTransform'
|
||||
]);
|
||||
support.transition = hasProp([
|
||||
'transitionProperty',
|
||||
'WebkitTransitionProperty',
|
||||
'MozTransitionProperty',
|
||||
'OTransitionProperty',
|
||||
'msTransitionProperty'
|
||||
]);
|
||||
support.addEventListener = 'addEventListener' in window;
|
||||
support.msPointer = window.navigator.msPointerEnabled;
|
||||
support.cssAnimation = (support.transform3d || support.transform) && support.transition;
|
||||
support.touch = 'ontouchend' in window;
|
||||
EVENTS = {
|
||||
start: {
|
||||
touch: 'touchstart',
|
||||
mouse: 'mousedown'
|
||||
},
|
||||
move: {
|
||||
touch: 'touchmove',
|
||||
mouse: 'mousemove'
|
||||
},
|
||||
end: {
|
||||
touch: 'touchend',
|
||||
mouse: 'mouseup'
|
||||
}
|
||||
};
|
||||
gestureStart = false;
|
||||
if (support.addEventListener) {
|
||||
document.addEventListener('gesturestart', function() {
|
||||
gestureStart = true;
|
||||
});
|
||||
document.addEventListener('gestureend', function() {
|
||||
gestureStart = false;
|
||||
});
|
||||
}
|
||||
ANIME_SPEED = {
|
||||
slider: 200,
|
||||
scrollOverBack: 400
|
||||
};
|
||||
SLIDE_STATUS = {
|
||||
close: 0,
|
||||
open: 1,
|
||||
progress: 2
|
||||
};
|
||||
THRESHOLD = 10;
|
||||
EVENT_MOE_TIME = 50;
|
||||
rclass = /[\t\r\n\f]/g;
|
||||
ITEM_CLICK_CLASS_NAME = 'menu-item';
|
||||
/*
|
||||
[MEMO]
|
||||
SpSlidemenu properties which is not function is ...
|
||||
-- element --
|
||||
element: main
|
||||
element: slidemenu
|
||||
element: button
|
||||
element: slidemenuBody
|
||||
element: slidemenuContent
|
||||
element: slidemenuHeader
|
||||
-- options --
|
||||
bool: disableCssAnimation
|
||||
bool: disabled3d
|
||||
-- animation --
|
||||
bool: useCssAnimation
|
||||
bool: use3d
|
||||
-- slide --
|
||||
int: slideWidth
|
||||
string: htmlOverflowX
|
||||
string: bodyOverflowX
|
||||
int: buttonStartPageX
|
||||
int: buttonStartPageY
|
||||
-- scroll --
|
||||
bool: scrollTouchStarted
|
||||
bool: scrollMoveReady
|
||||
int: scrollStartPageX
|
||||
int: scrollStartPageY
|
||||
int: scrollBasePageY
|
||||
int: scrollTimeForVelocity
|
||||
int: scrollCurrentY
|
||||
int: scrollMoveEventCnt
|
||||
int: scrollAnimationTimer
|
||||
int: scrollOverTimer
|
||||
int: scrollMaxY
|
||||
*/
|
||||
function SpSlidemenu(main, slidemenu, button, options) {
|
||||
if (this instanceof SpSlidemenu) {
|
||||
return this.init(main, slidemenu, button, options);
|
||||
} else {
|
||||
return new SpSlidemenu(main, slidemenu, button, options);
|
||||
}
|
||||
}
|
||||
SpSlidemenu.prototype.init = function(main, slidemenu, button, options) {
|
||||
var _this = this;
|
||||
// find and set element.
|
||||
_this.setElement(main, slidemenu, button);
|
||||
if (!_this.main || !_this.slidemenu || !_this.button || !_this.slidemenuBody || !_this.slidemenuContent) {
|
||||
throw new Error('Element not found. Please set correctly.');
|
||||
}
|
||||
// options
|
||||
options = options || {};
|
||||
_this.disableCssAnimation = (options.disableCssAnimation === undefined) ? false : options.disableCssAnimation;
|
||||
_this.disable3d = (options.disable3d === undefined) ? false : options.disable3d;
|
||||
_this.direction = 'left';
|
||||
if (options.direction === 'right') {
|
||||
_this.direction = 'right';
|
||||
}
|
||||
// animation
|
||||
_this.useCssAnimation = support.cssAnimation;
|
||||
if (_this.disableCssAnimation === true) {
|
||||
_this.useCssAnimation = false;
|
||||
}
|
||||
_this.use3d = support.transform3d;
|
||||
if (_this.disable3d === true) {
|
||||
_this.use3d = false;
|
||||
}
|
||||
// slide
|
||||
_this.slideWidth = (getDimentions(_this.slidemenu)).width;
|
||||
_this.main.SpSlidemenuStatus = SLIDE_STATUS.close;
|
||||
_this.htmlOverflowX = '';
|
||||
_this.bodyOverflowX = '';
|
||||
// scroll
|
||||
_this.scrollCurrentY = 0;
|
||||
_this.scrollAnimationTimer = false;
|
||||
_this.scrollOverTimer = false;
|
||||
// set default style.
|
||||
_this.setDefaultStyle();
|
||||
// bind some method for callback.
|
||||
_this.bindMethods();
|
||||
// add event
|
||||
addTouchEvent('start', _this.button, _this.buttonTouchStart, false);
|
||||
addTouchEvent('move', _this.button, blockEvent, false);
|
||||
addTouchEvent('end', _this.button, _this.buttonTouchEnd, false);
|
||||
addTouchEvent('start', _this.slidemenuContent, _this.scrollTouchStart, false);
|
||||
addTouchEvent('move', _this.slidemenuContent, _this.scrollTouchMove, false);
|
||||
addTouchEvent('end', _this.slidemenuContent, _this.scrollTouchEnd, false);
|
||||
_this.slidemenuContent.addEventListener('click', _this.itemClick, false);
|
||||
// window size change
|
||||
window.addEventListener('resize', debounce(_this.setHeight, 100), false);
|
||||
return _this;
|
||||
};
|
||||
SpSlidemenu.prototype.bindMethods = function() {
|
||||
var _this, funcs;
|
||||
_this = this;
|
||||
funcs = [
|
||||
'setHeight',
|
||||
'slideOpen', 'slideOpenEnd', 'slideClose', 'slideCloseEnd',
|
||||
'buttonTouchStart', 'buttonTouchEnd', 'mainTouchStart',
|
||||
'scrollTouchStart', 'scrollTouchMove', 'scrollTouchEnd', 'scrollInertiaMove', 'scrollOverBack', 'scrollOver',
|
||||
'itemClick'
|
||||
];
|
||||
funcs.forEach(function(func) {
|
||||
_this[func] = bind(_this[func], _this);
|
||||
});
|
||||
};
|
||||
SpSlidemenu.prototype.setElement = function(main, slidemenu, button) {
|
||||
var _this = this;
|
||||
_this.main = main;
|
||||
if (typeof main === 'string') {
|
||||
_this.main = document.querySelector(main);
|
||||
}
|
||||
_this.slidemenu = slidemenu;
|
||||
if (typeof slidemenu === 'string') {
|
||||
_this.slidemenu = document.querySelector(slidemenu);
|
||||
}
|
||||
_this.button = button;
|
||||
if (typeof button === 'string') {
|
||||
_this.button = document.querySelector(button);
|
||||
}
|
||||
if (!_this.slidemenu) {
|
||||
return;
|
||||
}
|
||||
_this.slidemenuBody = _this.slidemenu.querySelector('.body');
|
||||
_this.slidemenuContent = _this.slidemenu.querySelector('.content');
|
||||
_this.slidemenuHeader = _this.slidemenu.querySelector('.header');
|
||||
};
|
||||
SpSlidemenu.prototype.setDefaultStyle = function() {
|
||||
var _this = this;
|
||||
if (support.msPointer) {
|
||||
_this.slidemenuContent.style.msTouchAction = 'none';
|
||||
}
|
||||
_this.setHeight();
|
||||
if (_this.useCssAnimation) {
|
||||
setStyles(_this.main, {
|
||||
transitionProperty: getCSSName('transform'),
|
||||
transitionTimingFunction: 'ease-in-out',
|
||||
transitionDuration: ANIME_SPEED.slider + 'ms',
|
||||
transitionDelay: '0ms',
|
||||
transform: _this.getTranslateX(0)
|
||||
});
|
||||
setStyles(_this.slidemenu, {
|
||||
transitionProperty: 'visibility',
|
||||
transitionTimingFunction: 'linear',
|
||||
transitionDuration: '0ms',
|
||||
transitionDelay: ANIME_SPEED.slider + 'ms'
|
||||
});
|
||||
setStyles(_this.slidemenuContent, {
|
||||
transitionProperty: getCSSName('transform'),
|
||||
transitionTimingFunction: 'ease-in-out',
|
||||
transitionDuration: '0ms',
|
||||
transitionDelay: '0ms',
|
||||
transform: _this.getTranslateY(0)
|
||||
});
|
||||
} else {
|
||||
setStyles(_this.main, {
|
||||
position: 'relative',
|
||||
left: '0px'
|
||||
});
|
||||
setStyles(_this.slidemenuContent, {
|
||||
top: '0px'
|
||||
});
|
||||
}
|
||||
};
|
||||
SpSlidemenu.prototype.setHeight = function(event) {
|
||||
var _this, browserHeight;
|
||||
_this = this;
|
||||
browserHeight = getBrowserHeight();
|
||||
setStyles(_this.main, {
|
||||
minHeight: browserHeight + 'px'
|
||||
});
|
||||
setStyles(_this.slidemenu, {
|
||||
height: browserHeight + 'px'
|
||||
});
|
||||
};
|
||||
SpSlidemenu.prototype.buttonTouchStart = function(event) {
|
||||
var _this = this;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
switch (_this.main.SpSlidemenuStatus) {
|
||||
case SLIDE_STATUS.progress:
|
||||
break;
|
||||
case SLIDE_STATUS.open:
|
||||
case SLIDE_STATUS.close:
|
||||
_this.buttonStartPageX = getPage(event, 'pageX');
|
||||
_this.buttonStartPageY = getPage(event, 'pageY');
|
||||
break;
|
||||
}
|
||||
};
|
||||
SpSlidemenu.prototype.buttonTouchEnd = function(event) {
|
||||
var _this = this;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (_this.shouldTrigerNext(event)) {
|
||||
switch (_this.main.SpSlidemenuStatus) {
|
||||
case SLIDE_STATUS.progress:
|
||||
break;
|
||||
case SLIDE_STATUS.open:
|
||||
_this.slideClose(event);
|
||||
break;
|
||||
case SLIDE_STATUS.close:
|
||||
_this.slideOpen(event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
SpSlidemenu.prototype.mainTouchStart = function(event) {
|
||||
var _this = this;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
_this.slideClose(event);
|
||||
};
|
||||
SpSlidemenu.prototype.shouldTrigerNext = function(event) {
|
||||
var _this = this,
|
||||
buttonEndPageX = getPage(event, 'pageX'),
|
||||
buttonEndPageY = getPage(event, 'pageY'),
|
||||
deltaX = Math.abs(buttonEndPageX - _this.buttonStartPageX),
|
||||
deltaY = Math.abs(buttonEndPageY - _this.buttonStartPageY);
|
||||
return deltaX < 20 && deltaY < 20;
|
||||
};
|
||||
SpSlidemenu.prototype.slideOpen = function(event) {
|
||||
var _this = this, toX;
|
||||
|
||||
/// Misskey Original
|
||||
document.body.setAttribute('data-nav-open', 'true');
|
||||
|
||||
if (_this.direction === 'left') {
|
||||
toX = _this.slideWidth;
|
||||
} else {
|
||||
toX = -_this.slideWidth;
|
||||
}
|
||||
_this.main.SpSlidemenuStatus = SLIDE_STATUS.progress;
|
||||
//set event
|
||||
addTouchEvent('move', document, blockEvent, false);
|
||||
// change style
|
||||
_this.htmlOverflowX = document.documentElement.style['overflowX'];
|
||||
_this.bodyOverflowX = document.body.style['overflowX'];
|
||||
document.documentElement.style['overflowX'] = document.body.style['overflowX'] = 'hidden';
|
||||
if (_this.useCssAnimation) {
|
||||
setStyles(_this.main, {
|
||||
transform: _this.getTranslateX(toX)
|
||||
});
|
||||
setStyles(_this.slidemenu, {
|
||||
transitionProperty: 'z-index',
|
||||
visibility: 'visible',
|
||||
zIndex: '1'
|
||||
});
|
||||
} else {
|
||||
animate(_this.main, _this.direction, toX, ANIME_SPEED.slider);
|
||||
setStyles(_this.slidemenu, {
|
||||
visibility: 'visible'
|
||||
});
|
||||
}
|
||||
// set callback
|
||||
setTimeout(_this.slideOpenEnd, ANIME_SPEED.slider + EVENT_MOE_TIME);
|
||||
};
|
||||
SpSlidemenu.prototype.slideOpenEnd = function() {
|
||||
var _this = this;
|
||||
_this.main.SpSlidemenuStatus = SLIDE_STATUS.open;
|
||||
// change style
|
||||
if (_this.useCssAnimation) {
|
||||
} else {
|
||||
setStyles(_this.slidemenu, {
|
||||
zIndex: '1'
|
||||
});
|
||||
}
|
||||
// add event
|
||||
addTouchEvent('start', _this.main, _this.mainTouchStart, false);
|
||||
};
|
||||
SpSlidemenu.prototype.slideClose = function(event) {
|
||||
var _this = this;
|
||||
_this.main.SpSlidemenuStatus = SLIDE_STATUS.progress;
|
||||
|
||||
/// Misskey Original
|
||||
document.body.setAttribute('data-nav-open', 'false');
|
||||
|
||||
//event
|
||||
removeTouchEvent('start', _this.main, _this.mainTouchStart, false);
|
||||
// change style
|
||||
if (_this.useCssAnimation) {
|
||||
setStyles(_this.main, {
|
||||
transform: _this.getTranslateX(0)
|
||||
});
|
||||
setStyles(_this.slidemenu, {
|
||||
transitionProperty: 'visibility',
|
||||
visibility: 'hidden',
|
||||
zIndex: '-1'
|
||||
});
|
||||
} else {
|
||||
animate(_this.main, _this.direction, 0, ANIME_SPEED.slider);
|
||||
setStyles(_this.slidemenu, {
|
||||
zIndex: '-1'
|
||||
});
|
||||
}
|
||||
// set callback
|
||||
setTimeout(_this.slideCloseEnd, ANIME_SPEED.slider + EVENT_MOE_TIME);
|
||||
};
|
||||
SpSlidemenu.prototype.slideCloseEnd = function() {
|
||||
var _this = this;
|
||||
_this.main.SpSlidemenuStatus = SLIDE_STATUS.close;
|
||||
// change style
|
||||
document.documentElement.style['overflowX'] = _this.htmlOverflowX;
|
||||
document.body.style['overflowX'] = _this.bodyOverflowX;
|
||||
if (_this.useCssAnimation) {
|
||||
} else {
|
||||
setStyles(_this.slidemenu, {
|
||||
visibility: 'hidden'
|
||||
});
|
||||
}
|
||||
// set event
|
||||
removeTouchEvent('move', document, blockEvent, false);
|
||||
};
|
||||
SpSlidemenu.prototype.scrollTouchStart = function(event) {
|
||||
var _this = this;
|
||||
if (gestureStart) {
|
||||
return;
|
||||
}
|
||||
if (_this.scrollOverTimer !== false) {
|
||||
clearTimeout(_this.scrollOverTimer);
|
||||
}
|
||||
_this.scrollCurrentY = _this.getScrollCurrentY();
|
||||
if (_this.useCssAnimation) {
|
||||
setStyles(_this.slidemenuContent, {
|
||||
transitionTimingFunction: 'ease-in-out',
|
||||
transitionDuration: '0ms',
|
||||
transform: _this.getTranslateY(_this.scrollCurrentY)
|
||||
});
|
||||
} else {
|
||||
_this.stopScrollAnimate();
|
||||
setStyles(_this.slidemenuContent, {
|
||||
top: _this.scrollCurrentY + 'px'
|
||||
});
|
||||
}
|
||||
_this.scrollOverTimer = false;
|
||||
_this.scrollAnimationTimer = false;
|
||||
_this.scrollTouchStarted = true;
|
||||
_this.scrollMoveReady = false;
|
||||
_this.scrollMoveEventCnt = 0;
|
||||
_this.scrollMaxY = _this.calcMaxY();
|
||||
_this.scrollStartPageX = getPage(event, 'pageX');
|
||||
_this.scrollStartPageY = getPage(event, 'pageY');
|
||||
_this.scrollBasePageY = _this.scrollStartPageY;
|
||||
_this.scrollTimeForVelocity = event.timeStamp;
|
||||
_this.scrollPageYForVelocity = _this.scrollStartPageY;
|
||||
_this.slidemenuContent.removeEventListener('click', blockEvent, true);
|
||||
};
|
||||
SpSlidemenu.prototype.scrollTouchMove = function(event) {
|
||||
var _this, pageX, pageY, distY, newY, deltaX, deltaY;
|
||||
_this = this;
|
||||
if (!_this.scrollTouchStarted || gestureStart) {
|
||||
return;
|
||||
}
|
||||
pageX = getPage(event, 'pageX');
|
||||
pageY = getPage(event, 'pageY');
|
||||
if (_this.scrollMoveReady) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
distY = pageY - _this.scrollBasePageY;
|
||||
newY = _this.scrollCurrentY + distY;
|
||||
if (newY > 0 || newY < _this.scrollMaxY) {
|
||||
newY = Math.round(_this.scrollCurrentY + distY / 3);
|
||||
}
|
||||
_this.scrollSetY(newY);
|
||||
if (_this.scrollMoveEventCnt % THRESHOLD === 0) {
|
||||
_this.scrollPageYForVelocity = pageY;
|
||||
_this.scrollTimeForVelocity = event.timeStamp;
|
||||
}
|
||||
_this.scrollMoveEventCnt++;
|
||||
} else {
|
||||
deltaX = Math.abs(pageX - _this.scrollStartPageX);
|
||||
deltaY = Math.abs(pageY - _this.scrollStartPageY);
|
||||
if (deltaX > 5 || deltaY > 5) {
|
||||
_this.scrollMoveReady = true;
|
||||
_this.slidemenuContent.addEventListener('click', blockEvent, true);
|
||||
}
|
||||
}
|
||||
_this.scrollBasePageY = pageY;
|
||||
};
|
||||
SpSlidemenu.prototype.scrollTouchEnd = function(event) {
|
||||
var _this, speed, deltaY, deltaTime;
|
||||
_this = this;
|
||||
if (!_this.scrollTouchStarted) {
|
||||
return;
|
||||
}
|
||||
_this.scrollTouchStarted = false;
|
||||
_this.scrollMaxY = _this.calcMaxY();
|
||||
if (_this.scrollCurrentY > 0 || _this.scrollCurrentY < _this.scrollMaxY) {
|
||||
_this.scrollOverBack();
|
||||
return;
|
||||
}
|
||||
deltaY = getPage(event, 'pageY') - _this.scrollPageYForVelocity;
|
||||
deltaTime = event.timeStamp - _this.scrollTimeForVelocity;
|
||||
speed = deltaY / deltaTime;
|
||||
if (Math.abs(speed) >= 0.01) {
|
||||
_this.scrollInertia(speed);
|
||||
}
|
||||
};
|
||||
SpSlidemenu.prototype.scrollInertia = function(speed) {
|
||||
var _this, directionToTop, maxTo, distanceMaxTo, stopTime, canMove, to, duration, speedAtboundary, nextTo;
|
||||
_this = this;
|
||||
if (speed > 0) {
|
||||
directionToTop = true;
|
||||
maxTo = 0;
|
||||
} else {
|
||||
directionToTop = false;
|
||||
maxTo = _this.scrollMaxY;
|
||||
}
|
||||
distanceMaxTo = Math.abs(_this.scrollCurrentY - maxTo);
|
||||
speed = Math.abs(750 * speed);
|
||||
if (speed > 1000) {
|
||||
speed = 1000;
|
||||
}
|
||||
stopTime = speed / 500;
|
||||
canMove = (speed * stopTime) - ((500 * Math.pow(stopTime, 2)) / 2);
|
||||
if (canMove <= distanceMaxTo) {
|
||||
if (directionToTop) {
|
||||
to = _this.scrollCurrentY + canMove;
|
||||
} else {
|
||||
to = _this.scrollCurrentY - canMove;
|
||||
}
|
||||
duration = stopTime * 1000;
|
||||
_this.scrollInertiaMove(to, duration, false);
|
||||
} else {
|
||||
to = maxTo;
|
||||
speedAtboundary = Math.sqrt((2 * 500 * distanceMaxTo) + Math.pow(speed, 2));
|
||||
duration = (speedAtboundary - speed) / 500 * 1000;
|
||||
_this.scrollInertiaMove(to, duration, true, speedAtboundary, directionToTop);
|
||||
}
|
||||
};
|
||||
SpSlidemenu.prototype.scrollInertiaMove = function(to, duration, isOver, speed, directionToTop) {
|
||||
var _this = this, stopTime, canMove;
|
||||
_this.scrollCurrentY = to;
|
||||
if (_this.useCssAnimation) {
|
||||
setStyles(_this.slidemenuContent, {
|
||||
transitionTimingFunction: 'cubic-bezier(0.33, 0.66, 0.66, 1)',
|
||||
transitionDuration: duration + 'ms',
|
||||
transform: _this.getTranslateY(to)
|
||||
});
|
||||
} else {
|
||||
_this.scrollAnimate(to, duration);
|
||||
}
|
||||
if (!isOver) {
|
||||
return;
|
||||
}
|
||||
stopTime = speed / 7500;
|
||||
canMove = (speed * stopTime) - ((7500 * Math.pow(stopTime, 2)) / 2);
|
||||
if (directionToTop) {
|
||||
to = _this.scrollCurrentY + canMove;
|
||||
} else {
|
||||
to = _this.scrollCurrentY - canMove;
|
||||
}
|
||||
duration = stopTime * 1000;
|
||||
_this.scrollOver(to, duration);
|
||||
};
|
||||
SpSlidemenu.prototype.scrollOver = function(to, duration) {
|
||||
var _this;
|
||||
_this = this;
|
||||
_this.scrollCurrentY = to;
|
||||
if (_this.useCssAnimation) {
|
||||
setStyles(_this.slidemenuContent, {
|
||||
transitionTimingFunction: 'cubic-bezier(0.33, 0.66, 0.66, 1)',
|
||||
transitionDuration: duration + 'ms',
|
||||
transform: _this.getTranslateY(to)
|
||||
});
|
||||
} else {
|
||||
_this.scrollAnimate(to, duration);
|
||||
}
|
||||
_this.scrollOverTimer = setTimeout(_this.scrollOverBack, duration);
|
||||
};
|
||||
SpSlidemenu.prototype.scrollOverBack = function() {
|
||||
var _this, to;
|
||||
_this = this;
|
||||
if (_this.scrollCurrentY >= 0) {
|
||||
to = 0;
|
||||
} else {
|
||||
to = _this.scrollMaxY;
|
||||
}
|
||||
_this.scrollCurrentY = to;
|
||||
if (_this.useCssAnimation) {
|
||||
setStyles(_this.slidemenuContent, {
|
||||
transitionTimingFunction: 'ease-out',
|
||||
transitionDuration: ANIME_SPEED.scrollOverBack + 'ms',
|
||||
transform: _this.getTranslateY(to)
|
||||
});
|
||||
} else {
|
||||
_this.scrollAnimate(to, ANIME_SPEED.scrollOverBack);
|
||||
}
|
||||
};
|
||||
SpSlidemenu.prototype.scrollSetY = function(y) {
|
||||
var _this = this;
|
||||
_this.scrollCurrentY = y;
|
||||
if (_this.useCssAnimation) {
|
||||
setStyles(_this.slidemenuContent, {
|
||||
transitionTimingFunction: 'ease-in-out',
|
||||
transitionDuration: '0ms',
|
||||
transform: _this.getTranslateY(y)
|
||||
});
|
||||
} else {
|
||||
_this.slidemenuContent.style.top = y + 'px';
|
||||
}
|
||||
};
|
||||
SpSlidemenu.prototype.scrollAnimate = function(to, transitionDuration) {
|
||||
var _this = this;
|
||||
_this.stopScrollAnimate();
|
||||
_this.scrollAnimationTimer = animate(_this.slidemenuContent, 'top', to, transitionDuration);
|
||||
};
|
||||
SpSlidemenu.prototype.stopScrollAnimate = function() {
|
||||
var _this = this;
|
||||
if (_this.scrollAnimationTimer !== false) {
|
||||
clearInterval(_this.scrollAnimationTimer);
|
||||
}
|
||||
};
|
||||
SpSlidemenu.prototype.itemClick = function(event) {
|
||||
var elem = event.target || event.srcElement;
|
||||
if (hasClass(elem, ITEM_CLICK_CLASS_NAME)) {
|
||||
this.slideClose();
|
||||
}
|
||||
};
|
||||
SpSlidemenu.prototype.calcMaxY = function(x) {
|
||||
var _this, contentHeight, bodyHeight, headerHeight;
|
||||
_this = this;
|
||||
contentHeight = _this.slidemenuContent.offsetHeight;
|
||||
bodyHeight = _this.slidemenuBody.offsetHeight;
|
||||
headerHeight = 0;
|
||||
if (_this.slidemenuHeader) {
|
||||
headerHeight = _this.slidemenuHeader.offsetHeight;
|
||||
}
|
||||
if (contentHeight > bodyHeight) {
|
||||
return -(contentHeight - bodyHeight + headerHeight);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
SpSlidemenu.prototype.getScrollCurrentY = function() {
|
||||
var ret = 0;
|
||||
if (this.useCssAnimation) {
|
||||
getStyle(window.getComputedStyle(this.slidemenuContent, ''), 'transform').split(',').forEach(function(value) {
|
||||
var number = parseInt(value, 10);
|
||||
if (!isNaN(number) && number !== 0 && number !== 1) {
|
||||
ret = number;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
var number = parseInt(getStyle(window.getComputedStyle(this.slidemenuContent, ''), 'top'), 10);
|
||||
if (!isNaN(number) && number !== 0 && number !== 1) {
|
||||
ret = number;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
SpSlidemenu.prototype.getTranslateX = function(x) {
|
||||
var _this = this;
|
||||
return _this.use3d ? 'translate3d(' + x + 'px, 0px, 0px)' : 'translate(' + x + 'px, 0px)';
|
||||
};
|
||||
SpSlidemenu.prototype.getTranslateY = function(y) {
|
||||
var _this = this;
|
||||
return _this.use3d ? 'translate3d(0px, ' + y + 'px, 0px)' : 'translate(0px, ' + y + 'px)';
|
||||
};
|
||||
//Utility Function
|
||||
function hasProp(props) {
|
||||
return some(props, function(prop) {
|
||||
return div.style[prop] !== undefined;
|
||||
});
|
||||
}
|
||||
function upperCaseFirst(str) {
|
||||
return str.charAt(0).toUpperCase() + str.substr(1);
|
||||
}
|
||||
function some(ary, callback) {
|
||||
var i, len;
|
||||
for (i = 0, len = ary.length; i < len; i++) {
|
||||
if (callback(ary[i], i)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function setStyle(elem, prop, val) {
|
||||
var style = elem.style;
|
||||
if (!setStyle.cache) {
|
||||
setStyle.cache = {};
|
||||
}
|
||||
if (setStyle.cache[prop] !== undefined) {
|
||||
style[setStyle.cache[prop]] = val;
|
||||
return;
|
||||
}
|
||||
if (style[prop] !== undefined) {
|
||||
setStyle.cache[prop] = prop;
|
||||
style[prop] = val;
|
||||
return;
|
||||
}
|
||||
some(PREFIX, function(_prefix) {
|
||||
var _prop = upperCaseFirst(_prefix) + upperCaseFirst(prop);
|
||||
if (style[_prop] !== undefined) {
|
||||
//setStyle.cache[prop] = _prop;
|
||||
style[_prop] = val;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
function setStyles(elem, styles) {
|
||||
var style, prop;
|
||||
for (prop in styles) {
|
||||
if (styles.hasOwnProperty(prop)) {
|
||||
setStyle(elem, prop, styles[prop]);
|
||||
}
|
||||
}
|
||||
}
|
||||
function getStyle(style, prop) {
|
||||
var ret;
|
||||
if (style[prop] !== undefined) {
|
||||
return style[prop];
|
||||
}
|
||||
some(PREFIX, function(_prefix) {
|
||||
var _prop = upperCaseFirst(_prefix) + upperCaseFirst(prop);
|
||||
if (style[_prop] !== undefined) {
|
||||
ret = style[_prop];
|
||||
return true;
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
function getCSSName(prop) {
|
||||
var ret;
|
||||
if (!getCSSName.cache) {
|
||||
getCSSName.cache = {};
|
||||
}
|
||||
if (getCSSName.cache[prop] !== undefined) {
|
||||
return getCSSName.cache[prop];
|
||||
}
|
||||
if (div.style[prop] !== undefined) {
|
||||
getCSSName.cache[prop] = prop;
|
||||
return prop;
|
||||
}
|
||||
some(PREFIX, function(_prefix) {
|
||||
var _prop = upperCaseFirst(_prefix) + upperCaseFirst(prop);
|
||||
if (div.style[_prop] !== undefined) {
|
||||
ret = '-' + _prefix + '-' + prop;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
getCSSName.cache[prop] = ret;
|
||||
return ret;
|
||||
}
|
||||
function bind(func, context) {
|
||||
var nativeBind, slice, args;
|
||||
nativeBind = Function.prototype.bind;
|
||||
slice = Array.prototype.slice;
|
||||
if (func.bind === nativeBind && nativeBind) {
|
||||
return nativeBind.apply(func, slice.call(arguments, 1));
|
||||
}
|
||||
args = slice.call(arguments, 2);
|
||||
return function() {
|
||||
return func.apply(context, args.concat(slice.call(arguments)));
|
||||
};
|
||||
}
|
||||
function blockEvent(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
function getDimentions(element) {
|
||||
var previous, key, properties, result;
|
||||
previous = {};
|
||||
properties = {
|
||||
position: 'absolute',
|
||||
visibility: 'hidden',
|
||||
display: 'block'
|
||||
};
|
||||
for (key in properties) {
|
||||
previous[key] = element.style[key];
|
||||
element.style[key] = properties[key];
|
||||
}
|
||||
result = {
|
||||
width: element.offsetWidth,
|
||||
height: element.offsetHeight
|
||||
};
|
||||
for (key in properties) {
|
||||
element.style[key] = previous[key];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function getPage(event, page) {
|
||||
return event.changedTouches ? event.changedTouches[0][page] : event[page];
|
||||
}
|
||||
function addTouchEvent(eventType, element, listener, useCapture) {
|
||||
useCapture = useCapture || false;
|
||||
if (support.touch) {
|
||||
element.addEventListener(EVENTS[eventType].touch, listener, { passive: useCapture });
|
||||
} else {
|
||||
element.addEventListener(EVENTS[eventType].mouse, listener, { passive: useCapture });
|
||||
}
|
||||
}
|
||||
function removeTouchEvent(eventType, element, listener, useCapture) {
|
||||
useCapture = useCapture || false;
|
||||
if (support.touch) {
|
||||
element.removeEventListener(EVENTS[eventType].touch, listener, useCapture);
|
||||
} else {
|
||||
element.removeEventListener(EVENTS[eventType].mouse, listener, useCapture);
|
||||
}
|
||||
}
|
||||
function hasClass(elem, className) {
|
||||
className = " " + className + " ";
|
||||
if (elem.nodeType === 1 && (" " + elem.className + " ").replace(rclass, " ").indexOf(className) >= 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function animate(elem, prop, to, transitionDuration) {
|
||||
var begin, from, duration, easing, timer;
|
||||
begin = +new Date();
|
||||
from = parseInt(elem.style[prop], 10);
|
||||
to = parseInt(to, 10);
|
||||
duration = parseInt(transitionDuration, 10);
|
||||
easing = function(time, duration) {
|
||||
return -(time /= duration) * (time - 2);
|
||||
};
|
||||
timer = setInterval(function() {
|
||||
var time, pos, now;
|
||||
time = new Date() - begin;
|
||||
if (time > duration) {
|
||||
clearInterval(timer);
|
||||
now = to;
|
||||
} else {
|
||||
pos = easing(time, duration);
|
||||
now = pos * (to - from) + from;
|
||||
}
|
||||
elem.style[prop] = now + 'px';
|
||||
}, 10);
|
||||
return timer;
|
||||
}
|
||||
function getBrowserHeight() {
|
||||
if (window.innerHeight) {
|
||||
return window.innerHeight;
|
||||
}
|
||||
else if (document.documentElement && document.documentElement.clientHeight !== 0) {
|
||||
return document.documentElement.clientHeight;
|
||||
}
|
||||
else if (document.body) {
|
||||
return document.body.clientHeight;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
function debounce(func, wait, immediate) {
|
||||
var timeout, result;
|
||||
return function() {
|
||||
var context = this, args = arguments;
|
||||
var later = function() {
|
||||
timeout = null;
|
||||
if (!immediate) result = func.apply(context, args);
|
||||
};
|
||||
var callNow = immediate && !timeout;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
if (callNow) result = func.apply(context, args);
|
||||
return result;
|
||||
};
|
||||
}
|
||||
window.SpSlidemenu = SpSlidemenu;
|
||||
})(window, window.document);
|
13
src/web/app/mobile/scripts/stream.ls
Normal file
13
src/web/app/mobile/scripts/stream.ls
Normal file
@@ -0,0 +1,13 @@
|
||||
# Stream
|
||||
#================================
|
||||
|
||||
stream = require '../../common/scripts/stream.ls'
|
||||
riot = require \riot
|
||||
|
||||
module.exports = (me) ~>
|
||||
s = stream me
|
||||
|
||||
riot.mixin \stream do
|
||||
stream: s.event
|
||||
get-stream-state: s.get-state
|
||||
stream-state-ev: s.state-ev
|
6
src/web/app/mobile/scripts/ui.ls
Normal file
6
src/web/app/mobile/scripts/ui.ls
Normal file
@@ -0,0 +1,6 @@
|
||||
riot = require \riot
|
||||
|
||||
ui = riot.observable!
|
||||
|
||||
riot.mixin \ui do
|
||||
ui: ui
|
12
src/web/app/mobile/style.styl
Normal file
12
src/web/app/mobile/style.styl
Normal file
@@ -0,0 +1,12 @@
|
||||
@import "../base"
|
||||
|
||||
body[data-nav-open='true']
|
||||
#hamburger
|
||||
> i
|
||||
-webkit-transform rotate(-90deg)
|
||||
transform rotate(-90deg)
|
||||
|
||||
#wait
|
||||
top auto
|
||||
bottom 15px
|
||||
left 15px
|
44
src/web/app/mobile/tags.ls
Normal file
44
src/web/app/mobile/tags.ls
Normal file
@@ -0,0 +1,44 @@
|
||||
require './tags/ui.tag'
|
||||
require './tags/ui-header.tag'
|
||||
require './tags/ui-nav.tag'
|
||||
require './tags/stream-indicator.tag'
|
||||
require './tags/page/entrance.tag'
|
||||
require './tags/page/entrance/signin.tag'
|
||||
require './tags/page/entrance/signup.tag'
|
||||
require './tags/page/home.tag'
|
||||
require './tags/page/drive.tag'
|
||||
require './tags/page/notifications.tag'
|
||||
require './tags/page/user.tag'
|
||||
require './tags/page/user-followers.tag'
|
||||
require './tags/page/user-following.tag'
|
||||
require './tags/page/post.tag'
|
||||
require './tags/page/new-post.tag'
|
||||
require './tags/page/search.tag'
|
||||
require './tags/home.tag'
|
||||
require './tags/home-timeline.tag'
|
||||
require './tags/timeline.tag'
|
||||
require './tags/timeline-post.tag'
|
||||
require './tags/timeline-post-sub.tag'
|
||||
require './tags/post-preview.tag'
|
||||
require './tags/sub-post-content.tag'
|
||||
require './tags/images-viewer.tag'
|
||||
require './tags/drive.tag'
|
||||
require './tags/drive-selector.tag'
|
||||
require './tags/drive/file.tag'
|
||||
require './tags/drive/folder.tag'
|
||||
require './tags/drive/file-viewer.tag'
|
||||
require './tags/post-form.tag'
|
||||
require './tags/notification.tag'
|
||||
require './tags/notifications.tag'
|
||||
require './tags/notify.tag'
|
||||
require './tags/notification-preview.tag'
|
||||
require './tags/search.tag'
|
||||
require './tags/search-posts.tag'
|
||||
require './tags/post-detail.tag'
|
||||
require './tags/user.tag'
|
||||
require './tags/user-timeline.tag'
|
||||
require './tags/follow-button.tag'
|
||||
require './tags/user-preview.tag'
|
||||
require './tags/users-list.tag'
|
||||
require './tags/user-following.tag'
|
||||
require './tags/user-followers.tag'
|
75
src/web/app/mobile/tags/drive-selector.tag
Normal file
75
src/web/app/mobile/tags/drive-selector.tag
Normal file
@@ -0,0 +1,75 @@
|
||||
mk-drive-selector
|
||||
div.body
|
||||
header
|
||||
h1
|
||||
| ファイルを選択
|
||||
span.count(if={ files.length > 0 }) ({ files.length })
|
||||
button.close(onclick={ cancel }): i.fa.fa-times
|
||||
button.ok(onclick={ ok }): i.fa.fa-check
|
||||
mk-drive@browser(select={ true }, multiple={ opts.multiple })
|
||||
|
||||
style.
|
||||
display block
|
||||
|
||||
> .body
|
||||
position fixed
|
||||
z-index 2048
|
||||
top 0
|
||||
left 0
|
||||
right 0
|
||||
margin 0 auto
|
||||
width 100%
|
||||
max-width 500px
|
||||
height 100%
|
||||
overflow hidden
|
||||
background #fff
|
||||
box-shadow 0 0 16px rgba(#000, 0.3)
|
||||
|
||||
> header
|
||||
border-bottom solid 1px #eee
|
||||
|
||||
> h1
|
||||
margin 0
|
||||
padding 0
|
||||
text-align center
|
||||
line-height 42px
|
||||
font-size 1em
|
||||
font-weight normal
|
||||
|
||||
> .count
|
||||
margin-left 4px
|
||||
opacity 0.5
|
||||
|
||||
> .close
|
||||
position absolute
|
||||
top 0
|
||||
left 0
|
||||
line-height 42px
|
||||
width 42px
|
||||
|
||||
> .ok
|
||||
position absolute
|
||||
top 0
|
||||
right 0
|
||||
line-height 42px
|
||||
width 42px
|
||||
|
||||
> mk-drive
|
||||
height calc(100% - 42px)
|
||||
overflow scroll
|
||||
|
||||
script.
|
||||
@files = []
|
||||
|
||||
@on \mount ~>
|
||||
@refs.browser.on \change-selected (files) ~>
|
||||
@files = files
|
||||
@update!
|
||||
|
||||
@cancel = ~>
|
||||
@trigger \canceled
|
||||
@unmount!
|
||||
|
||||
@ok = ~>
|
||||
@trigger \selected @files
|
||||
@unmount!
|
338
src/web/app/mobile/tags/drive.tag
Normal file
338
src/web/app/mobile/tags/drive.tag
Normal file
@@ -0,0 +1,338 @@
|
||||
mk-drive
|
||||
nav
|
||||
p(onclick={ go-root })
|
||||
i.fa.fa-cloud
|
||||
| ドライブ
|
||||
virtual(each={ folder in hierarchy-folders })
|
||||
span: i.fa.fa-angle-right
|
||||
p(onclick={ _move }) { folder.name }
|
||||
span(if={ folder != null }): i.fa.fa-angle-right
|
||||
p(if={ folder != null }) { folder.name }
|
||||
div.browser(if={ file == null }, class={ loading: loading })
|
||||
div.folders(if={ folders.length > 0 })
|
||||
virtual(each={ folder in folders })
|
||||
mk-drive-folder(folder={ folder })
|
||||
p(if={ more-folders })
|
||||
| もっと読み込む
|
||||
div.files(if={ files.length > 0 })
|
||||
virtual(each={ file in files })
|
||||
mk-drive-file(file={ file })
|
||||
p(if={ more-files })
|
||||
| もっと読み込む
|
||||
div.empty(if={ files.length == 0 && folders.length == 0 && !loading })
|
||||
p(if={ !folder == null })
|
||||
| ドライブには何もありません。
|
||||
p(if={ folder != null })
|
||||
| このフォルダーは空です
|
||||
div.loading(if={ loading }).
|
||||
<div class="spinner">
|
||||
<div class="dot1"></div>
|
||||
<div class="dot2"></div>
|
||||
</div>
|
||||
mk-drive-file-viewer(if={ file != null }, file={ file })
|
||||
|
||||
style.
|
||||
display block
|
||||
background #fff
|
||||
|
||||
> nav
|
||||
display block
|
||||
width 100%
|
||||
padding 10px 12px
|
||||
overflow auto
|
||||
white-space nowrap
|
||||
font-size 0.9em
|
||||
color #555
|
||||
background #fff
|
||||
border-bottom solid 1px #dfdfdf
|
||||
|
||||
> p
|
||||
display inline
|
||||
margin 0
|
||||
padding 0
|
||||
|
||||
&:last-child
|
||||
font-weight bold
|
||||
|
||||
> i
|
||||
margin-right 4px
|
||||
|
||||
> span
|
||||
margin 0 8px
|
||||
opacity 0.5
|
||||
|
||||
> .browser
|
||||
&.loading
|
||||
opacity 0.5
|
||||
|
||||
> .folders
|
||||
> mk-drive-folder
|
||||
border-bottom solid 1px #eee
|
||||
|
||||
> .files
|
||||
> mk-drive-file
|
||||
border-bottom solid 1px #eee
|
||||
|
||||
> .empty
|
||||
padding 16px
|
||||
text-align center
|
||||
color #999
|
||||
pointer-events none
|
||||
|
||||
> p
|
||||
margin 0
|
||||
|
||||
> .loading
|
||||
.spinner
|
||||
margin 100px auto
|
||||
width 40px
|
||||
height 40px
|
||||
text-align center
|
||||
|
||||
animation sk-rotate 2.0s infinite linear
|
||||
|
||||
.dot1, .dot2
|
||||
width 60%
|
||||
height 60%
|
||||
display inline-block
|
||||
position absolute
|
||||
top 0
|
||||
background-color rgba(0, 0, 0, 0.3)
|
||||
border-radius 100%
|
||||
|
||||
animation sk-bounce 2.0s infinite ease-in-out
|
||||
|
||||
.dot2
|
||||
top auto
|
||||
bottom 0
|
||||
animation-delay -1.0s
|
||||
|
||||
@keyframes sk-rotate { 100% { transform: rotate(360deg); }}
|
||||
|
||||
@keyframes sk-bounce {
|
||||
0%, 100% {
|
||||
transform: scale(0.0);
|
||||
} 50% {
|
||||
transform: scale(1.0);
|
||||
}
|
||||
}
|
||||
|
||||
script.
|
||||
@mixin \api
|
||||
@mixin \stream
|
||||
|
||||
@files = []
|
||||
@folders = []
|
||||
@hierarchy-folders = []
|
||||
@selected-files = []
|
||||
|
||||
# 現在の階層(フォルダ)
|
||||
# * null でルートを表す
|
||||
@folder = null
|
||||
|
||||
@file = null
|
||||
|
||||
@is-select-mode = @opts.select? and @opts.select
|
||||
@multiple = if @opts.multiple? then @opts.multiple else false
|
||||
|
||||
@on \mount ~>
|
||||
@stream.on \drive_file_created @on-stream-drive-file-created
|
||||
@stream.on \drive_file_updated @on-stream-drive-file-updated
|
||||
@stream.on \drive_folder_created @on-stream-drive-folder-created
|
||||
@stream.on \drive_folder_updated @on-stream-drive-folder-updated
|
||||
|
||||
# Riotのバグでnullを渡しても""になる
|
||||
# https://github.com/riot/riot/issues/2080
|
||||
#if @opts.folder?
|
||||
if @opts.folder? and @opts.folder != ''
|
||||
@cd @opts.folder
|
||||
else
|
||||
@load!
|
||||
|
||||
@on \unmount ~>
|
||||
@stream.off \drive_file_created @on-stream-drive-file-created
|
||||
@stream.off \drive_file_updated @on-stream-drive-file-updated
|
||||
@stream.off \drive_folder_created @on-stream-drive-folder-created
|
||||
@stream.off \drive_folder_updated @on-stream-drive-folder-updated
|
||||
|
||||
@on-stream-drive-file-created = (file) ~>
|
||||
@add-file file, true
|
||||
|
||||
@on-stream-drive-file-updated = (file) ~>
|
||||
current = if @folder? then @folder.id else null
|
||||
if current != file.folder_id
|
||||
@remove-file file
|
||||
else
|
||||
@add-file file, true
|
||||
|
||||
@on-stream-drive-folder-created = (folder) ~>
|
||||
@add-folder folder, true
|
||||
|
||||
@on-stream-drive-folder-updated = (folder) ~>
|
||||
current = if @folder? then @folder.id else null
|
||||
if current != folder.parent_id
|
||||
@remove-folder folder
|
||||
else
|
||||
@add-folder folder, true
|
||||
|
||||
@_move = (ev) ~>
|
||||
@move ev.item.folder
|
||||
|
||||
@move = (target-folder) ~>
|
||||
@cd target-folder, true
|
||||
|
||||
@cd = (target-folder, is-move) ~>
|
||||
if target-folder? and typeof target-folder == \object
|
||||
target-folder = target-folder.id
|
||||
|
||||
if target-folder == null
|
||||
@go-root!
|
||||
return
|
||||
|
||||
@loading = true
|
||||
@update!
|
||||
|
||||
@api \drive/folders/show do
|
||||
folder_id: target-folder
|
||||
.then (folder) ~>
|
||||
@folder = folder
|
||||
@hierarchy-folders = []
|
||||
|
||||
x = (f) ~>
|
||||
@hierarchy-folders.unshift f
|
||||
if f.parent?
|
||||
x f.parent
|
||||
|
||||
if folder.parent?
|
||||
x folder.parent
|
||||
|
||||
@update!
|
||||
if is-move then @trigger \move @folder
|
||||
@trigger \cd @folder
|
||||
@load!
|
||||
.catch (err, text-status) ->
|
||||
console.error err
|
||||
|
||||
@add-folder = (folder, unshift = false) ~>
|
||||
current = if @folder? then @folder.id else null
|
||||
if current != folder.parent_id
|
||||
return
|
||||
|
||||
if (@folders.some (f) ~> f.id == folder.id)
|
||||
return
|
||||
|
||||
if unshift
|
||||
@folders.unshift folder
|
||||
else
|
||||
@folders.push folder
|
||||
|
||||
@update!
|
||||
|
||||
@add-file = (file, unshift = false) ~>
|
||||
current = if @folder? then @folder.id else null
|
||||
if current != file.folder_id
|
||||
return
|
||||
|
||||
if (@files.some (f) ~> f.id == file.id)
|
||||
exist = (@files.map (f) -> f.id).index-of file.id
|
||||
@files[exist] = file
|
||||
@update!
|
||||
return
|
||||
|
||||
if unshift
|
||||
@files.unshift file
|
||||
else
|
||||
@files.push file
|
||||
|
||||
@update!
|
||||
|
||||
@remove-folder = (folder) ~>
|
||||
if typeof folder == \object
|
||||
folder = folder.id
|
||||
@folders = @folders.filter (f) -> f.id != folder
|
||||
@update!
|
||||
|
||||
@remove-file = (file) ~>
|
||||
if typeof file == \object
|
||||
file = file.id
|
||||
@files = @files.filter (f) -> f.id != file
|
||||
@update!
|
||||
|
||||
@go-root = ~>
|
||||
if @folder != null
|
||||
@folder = null
|
||||
@hierarchy-folders = []
|
||||
@update!
|
||||
@trigger \move-root
|
||||
@load!
|
||||
|
||||
@load = ~>
|
||||
@folders = []
|
||||
@files = []
|
||||
@more-folders = false
|
||||
@more-files = false
|
||||
@loading = true
|
||||
@update!
|
||||
|
||||
@trigger \begin-load
|
||||
|
||||
load-folders = null
|
||||
load-files = null
|
||||
|
||||
folders-max = 20
|
||||
files-max = 20
|
||||
|
||||
# フォルダ一覧取得
|
||||
@api \drive/folders do
|
||||
folder_id: if @folder? then @folder.id else null
|
||||
limit: folders-max + 1
|
||||
.then (folders) ~>
|
||||
if folders.length == folders-max + 1
|
||||
@more-folders = true
|
||||
folders.pop!
|
||||
load-folders := folders
|
||||
complete!
|
||||
.catch (err, text-status) ~>
|
||||
console.error err
|
||||
|
||||
# ファイル一覧取得
|
||||
@api \drive/files do
|
||||
folder_id: if @folder? then @folder.id else null
|
||||
limit: files-max + 1
|
||||
.then (files) ~>
|
||||
if files.length == files-max + 1
|
||||
@more-files = true
|
||||
files.pop!
|
||||
load-files := files
|
||||
complete!
|
||||
.catch (err, text-status) ~>
|
||||
console.error err
|
||||
|
||||
flag = false
|
||||
complete = ~>
|
||||
if flag
|
||||
load-folders.for-each (folder) ~>
|
||||
@add-folder folder
|
||||
load-files.for-each (file) ~>
|
||||
@add-file file
|
||||
@loading = false
|
||||
@update!
|
||||
|
||||
@trigger \loaded
|
||||
else
|
||||
flag := true
|
||||
@trigger \load-mid
|
||||
|
||||
@choose-file = (file) ~>
|
||||
if @is-select-mode
|
||||
exist = @selected-files.some (f) ~> f.id == file.id
|
||||
if exist
|
||||
@selected-files = (@selected-files.filter (f) ~> f.id != file.id)
|
||||
else
|
||||
@selected-files.push file
|
||||
@update!
|
||||
@trigger \change-selected @selected-files
|
||||
else
|
||||
@file = file
|
||||
@update!
|
||||
@trigger \open-file @file
|
8
src/web/app/mobile/tags/drive/file-viewer.tag
Normal file
8
src/web/app/mobile/tags/drive/file-viewer.tag
Normal file
@@ -0,0 +1,8 @@
|
||||
mk-drive-file-viewer
|
||||
p.name { file.name }
|
||||
|
||||
style.
|
||||
display block
|
||||
|
||||
script.
|
||||
@file = @opts.file
|
130
src/web/app/mobile/tags/drive/file.tag
Normal file
130
src/web/app/mobile/tags/drive/file.tag
Normal file
@@ -0,0 +1,130 @@
|
||||
mk-drive-file(onclick={ onclick }, data-is-selected={ is-selected })
|
||||
div.container
|
||||
div.thumbnail(style={ 'background-image: url(' + file.url + '?thumbnail&size=128)' })
|
||||
div.body
|
||||
p.name { file.name }
|
||||
//
|
||||
if file.tags.length > 0
|
||||
ul.tags
|
||||
each tag in file.tags
|
||||
li.tag(style={background: tag.color, color: contrast(tag.color)})= tag.name
|
||||
footer
|
||||
p.type
|
||||
mk-file-type-icon(file={ file })
|
||||
| { file.type }
|
||||
p.separator
|
||||
p.data-size { bytes-to-size(file.datasize) }
|
||||
p.separator
|
||||
p.created-at
|
||||
i.fa.fa-clock-o
|
||||
mk-time(time={ file.created_at })
|
||||
|
||||
style.
|
||||
display block
|
||||
|
||||
&, *
|
||||
user-select none
|
||||
|
||||
*
|
||||
pointer-events none
|
||||
|
||||
> .container
|
||||
max-width 500px
|
||||
margin 0 auto
|
||||
padding 16px
|
||||
|
||||
&:after
|
||||
content ""
|
||||
display block
|
||||
clear both
|
||||
|
||||
> .thumbnail
|
||||
display block
|
||||
float left
|
||||
width 64px
|
||||
height 64px
|
||||
background-size cover
|
||||
background-position center center
|
||||
|
||||
> .body
|
||||
display block
|
||||
float left
|
||||
width calc(100% - 74px)
|
||||
margin-left 10px
|
||||
|
||||
> .name
|
||||
display block
|
||||
margin 0
|
||||
padding 0
|
||||
font-size 0.9em
|
||||
font-weight bold
|
||||
color #555
|
||||
text-overflow ellipsis
|
||||
word-wrap break-word
|
||||
|
||||
> .tags
|
||||
display block
|
||||
margin 4px 0 0 0
|
||||
padding 0
|
||||
list-style none
|
||||
font-size 0.5em
|
||||
|
||||
> .tag
|
||||
display inline-block
|
||||
margin 0 5px 0 0
|
||||
padding 1px 5px
|
||||
border-radius 2px
|
||||
|
||||
> footer
|
||||
display block
|
||||
margin 4px 0 0 0
|
||||
font-size 0.7em
|
||||
|
||||
> .separator
|
||||
display inline
|
||||
margin 0
|
||||
padding 0 4px
|
||||
color #CDCDCD
|
||||
|
||||
> .type
|
||||
display inline
|
||||
margin 0
|
||||
padding 0
|
||||
color #9D9D9D
|
||||
|
||||
> mk-file-type-icon
|
||||
margin-right 4px
|
||||
|
||||
> .data-size
|
||||
display inline
|
||||
margin 0
|
||||
padding 0
|
||||
color #9D9D9D
|
||||
|
||||
> .created-at
|
||||
display inline
|
||||
margin 0
|
||||
padding 0
|
||||
color #BDBDBD
|
||||
|
||||
> i
|
||||
margin-right 2px
|
||||
|
||||
&[data-is-selected]
|
||||
background $theme-color
|
||||
|
||||
&, *
|
||||
color #fff !important
|
||||
|
||||
script.
|
||||
@mixin \bytes-to-size
|
||||
|
||||
@browser = @parent
|
||||
@file = @opts.file
|
||||
@is-selected = @browser.selected-files.some (f) ~> f.id == @file.id
|
||||
|
||||
@browser.on \change-selected (selects) ~>
|
||||
@is-selected = selects.some (f) ~> f.id == @file.id
|
||||
|
||||
@onclick = ~>
|
||||
@browser.choose-file @file
|
45
src/web/app/mobile/tags/drive/folder.tag
Normal file
45
src/web/app/mobile/tags/drive/folder.tag
Normal file
@@ -0,0 +1,45 @@
|
||||
mk-drive-folder(onclick={ onclick })
|
||||
div.container
|
||||
p.name
|
||||
i.fa.fa-folder
|
||||
| { folder.name }
|
||||
i.fa.fa-angle-right
|
||||
|
||||
style.
|
||||
display block
|
||||
color #777
|
||||
|
||||
&, *
|
||||
user-select none
|
||||
|
||||
*
|
||||
pointer-events none
|
||||
|
||||
> .container
|
||||
max-width 500px
|
||||
margin 0 auto
|
||||
padding 16px
|
||||
|
||||
> .name
|
||||
display block
|
||||
margin 0
|
||||
padding 0
|
||||
|
||||
> i
|
||||
margin-right 6px
|
||||
|
||||
> i
|
||||
position absolute
|
||||
top 0
|
||||
bottom 0
|
||||
right 8px
|
||||
margin auto 0 auto 0
|
||||
width 1em
|
||||
height 1em
|
||||
|
||||
script.
|
||||
@browser = @parent
|
||||
@folder = @opts.folder
|
||||
|
||||
@onclick = ~>
|
||||
@browser.move @folder
|
108
src/web/app/mobile/tags/follow-button.tag
Normal file
108
src/web/app/mobile/tags/follow-button.tag
Normal file
@@ -0,0 +1,108 @@
|
||||
mk-follow-button
|
||||
button(if={ !init }, class={ wait: wait, follow: !user.is_following, unfollow: user.is_following },
|
||||
onclick={ onclick },
|
||||
disabled={ wait })
|
||||
i.fa.fa-minus(if={ !wait && user.is_following })
|
||||
i.fa.fa-plus(if={ !wait && !user.is_following })
|
||||
i.fa.fa-spinner.fa-pulse.fa-fw(if={ wait })
|
||||
| { user.is_following ? 'フォロー解除' : 'フォロー' }
|
||||
div.init(if={ init }): i.fa.fa-spinner.fa-pulse.fa-fw
|
||||
|
||||
style.
|
||||
display block
|
||||
|
||||
> button
|
||||
> .init
|
||||
display block
|
||||
user-select none
|
||||
cursor pointer
|
||||
padding 0 16px
|
||||
margin 0
|
||||
height inherit
|
||||
font-size 16px
|
||||
outline none
|
||||
border solid 1px $theme-color
|
||||
border-radius 4px
|
||||
|
||||
*
|
||||
pointer-events none
|
||||
|
||||
&.follow
|
||||
color $theme-color
|
||||
background transparent
|
||||
|
||||
&:hover
|
||||
background rgba($theme-color, 0.1)
|
||||
|
||||
&:active
|
||||
background rgba($theme-color, 0.2)
|
||||
|
||||
&.unfollow
|
||||
color $theme-color-foreground
|
||||
background $theme-color
|
||||
|
||||
&.wait
|
||||
cursor wait !important
|
||||
opacity 0.7
|
||||
|
||||
&.init
|
||||
cursor wait !important
|
||||
opacity 0.7
|
||||
|
||||
> i
|
||||
margin-right 4px
|
||||
|
||||
script.
|
||||
@mixin \api
|
||||
@mixin \is-promise
|
||||
@mixin \stream
|
||||
|
||||
@user = null
|
||||
@user-promise = if @is-promise @opts.user then @opts.user else Promise.resolve @opts.user
|
||||
@init = true
|
||||
@wait = false
|
||||
|
||||
@on \mount ~>
|
||||
@user-promise.then (user) ~>
|
||||
@user = user
|
||||
@init = false
|
||||
@update!
|
||||
@stream.on \follow @on-stream-follow
|
||||
@stream.on \unfollow @on-stream-unfollow
|
||||
|
||||
@on \unmount ~>
|
||||
@stream.off \follow @on-stream-follow
|
||||
@stream.off \unfollow @on-stream-unfollow
|
||||
|
||||
@on-stream-follow = (user) ~>
|
||||
if user.id == @user.id
|
||||
@user = user
|
||||
@update!
|
||||
|
||||
@on-stream-unfollow = (user) ~>
|
||||
if user.id == @user.id
|
||||
@user = user
|
||||
@update!
|
||||
|
||||
@onclick = ~>
|
||||
@wait = true
|
||||
if @user.is_following
|
||||
@api \following/delete do
|
||||
user_id: @user.id
|
||||
.then ~>
|
||||
@user.is_following = false
|
||||
.catch (err) ->
|
||||
console.error err
|
||||
.then ~>
|
||||
@wait = false
|
||||
@update!
|
||||
else
|
||||
@api \following/create do
|
||||
user_id: @user.id
|
||||
.then ~>
|
||||
@user.is_following = true
|
||||
.catch (err) ->
|
||||
console.error err
|
||||
.then ~>
|
||||
@wait = false
|
||||
@update!
|
40
src/web/app/mobile/tags/home-timeline.tag
Normal file
40
src/web/app/mobile/tags/home-timeline.tag
Normal file
@@ -0,0 +1,40 @@
|
||||
mk-home-timeline
|
||||
mk-timeline@timeline(init={ init }, more={ more }, empty={ '表示する投稿がありません。誰かしらをフォローするなどしましょう。' })
|
||||
|
||||
style.
|
||||
display block
|
||||
|
||||
script.
|
||||
@mixin \api
|
||||
@mixin \stream
|
||||
|
||||
@init = new Promise (res, rej) ~>
|
||||
@api \posts/timeline
|
||||
.then (posts) ~>
|
||||
res posts
|
||||
@trigger \loaded
|
||||
|
||||
@on \mount ~>
|
||||
@stream.on \post @on-stream-post
|
||||
@stream.on \follow @on-stream-follow
|
||||
@stream.on \unfollow @on-stream-unfollow
|
||||
|
||||
@on \unmount ~>
|
||||
@stream.off \post @on-stream-post
|
||||
@stream.off \follow @on-stream-follow
|
||||
@stream.off \unfollow @on-stream-unfollow
|
||||
|
||||
@more = ~>
|
||||
@api \posts/timeline do
|
||||
max_id: @refs.timeline.tail!.id
|
||||
|
||||
@on-stream-post = (post) ~>
|
||||
@is-empty = false
|
||||
@update!
|
||||
@refs.timeline.add-post post
|
||||
|
||||
@on-stream-follow = ~>
|
||||
@fetch!
|
||||
|
||||
@on-stream-unfollow = ~>
|
||||
@fetch!
|
17
src/web/app/mobile/tags/home.tag
Normal file
17
src/web/app/mobile/tags/home.tag
Normal file
@@ -0,0 +1,17 @@
|
||||
mk-home
|
||||
mk-home-timeline@tl
|
||||
|
||||
style.
|
||||
display block
|
||||
|
||||
> mk-home-timeline
|
||||
max-width 600px
|
||||
margin 0 auto
|
||||
|
||||
@media (min-width 500px)
|
||||
padding 16px
|
||||
|
||||
script.
|
||||
@on \mount ~>
|
||||
@refs.tl.on \loaded ~>
|
||||
@trigger \loaded
|
25
src/web/app/mobile/tags/images-viewer.tag
Normal file
25
src/web/app/mobile/tags/images-viewer.tag
Normal file
@@ -0,0 +1,25 @@
|
||||
mk-images-viewer
|
||||
div.image@view(onclick={ click })
|
||||
img@img(src={ image.url + '?thumbnail&size=512' }, alt={ image.name }, title={ image.name })
|
||||
|
||||
style.
|
||||
display block
|
||||
padding 8px
|
||||
overflow hidden
|
||||
box-shadow 0 0 4px rgba(0, 0, 0, 0.2)
|
||||
border-radius 4px
|
||||
|
||||
> .image
|
||||
|
||||
> img
|
||||
display block
|
||||
max-height 256px
|
||||
max-width 100%
|
||||
margin 0 auto
|
||||
|
||||
script.
|
||||
@images = @opts.images
|
||||
@image = @images.0
|
||||
|
||||
@click = ~>
|
||||
window.open @image.url
|
117
src/web/app/mobile/tags/notification-preview.tag
Normal file
117
src/web/app/mobile/tags/notification-preview.tag
Normal file
@@ -0,0 +1,117 @@
|
||||
mk-notification-preview(class={ notification.type })
|
||||
div.main(if={ notification.type == 'like' })
|
||||
img.avatar(src={ notification.user.avatar_url + '?thumbnail&size=64' }, alt='avatar')
|
||||
div.text
|
||||
p
|
||||
i.fa.fa-thumbs-o-up
|
||||
| { notification.user.name }
|
||||
p.post-ref { get-post-summary(notification.post) }
|
||||
|
||||
div.main(if={ notification.type == 'repost' })
|
||||
img.avatar(src={ notification.post.user.avatar_url + '?thumbnail&size=64' }, alt='avatar')
|
||||
div.text
|
||||
p
|
||||
i.fa.fa-retweet
|
||||
| { notification.post.user.name }
|
||||
p.post-ref { get-post-summary(notification.post.repost) }
|
||||
|
||||
div.main(if={ notification.type == 'quote' })
|
||||
img.avatar(src={ notification.post.user.avatar_url + '?thumbnail&size=64' }, alt='avatar')
|
||||
div.text
|
||||
p
|
||||
i.fa.fa-quote-left
|
||||
| { notification.post.user.name }
|
||||
p.post-preview { get-post-summary(notification.post) }
|
||||
|
||||
div.main(if={ notification.type == 'follow' })
|
||||
img.avatar(src={ notification.user.avatar_url + '?thumbnail&size=64' }, alt='avatar')
|
||||
div.text
|
||||
p
|
||||
i.fa.fa-user-plus
|
||||
| { notification.user.name }
|
||||
|
||||
div.main(if={ notification.type == 'reply' })
|
||||
img.avatar(src={ notification.post.user.avatar_url + '?thumbnail&size=64' }, alt='avatar')
|
||||
div.text
|
||||
p
|
||||
i.fa.fa-reply
|
||||
| { notification.post.user.name }
|
||||
p.post-preview { get-post-summary(notification.post) }
|
||||
|
||||
div.main(if={ notification.type == 'mention' })
|
||||
img.avatar(src={ notification.post.user.avatar_url + '?thumbnail&size=64' }, alt='avatar')
|
||||
div.text
|
||||
p
|
||||
i.fa.fa-at
|
||||
| { notification.post.user.name }
|
||||
p.post-preview { get-post-summary(notification.post) }
|
||||
|
||||
style.
|
||||
display block
|
||||
margin 0
|
||||
padding 8px
|
||||
color #fff
|
||||
|
||||
> .main
|
||||
word-wrap break-word
|
||||
|
||||
&:after
|
||||
content ""
|
||||
display block
|
||||
clear both
|
||||
|
||||
img
|
||||
display block
|
||||
float left
|
||||
min-width 36px
|
||||
min-height 36px
|
||||
max-width 36px
|
||||
max-height 36px
|
||||
border-radius 6px
|
||||
|
||||
.text
|
||||
float right
|
||||
width calc(100% - 36px)
|
||||
padding-left 8px
|
||||
|
||||
p
|
||||
margin 0
|
||||
|
||||
i
|
||||
margin-right 4px
|
||||
|
||||
.post-ref
|
||||
|
||||
&:before, &:after
|
||||
font-family FontAwesome
|
||||
font-size 1em
|
||||
font-weight normal
|
||||
font-style normal
|
||||
display inline-block
|
||||
margin-right 3px
|
||||
|
||||
&:before
|
||||
content "\f10d"
|
||||
|
||||
&:after
|
||||
content "\f10e"
|
||||
|
||||
&.like
|
||||
.text p i
|
||||
color #FFAC33
|
||||
|
||||
&.repost, &.quote
|
||||
.text p i
|
||||
color #77B255
|
||||
|
||||
&.follow
|
||||
.text p i
|
||||
color #53c7ce
|
||||
|
||||
&.reply, &.mention
|
||||
.text p i
|
||||
color #fff
|
||||
|
||||
script.
|
||||
@mixin \get-post-summary
|
||||
@notification = @opts.notification
|
142
src/web/app/mobile/tags/notification.tag
Normal file
142
src/web/app/mobile/tags/notification.tag
Normal file
@@ -0,0 +1,142 @@
|
||||
mk-notification(class={ notification.type })
|
||||
mk-time(time={ notification.created_at })
|
||||
|
||||
div.main(if={ notification.type == 'like' })
|
||||
a.avatar-anchor(href={ CONFIG.url + '/' + notification.user.username })
|
||||
img.avatar(src={ notification.user.avatar_url + '?thumbnail&size=64' }, alt='avatar')
|
||||
div.text
|
||||
p
|
||||
i.fa.fa-thumbs-o-up
|
||||
a(href={ CONFIG.url + '/' + notification.user.username }) { notification.user.name }
|
||||
a.post-ref(href={ CONFIG.url + '/' + notification.post.user.username + '/' + notification.post.id }) { get-post-summary(notification.post) }
|
||||
|
||||
div.main(if={ notification.type == 'repost' })
|
||||
a.avatar-anchor(href={ CONFIG.url + '/' + notification.post.user.username })
|
||||
img.avatar(src={ notification.post.user.avatar_url + '?thumbnail&size=64' }, alt='avatar')
|
||||
div.text
|
||||
p
|
||||
i.fa.fa-retweet
|
||||
a(href={ CONFIG.url + '/' + notification.post.user.username }) { notification.post.user.name }
|
||||
a.post-ref(href={ CONFIG.url + '/' + notification.post.user.username + '/' + notification.post.id }) { get-post-summary(notification.post.repost) }
|
||||
|
||||
div.main(if={ notification.type == 'quote' })
|
||||
a.avatar-anchor(href={ CONFIG.url + '/' + notification.post.user.username })
|
||||
img.avatar(src={ notification.post.user.avatar_url + '?thumbnail&size=64' }, alt='avatar')
|
||||
div.text
|
||||
p
|
||||
i.fa.fa-quote-left
|
||||
a(href={ CONFIG.url + '/' + notification.post.user.username }) { notification.post.user.name }
|
||||
a.post-preview(href={ CONFIG.url + '/' + notification.post.user.username + '/' + notification.post.id }) { get-post-summary(notification.post) }
|
||||
|
||||
div.main(if={ notification.type == 'follow' })
|
||||
a.avatar-anchor(href={ CONFIG.url + '/' + notification.user.username })
|
||||
img.avatar(src={ notification.user.avatar_url + '?thumbnail&size=64' }, alt='avatar')
|
||||
div.text
|
||||
p
|
||||
i.fa.fa-user-plus
|
||||
a(href={ CONFIG.url + '/' + notification.user.username }) { notification.user.name }
|
||||
|
||||
div.main(if={ notification.type == 'reply' })
|
||||
a.avatar-anchor(href={ CONFIG.url + '/' + notification.post.user.username })
|
||||
img.avatar(src={ notification.post.user.avatar_url + '?thumbnail&size=64' }, alt='avatar')
|
||||
div.text
|
||||
p
|
||||
i.fa.fa-reply
|
||||
a(href={ CONFIG.url + '/' + notification.post.user.username }) { notification.post.user.name }
|
||||
a.post-preview(href={ CONFIG.url + '/' + notification.post.user.username + '/' + notification.post.id }) { get-post-summary(notification.post) }
|
||||
|
||||
div.main(if={ notification.type == 'mention' })
|
||||
a.avatar-anchor(href={ CONFIG.url + '/' + notification.post.user.username })
|
||||
img.avatar(src={ notification.post.user.avatar_url + '?thumbnail&size=64' }, alt='avatar')
|
||||
div.text
|
||||
p
|
||||
i.fa.fa-at
|
||||
a(href={ CONFIG.url + '/' + notification.post.user.username }) { notification.post.user.name }
|
||||
a.post-preview(href={ CONFIG.url + '/' + notification.post.user.username + '/' + notification.post.id }) { get-post-summary(notification.post) }
|
||||
|
||||
style.
|
||||
display block
|
||||
margin 0
|
||||
padding 16px
|
||||
|
||||
> mk-time
|
||||
display inline
|
||||
position absolute
|
||||
top 16px
|
||||
right 12px
|
||||
vertical-align top
|
||||
color rgba(0, 0, 0, 0.6)
|
||||
font-size 12px
|
||||
|
||||
> .main
|
||||
word-wrap break-word
|
||||
|
||||
&:after
|
||||
content ""
|
||||
display block
|
||||
clear both
|
||||
|
||||
.avatar-anchor
|
||||
display block
|
||||
float left
|
||||
|
||||
img
|
||||
min-width 36px
|
||||
min-height 36px
|
||||
max-width 36px
|
||||
max-height 36px
|
||||
border-radius 6px
|
||||
|
||||
.text
|
||||
float right
|
||||
width calc(100% - 36px)
|
||||
padding-left 8px
|
||||
|
||||
p
|
||||
margin 0
|
||||
|
||||
i
|
||||
margin-right 4px
|
||||
|
||||
.post-preview
|
||||
color rgba(0, 0, 0, 0.7)
|
||||
|
||||
.post-ref
|
||||
color rgba(0, 0, 0, 0.7)
|
||||
|
||||
&:before, &:after
|
||||
font-family FontAwesome
|
||||
font-size 1em
|
||||
font-weight normal
|
||||
font-style normal
|
||||
display inline-block
|
||||
margin-right 3px
|
||||
|
||||
&:before
|
||||
content "\f10d"
|
||||
|
||||
&:after
|
||||
content "\f10e"
|
||||
|
||||
&.like
|
||||
.text p i
|
||||
color #FFAC33
|
||||
|
||||
&.repost, &.quote
|
||||
.text p i
|
||||
color #77B255
|
||||
|
||||
&.follow
|
||||
.text p i
|
||||
color #53c7ce
|
||||
|
||||
&.reply, &.mention
|
||||
.text p i
|
||||
color #555
|
||||
|
||||
.post-preview
|
||||
color rgba(0, 0, 0, 0.7)
|
||||
|
||||
script.
|
||||
@mixin \get-post-summary
|
||||
@notification = @opts.notification
|
98
src/web/app/mobile/tags/notifications.tag
Normal file
98
src/web/app/mobile/tags/notifications.tag
Normal file
@@ -0,0 +1,98 @@
|
||||
mk-notifications
|
||||
div.notifications(if={ notifications.length != 0 })
|
||||
virtual(each={ notification, i in notifications })
|
||||
mk-notification(notification={ notification })
|
||||
|
||||
p.date(if={ i != notifications.length - 1 && notification._date != notifications[i + 1]._date })
|
||||
span
|
||||
i.fa.fa-angle-up
|
||||
| { notification._datetext }
|
||||
span
|
||||
i.fa.fa-angle-down
|
||||
| { notifications[i + 1]._datetext }
|
||||
|
||||
p.empty(if={ notifications.length == 0 && !loading })
|
||||
| ありません!
|
||||
p.loading(if={ loading })
|
||||
i.fa.fa-spinner.fa-pulse.fa-fw
|
||||
| 読み込んでいます
|
||||
mk-ellipsis
|
||||
|
||||
style.
|
||||
display block
|
||||
background #fff
|
||||
|
||||
> .notifications
|
||||
margin 0 auto
|
||||
max-width 500px
|
||||
|
||||
> mk-notification
|
||||
border-bottom solid 1px rgba(0, 0, 0, 0.05)
|
||||
|
||||
&:last-child
|
||||
border-bottom none
|
||||
|
||||
> .date
|
||||
display block
|
||||
margin 0
|
||||
line-height 32px
|
||||
text-align center
|
||||
font-size 0.8em
|
||||
color #aaa
|
||||
background #fdfdfd
|
||||
border-bottom solid 1px rgba(0, 0, 0, 0.05)
|
||||
|
||||
span
|
||||
margin 0 16px
|
||||
|
||||
i
|
||||
margin-right 8px
|
||||
|
||||
> .empty
|
||||
margin 0
|
||||
padding 16px
|
||||
text-align center
|
||||
color #aaa
|
||||
|
||||
> .loading
|
||||
margin 0
|
||||
padding 16px
|
||||
text-align center
|
||||
color #aaa
|
||||
|
||||
> i
|
||||
margin-right 4px
|
||||
|
||||
script.
|
||||
@mixin \api
|
||||
@mixin \stream
|
||||
@mixin \get-post-summary
|
||||
|
||||
@notifications = []
|
||||
@loading = true
|
||||
|
||||
@on \mount ~>
|
||||
@api \i/notifications
|
||||
.then (notifications) ~>
|
||||
@notifications = notifications
|
||||
@loading = false
|
||||
@update!
|
||||
@trigger \loaded
|
||||
.catch (err, text-status) ->
|
||||
console.error err
|
||||
|
||||
@stream.on \notification @on-notification
|
||||
|
||||
@on \unmount ~>
|
||||
@stream.off \notification @on-notification
|
||||
|
||||
@on-notification = (notification) ~>
|
||||
@notifications.unshift notification
|
||||
@update!
|
||||
|
||||
@on \update ~>
|
||||
@notifications.for-each (notification) ~>
|
||||
date = (new Date notification.created_at).get-date!
|
||||
month = (new Date notification.created_at).get-month! + 1
|
||||
notification._date = date
|
||||
notification._datetext = month + '月 ' + date + '日'
|
35
src/web/app/mobile/tags/notify.tag
Normal file
35
src/web/app/mobile/tags/notify.tag
Normal file
@@ -0,0 +1,35 @@
|
||||
mk-notify
|
||||
mk-notification-preview(notification={ opts.notification })
|
||||
|
||||
style.
|
||||
display block
|
||||
position fixed
|
||||
z-index 1024
|
||||
bottom -64px
|
||||
left 0
|
||||
width 100%
|
||||
height 64px
|
||||
pointer-events none
|
||||
-webkit-backdrop-filter blur(2px)
|
||||
backdrop-filter blur(2px)
|
||||
background-color rgba(#000, 0.5)
|
||||
|
||||
script.
|
||||
@on \mount ~>
|
||||
Velocity @root, {
|
||||
bottom: \0px
|
||||
} {
|
||||
duration: 500ms
|
||||
easing: \ease-out
|
||||
}
|
||||
|
||||
set-timeout ~>
|
||||
Velocity @root, {
|
||||
bottom: \-64px
|
||||
} {
|
||||
duration: 500ms
|
||||
easing: \ease-out
|
||||
complete: ~>
|
||||
@unmount!
|
||||
}
|
||||
, 6000ms
|
46
src/web/app/mobile/tags/page/drive.tag
Normal file
46
src/web/app/mobile/tags/page/drive.tag
Normal file
@@ -0,0 +1,46 @@
|
||||
mk-drive-page
|
||||
mk-ui@ui: mk-drive@browser(folder={ parent.opts.folder }, file={ parent.opts.file })
|
||||
|
||||
style.
|
||||
display block
|
||||
|
||||
script.
|
||||
@mixin \ui
|
||||
@mixin \ui-progress
|
||||
|
||||
@on \mount ~>
|
||||
document.title = 'Misskey Drive'
|
||||
@ui.trigger \title '<i class="fa fa-cloud"></i>ドライブ'
|
||||
|
||||
@refs.ui.refs.browser.on \begin-load ~>
|
||||
@Progress.start!
|
||||
|
||||
@refs.ui.refs.browser.on \loaded-mid ~>
|
||||
@Progress.set 0.5
|
||||
|
||||
@refs.ui.refs.browser.on \loaded ~>
|
||||
@Progress.done!
|
||||
|
||||
@refs.ui.refs.browser.on \move-root ~>
|
||||
@ui.trigger \title '<i class="fa fa-cloud"></i>ドライブ'
|
||||
|
||||
# Rewrite URL
|
||||
history.push-state null null '/i/drive'
|
||||
|
||||
@refs.ui.refs.browser.on \cd (folder) ~>
|
||||
# TODO: escape html characters in folder.name
|
||||
@ui.trigger \title '<i class="fa fa-folder-open"></i>' + folder.name
|
||||
|
||||
@refs.ui.refs.browser.on \move (folder) ~>
|
||||
# Rewrite URL
|
||||
history.push-state null null '/i/drive/folder/' + folder.id
|
||||
|
||||
@refs.ui.refs.browser.on \open-file (file) ~>
|
||||
# TODO: escape html characters in file.name
|
||||
@ui.trigger \title '<mk-file-type-icon class="icon"></mk-file-type-icon>' + file.name
|
||||
|
||||
# Rewrite URL
|
||||
history.push-state null null '/i/drive/file/' + file.id
|
||||
|
||||
riot.mount \mk-file-type-icon do
|
||||
file: file
|
57
src/web/app/mobile/tags/page/entrance.tag
Normal file
57
src/web/app/mobile/tags/page/entrance.tag
Normal file
@@ -0,0 +1,57 @@
|
||||
mk-entrance
|
||||
main
|
||||
img(src='/_/resources/title.svg', alt='Misskey')
|
||||
|
||||
mk-entrance-signin(if={ mode == 'signin' })
|
||||
mk-entrance-signup(if={ mode == 'signup' })
|
||||
div.introduction(if={ mode == 'introduction' })
|
||||
mk-introduction
|
||||
button(onclick={ signin }) わかった
|
||||
|
||||
footer
|
||||
mk-copyright
|
||||
|
||||
style.
|
||||
display block
|
||||
height 100%
|
||||
|
||||
> main
|
||||
display block
|
||||
|
||||
> img
|
||||
display block
|
||||
width 130px
|
||||
height 120px
|
||||
margin 0 auto
|
||||
|
||||
> .introduction
|
||||
max-width 300px
|
||||
margin 0 auto
|
||||
color #666
|
||||
|
||||
> button
|
||||
display block
|
||||
margin 16px auto 0 auto
|
||||
|
||||
> footer
|
||||
> mk-copyright
|
||||
margin 0
|
||||
text-align center
|
||||
line-height 64px
|
||||
font-size 10px
|
||||
color rgba(#000, 0.5)
|
||||
|
||||
script.
|
||||
@mode = \signin
|
||||
|
||||
@signup = ~>
|
||||
@mode = \signup
|
||||
@update!
|
||||
|
||||
@signin = ~>
|
||||
@mode = \signin
|
||||
@update!
|
||||
|
||||
@introduction = ~>
|
||||
@mode = \introduction
|
||||
@update!
|
45
src/web/app/mobile/tags/page/entrance/signin.tag
Normal file
45
src/web/app/mobile/tags/page/entrance/signin.tag
Normal file
@@ -0,0 +1,45 @@
|
||||
mk-entrance-signin
|
||||
mk-signin
|
||||
div.divider: span or
|
||||
button.signup(onclick={ parent.signup }) 新規登録
|
||||
a.introduction(onclick={ parent.introduction }) Misskeyについて
|
||||
|
||||
style.
|
||||
display block
|
||||
margin 0 auto
|
||||
padding 0 8px
|
||||
max-width 350px
|
||||
text-align center
|
||||
|
||||
> .signup
|
||||
padding 16px
|
||||
width 100%
|
||||
font-size 1em
|
||||
color #fff
|
||||
background $theme-color
|
||||
border-radius 3px
|
||||
|
||||
> .divider
|
||||
padding 16px 0
|
||||
text-align center
|
||||
|
||||
&:after
|
||||
content ""
|
||||
display block
|
||||
position absolute
|
||||
top 50%
|
||||
width 100%
|
||||
height 1px
|
||||
border-top solid 1px rgba(0, 0, 0, 0.1)
|
||||
|
||||
> *
|
||||
z-index 1
|
||||
padding 0 8px
|
||||
color rgba(0, 0, 0, 0.5)
|
||||
background #fdfdfd
|
||||
|
||||
> .introduction
|
||||
display inline-block
|
||||
margin-top 16px
|
||||
font-size 12px
|
||||
color #666
|
35
src/web/app/mobile/tags/page/entrance/signup.tag
Normal file
35
src/web/app/mobile/tags/page/entrance/signup.tag
Normal file
@@ -0,0 +1,35 @@
|
||||
mk-entrance-signup
|
||||
mk-signup
|
||||
button.cancel(type='button', onclick={ parent.signin }, title='キャンセル'): i.fa.fa-times
|
||||
|
||||
style.
|
||||
display block
|
||||
margin 0 auto
|
||||
padding 0 8px
|
||||
max-width 350px
|
||||
|
||||
> .cancel
|
||||
cursor pointer
|
||||
display block
|
||||
position absolute
|
||||
top 0
|
||||
right 0
|
||||
z-index 1
|
||||
margin 0
|
||||
padding 0
|
||||
font-size 1.2em
|
||||
color #999
|
||||
border none
|
||||
outline none
|
||||
box-shadow none
|
||||
background transparent
|
||||
transition opacity 0.1s ease
|
||||
|
||||
&:hover
|
||||
color #555
|
||||
|
||||
&:active
|
||||
color #222
|
||||
|
||||
> i
|
||||
padding 14px
|
40
src/web/app/mobile/tags/page/home.tag
Normal file
40
src/web/app/mobile/tags/page/home.tag
Normal file
@@ -0,0 +1,40 @@
|
||||
mk-home-page
|
||||
mk-ui@ui: mk-home@home
|
||||
|
||||
style.
|
||||
display block
|
||||
|
||||
script.
|
||||
@mixin \i
|
||||
@mixin \ui
|
||||
@mixin \ui-progress
|
||||
@mixin \stream
|
||||
@mixin \get-post-summary
|
||||
|
||||
@unread-count = 0
|
||||
|
||||
@on \mount ~>
|
||||
document.title = 'Misskey'
|
||||
@ui.trigger \title '<i class="fa fa-home"></i>ホーム'
|
||||
|
||||
@Progress.start!
|
||||
|
||||
@stream.on \post @on-stream-post
|
||||
document.add-event-listener \visibilitychange @window-on-visibilitychange, false
|
||||
|
||||
@refs.ui.refs.home.on \loaded ~>
|
||||
@Progress.done!
|
||||
|
||||
@on \unmount ~>
|
||||
@stream.off \post @on-stream-post
|
||||
document.remove-event-listener \visibilitychange @window-on-visibilitychange
|
||||
|
||||
@on-stream-post = (post) ~>
|
||||
if document.hidden and post.user_id !== @I.id
|
||||
@unread-count++
|
||||
document.title = '(' + @unread-count + ') ' + @get-post-summary post
|
||||
|
||||
@window-on-visibilitychange = ~>
|
||||
if !document.hidden
|
||||
@unread-count = 0
|
||||
document.title = 'Misskey'
|
5
src/web/app/mobile/tags/page/new-post.tag
Normal file
5
src/web/app/mobile/tags/page/new-post.tag
Normal file
@@ -0,0 +1,5 @@
|
||||
mk-new-post-page
|
||||
mk-post-form@form
|
||||
|
||||
style.
|
||||
display block
|
18
src/web/app/mobile/tags/page/notifications.tag
Normal file
18
src/web/app/mobile/tags/page/notifications.tag
Normal file
@@ -0,0 +1,18 @@
|
||||
mk-notifications-page
|
||||
mk-ui@ui: mk-notifications@notifications
|
||||
|
||||
style.
|
||||
display block
|
||||
|
||||
script.
|
||||
@mixin \ui
|
||||
@mixin \ui-progress
|
||||
|
||||
@on \mount ~>
|
||||
document.title = 'Misskey | 通知'
|
||||
@ui.trigger \title '<i class="fa fa-bell-o"></i>通知'
|
||||
|
||||
@Progress.start!
|
||||
|
||||
@refs.ui.refs.notifications.on \loaded ~>
|
||||
@Progress.done!
|
31
src/web/app/mobile/tags/page/post.tag
Normal file
31
src/web/app/mobile/tags/page/post.tag
Normal file
@@ -0,0 +1,31 @@
|
||||
mk-post-page
|
||||
mk-ui@ui: main: mk-post-detail@post(post={ parent.post })
|
||||
|
||||
style.
|
||||
display block
|
||||
|
||||
main
|
||||
background #fff
|
||||
|
||||
> mk-post-detail
|
||||
width 100%
|
||||
max-width 500px
|
||||
margin 0 auto
|
||||
|
||||
script.
|
||||
@mixin \ui
|
||||
@mixin \ui-progress
|
||||
|
||||
@post = @opts.post
|
||||
|
||||
@on \mount ~>
|
||||
document.title = 'Misskey'
|
||||
@ui.trigger \title '<i class="fa fa-sticky-note-o"></i>投稿'
|
||||
|
||||
@Progress.start!
|
||||
|
||||
@refs.ui.refs.post.on \post-fetched ~>
|
||||
@Progress.set 0.5
|
||||
|
||||
@refs.ui.refs.post.on \loaded ~>
|
||||
@Progress.done!
|
19
src/web/app/mobile/tags/page/search.tag
Normal file
19
src/web/app/mobile/tags/page/search.tag
Normal file
@@ -0,0 +1,19 @@
|
||||
mk-search-page
|
||||
mk-ui@ui: mk-search@search(query={ parent.opts.query })
|
||||
|
||||
style.
|
||||
display block
|
||||
|
||||
script.
|
||||
@mixin \ui
|
||||
@mixin \ui-progress
|
||||
|
||||
@on \mount ~>
|
||||
document.title = '検索: ' + @opts.query + ' | Misskey'
|
||||
# TODO: クエリをHTMLエスケープ
|
||||
@ui.trigger \title '<i class="fa fa-search"></i>' + @opts.query
|
||||
|
||||
@Progress.start!
|
||||
|
||||
@refs.ui.refs.search.on \loaded ~>
|
||||
@Progress.done!
|
31
src/web/app/mobile/tags/page/user-followers.tag
Normal file
31
src/web/app/mobile/tags/page/user-followers.tag
Normal file
@@ -0,0 +1,31 @@
|
||||
mk-user-followers-page
|
||||
mk-ui@ui: mk-user-followers@list(if={ !parent.fetching }, user={ parent.user })
|
||||
|
||||
style.
|
||||
display block
|
||||
|
||||
script.
|
||||
@mixin \ui
|
||||
@mixin \ui-progress
|
||||
@mixin \api
|
||||
|
||||
@fetching = true
|
||||
@user = null
|
||||
|
||||
@on \mount ~>
|
||||
@Progress.start!
|
||||
|
||||
@api \users/show do
|
||||
username: @opts.user
|
||||
.then (user) ~>
|
||||
@user = user
|
||||
@fetching = false
|
||||
|
||||
document.title = user.name + 'のフォロワー | Misskey'
|
||||
# TODO: ユーザー名をエスケープ
|
||||
@ui.trigger \title '<img src="' + user.avatar_url + '?thumbnail&size=64">' + user.name + 'のフォロー'
|
||||
|
||||
@update!
|
||||
|
||||
@refs.ui.refs.list.on \loaded ~>
|
||||
@Progress.done!
|
31
src/web/app/mobile/tags/page/user-following.tag
Normal file
31
src/web/app/mobile/tags/page/user-following.tag
Normal file
@@ -0,0 +1,31 @@
|
||||
mk-user-following-page
|
||||
mk-ui@ui: mk-user-following@list(if={ !parent.fetching }, user={ parent.user })
|
||||
|
||||
style.
|
||||
display block
|
||||
|
||||
script.
|
||||
@mixin \ui
|
||||
@mixin \ui-progress
|
||||
@mixin \api
|
||||
|
||||
@fetching = true
|
||||
@user = null
|
||||
|
||||
@on \mount ~>
|
||||
@Progress.start!
|
||||
|
||||
@api \users/show do
|
||||
username: @opts.user
|
||||
.then (user) ~>
|
||||
@user = user
|
||||
@fetching = false
|
||||
|
||||
document.title = user.name + 'のフォロー | Misskey'
|
||||
# TODO: ユーザー名をエスケープ
|
||||
@ui.trigger \title '<img src="' + user.avatar_url + '?thumbnail&size=64">' + user.name + 'のフォロー'
|
||||
|
||||
@update!
|
||||
|
||||
@refs.ui.refs.list.on \loaded ~>
|
||||
@Progress.done!
|
20
src/web/app/mobile/tags/page/user.tag
Normal file
20
src/web/app/mobile/tags/page/user.tag
Normal file
@@ -0,0 +1,20 @@
|
||||
mk-user-page
|
||||
mk-ui@ui: mk-user@user(user={ parent.user }, page={ parent.opts.page })
|
||||
|
||||
style.
|
||||
display block
|
||||
|
||||
script.
|
||||
@mixin \ui
|
||||
@mixin \ui-progress
|
||||
|
||||
@user = @opts.user
|
||||
|
||||
@on \mount ~>
|
||||
@Progress.start!
|
||||
|
||||
@refs.ui.refs.user.on \loaded (user) ~>
|
||||
@Progress.done!
|
||||
document.title = user.name + ' | Misskey'
|
||||
# TODO: ユーザー名をエスケープ
|
||||
@ui.trigger \title '<i class="fa fa-user"></i>' + user.name
|
415
src/web/app/mobile/tags/post-detail.tag
Normal file
415
src/web/app/mobile/tags/post-detail.tag
Normal file
@@ -0,0 +1,415 @@
|
||||
mk-post-detail
|
||||
|
||||
div.fetching(if={ fetching })
|
||||
mk-ellipsis-icon
|
||||
|
||||
div.main(if={ !fetching })
|
||||
|
||||
button.read-more(if={ p.reply_to && p.reply_to.reply_to_id && context == null }, onclick={ load-context }, disabled={ loading-context })
|
||||
i.fa.fa-ellipsis-v(if={ !loading-context })
|
||||
i.fa.fa-spinner.fa-pulse(if={ loading-context })
|
||||
|
||||
div.context
|
||||
virtual(each={ post in context })
|
||||
mk-post-preview(post={ post })
|
||||
|
||||
div.reply-to(if={ p.reply_to })
|
||||
mk-post-preview(post={ p.reply_to })
|
||||
|
||||
div.repost(if={ is-repost })
|
||||
p
|
||||
a.avatar-anchor(href={ CONFIG.url + '/' + post.user.username }): img.avatar(src={ post.user.avatar_url + '?thumbnail&size=32' }, alt='avatar')
|
||||
i.fa.fa-retweet
|
||||
a.name(href={ CONFIG.url + '/' + post.user.username }) { post.user.name }
|
||||
| がRepost
|
||||
|
||||
article
|
||||
a.avatar-anchor(href={ CONFIG.url + '/' + p.user.username })
|
||||
img.avatar(src={ p.user.avatar_url + '?thumbnail&size=64' }, alt='avatar')
|
||||
header
|
||||
a.name(href={ CONFIG.url + '/' + p.user.username })
|
||||
| { p.user.name }
|
||||
span.username
|
||||
| @{ p.user.username }
|
||||
div.body
|
||||
div.text@text
|
||||
div.media(if={ p.media })
|
||||
virtual(each={ file in p.media })
|
||||
img(src={ file.url + '?thumbnail&size=512' }, alt={ file.name }, title={ file.name })
|
||||
a.time(href={ url })
|
||||
mk-time(time={ p.created_at }, mode='detail')
|
||||
footer
|
||||
button(onclick={ reply }, title='返信')
|
||||
i.fa.fa-reply
|
||||
p.count(if={ p.replies_count > 0 }) { p.replies_count }
|
||||
button(onclick={ repost }, title='Repost')
|
||||
i.fa.fa-retweet
|
||||
p.count(if={ p.repost_count > 0 }) { p.repost_count }
|
||||
button(class={ liked: p.is_liked }, onclick={ like }, title='善哉')
|
||||
i.fa.fa-thumbs-o-up
|
||||
p.count(if={ p.likes_count > 0 }) { p.likes_count }
|
||||
button(onclick={ NotImplementedException }): i.fa.fa-ellipsis-h
|
||||
div.reposts-and-likes
|
||||
div.reposts(if={ reposts && reposts.length > 0 })
|
||||
header
|
||||
a { p.repost_count }
|
||||
p Repost
|
||||
ol.users
|
||||
li.user(each={ reposts })
|
||||
a.avatar-anchor(href={ CONFIG.url + '/' + user.username }, title={ user.name })
|
||||
img.avatar(src={ user.avatar_url + '?thumbnail&size=32' }, alt='')
|
||||
div.likes(if={ likes && likes.length > 0 })
|
||||
header
|
||||
a { p.likes_count }
|
||||
p いいね
|
||||
ol.users
|
||||
li.user(each={ likes })
|
||||
a.avatar-anchor(href={ CONFIG.url + '/' + username }, title={ name })
|
||||
img.avatar(src={ avatar_url + '?thumbnail&size=32' }, alt='')
|
||||
|
||||
div.replies
|
||||
virtual(each={ post in replies })
|
||||
mk-post-detail-sub(post={ post })
|
||||
|
||||
style.
|
||||
display block
|
||||
margin 0
|
||||
padding 0
|
||||
|
||||
> .fetching
|
||||
padding 64px 0
|
||||
|
||||
> .main
|
||||
|
||||
> .read-more
|
||||
display block
|
||||
margin 0
|
||||
padding 10px 0
|
||||
width 100%
|
||||
font-size 1em
|
||||
text-align center
|
||||
color #999
|
||||
cursor pointer
|
||||
background #fafafa
|
||||
outline none
|
||||
border none
|
||||
border-bottom solid 1px #eef0f2
|
||||
border-radius 6px 6px 0 0
|
||||
box-shadow none
|
||||
|
||||
&:hover
|
||||
background #f6f6f6
|
||||
|
||||
&:active
|
||||
background #f0f0f0
|
||||
|
||||
&:disabled
|
||||
color #ccc
|
||||
|
||||
> .context
|
||||
> *
|
||||
border-bottom 1px solid #eef0f2
|
||||
|
||||
> .repost
|
||||
color #9dbb00
|
||||
background linear-gradient(to bottom, #edfde2 0%, #fff 100%)
|
||||
|
||||
> p
|
||||
margin 0
|
||||
padding 16px 32px
|
||||
|
||||
.avatar-anchor
|
||||
display inline-block
|
||||
|
||||
.avatar
|
||||
vertical-align bottom
|
||||
min-width 28px
|
||||
min-height 28px
|
||||
max-width 28px
|
||||
max-height 28px
|
||||
margin 0 8px 0 0
|
||||
border-radius 6px
|
||||
|
||||
i
|
||||
margin-right 4px
|
||||
|
||||
.name
|
||||
font-weight bold
|
||||
|
||||
& + article
|
||||
padding-top 8px
|
||||
|
||||
> .reply-to
|
||||
border-bottom 1px solid #eef0f2
|
||||
|
||||
> article
|
||||
padding 14px 16px 9px 16px
|
||||
|
||||
@media (min-width 500px)
|
||||
padding 28px 32px 18px 32px
|
||||
|
||||
&:after
|
||||
content ""
|
||||
display block
|
||||
clear both
|
||||
|
||||
&:hover
|
||||
> .main > footer > button
|
||||
color #888
|
||||
|
||||
> .avatar-anchor
|
||||
display block
|
||||
|
||||
> .avatar
|
||||
display block
|
||||
width 54px
|
||||
height 54px
|
||||
margin 0
|
||||
border-radius 8px
|
||||
vertical-align bottom
|
||||
|
||||
@media (min-width 500px)
|
||||
width 60px
|
||||
height 60px
|
||||
|
||||
> header
|
||||
position absolute
|
||||
top 18px
|
||||
left 80px
|
||||
width calc(100% - 80px)
|
||||
|
||||
@media (min-width 500px)
|
||||
top 28px
|
||||
left 108px
|
||||
width calc(100% - 108px)
|
||||
|
||||
> .name
|
||||
display inline-block
|
||||
margin 0
|
||||
color #777
|
||||
font-size 16px
|
||||
font-weight bold
|
||||
text-align left
|
||||
text-decoration none
|
||||
|
||||
&:hover
|
||||
text-decoration underline
|
||||
|
||||
> .username
|
||||
display block
|
||||
text-align left
|
||||
margin 0
|
||||
color #ccc
|
||||
|
||||
> .body
|
||||
padding 8px 0
|
||||
|
||||
> .text
|
||||
cursor default
|
||||
display block
|
||||
margin 0
|
||||
padding 0
|
||||
word-wrap break-word
|
||||
font-size 16px
|
||||
color #717171
|
||||
|
||||
@media (min-width 500px)
|
||||
font-size 24px
|
||||
|
||||
> mk-url-preview
|
||||
margin-top 8px
|
||||
|
||||
> .media
|
||||
> img
|
||||
display block
|
||||
max-width 100%
|
||||
|
||||
> .time
|
||||
font-size 16px
|
||||
color #c0c0c0
|
||||
|
||||
> footer
|
||||
font-size 1.2em
|
||||
|
||||
> button
|
||||
margin 0 28px 0 0
|
||||
padding 8px
|
||||
background transparent
|
||||
border none
|
||||
box-shadow none
|
||||
font-size 1em
|
||||
color #ddd
|
||||
cursor pointer
|
||||
|
||||
&:hover
|
||||
color #666
|
||||
|
||||
> .count
|
||||
display inline
|
||||
margin 0 0 0 8px
|
||||
color #999
|
||||
|
||||
&.liked
|
||||
color $theme-color
|
||||
|
||||
> .reposts-and-likes
|
||||
display flex
|
||||
justify-content center
|
||||
padding 0
|
||||
margin 16px 0
|
||||
|
||||
&:empty
|
||||
display none
|
||||
|
||||
> .reposts
|
||||
> .likes
|
||||
display flex
|
||||
flex 1 1
|
||||
padding 0
|
||||
border-top solid 1px #F2EFEE
|
||||
|
||||
> header
|
||||
flex 1 1 80px
|
||||
max-width 80px
|
||||
padding 8px 5px 0px 10px
|
||||
|
||||
> a
|
||||
display block
|
||||
font-size 1.5em
|
||||
line-height 1.4em
|
||||
|
||||
> p
|
||||
display block
|
||||
margin 0
|
||||
font-size 0.7em
|
||||
line-height 1em
|
||||
font-weight normal
|
||||
color #a0a2a5
|
||||
|
||||
> .users
|
||||
display block
|
||||
flex 1 1
|
||||
margin 0
|
||||
padding 10px 10px 10px 5px
|
||||
list-style none
|
||||
|
||||
> .user
|
||||
display block
|
||||
float left
|
||||
margin 4px
|
||||
padding 0
|
||||
|
||||
> .avatar-anchor
|
||||
display:block
|
||||
|
||||
> .avatar
|
||||
vertical-align bottom
|
||||
width 24px
|
||||
height 24px
|
||||
border-radius 4px
|
||||
|
||||
> .reposts + .likes
|
||||
margin-left 16px
|
||||
|
||||
> .replies
|
||||
> *
|
||||
border-top 1px solid #eef0f2
|
||||
|
||||
script.
|
||||
@mixin \api
|
||||
@mixin \text
|
||||
@mixin \get-post-summary
|
||||
@mixin \open-post-form
|
||||
|
||||
@fetching = true
|
||||
@loading-context = false
|
||||
@content = null
|
||||
@post = null
|
||||
|
||||
@on \mount ~>
|
||||
@api \posts/show do
|
||||
post_id: @opts.post
|
||||
.then (post) ~>
|
||||
@post = post
|
||||
@is-repost = @post.repost?
|
||||
@p = if @is-repost then @post.repost else @post
|
||||
@summary = @get-post-summary @p
|
||||
@trigger \loaded
|
||||
@fetching = false
|
||||
@update!
|
||||
|
||||
if @p.text?
|
||||
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
|
||||
.filter (t) -> t.type == \link
|
||||
.map (t) ~>
|
||||
@preview = @refs.text.append-child document.create-element \mk-url-preview
|
||||
riot.mount @preview, do
|
||||
url: t.content
|
||||
|
||||
# Get likes
|
||||
@api \posts/likes do
|
||||
post_id: @p.id
|
||||
limit: 8
|
||||
.then (likes) ~>
|
||||
@likes = likes
|
||||
@update!
|
||||
|
||||
# Get reposts
|
||||
@api \posts/reposts do
|
||||
post_id: @p.id
|
||||
limit: 8
|
||||
.then (reposts) ~>
|
||||
@reposts = reposts
|
||||
@update!
|
||||
|
||||
# Get replies
|
||||
@api \posts/replies do
|
||||
post_id: @p.id
|
||||
limit: 8
|
||||
.then (replies) ~>
|
||||
@replies = replies
|
||||
@update!
|
||||
|
||||
@reply = ~>
|
||||
@open-post-form do
|
||||
reply: @p
|
||||
|
||||
@repost = ~>
|
||||
text = window.prompt '「' + @summary + '」をRepost'
|
||||
if text?
|
||||
@api \posts/create do
|
||||
repost_id: @p.id
|
||||
text: if text == '' then undefined else text
|
||||
|
||||
@like = ~>
|
||||
if @p.is_liked
|
||||
@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 = ~>
|
||||
@loading-context = true
|
||||
|
||||
# Get context
|
||||
@api \posts/context do
|
||||
post_id: @p.reply_to_id
|
||||
.then (context) ~>
|
||||
@context = context.reverse!
|
||||
@loading-context = false
|
||||
@update!
|
254
src/web/app/mobile/tags/post-form.tag
Normal file
254
src/web/app/mobile/tags/post-form.tag
Normal file
@@ -0,0 +1,254 @@
|
||||
mk-post-form
|
||||
header: div
|
||||
button.cancel(onclick={ cancel }): i.fa.fa-times
|
||||
div
|
||||
span.text-count(class={ over: refs.text.value.length > 300 }) { 300 - refs.text.value.length }
|
||||
button.submit(onclick={ post }) 投稿
|
||||
div.form
|
||||
mk-post-preview(if={ opts.reply }, post={ opts.reply })
|
||||
textarea@text(disabled={ wait }, oninput={ update }, onkeypress={ onkeypress }, onpaste={ onpaste }, placeholder={ opts.reply ? 'この投稿への返信...' : 'いまどうしてる?' })
|
||||
div.attaches(if={ files.length != 0 })
|
||||
ul.files@attaches
|
||||
li.file(each={ files })
|
||||
div.img(style='background-image: url({ url + "?thumbnail&size=64" })', title={ name })
|
||||
li.add(if={ files.length < 4 }, title='PCからファイルを添付', onclick={ select-file }): i.fa.fa-plus
|
||||
mk-uploader@uploader
|
||||
button@upload(onclick={ select-file }): i.fa.fa-upload
|
||||
button@drive(onclick={ select-file-from-drive }): i.fa.fa-cloud
|
||||
input@file(type='file', accept='image/*', multiple, onchange={ change-file })
|
||||
|
||||
style.
|
||||
display block
|
||||
padding-top 50px
|
||||
|
||||
> header
|
||||
position fixed
|
||||
z-index 1000
|
||||
top 0
|
||||
left 0
|
||||
width 100%
|
||||
height 50px
|
||||
background #fff
|
||||
|
||||
> div
|
||||
max-width 500px
|
||||
margin 0 auto
|
||||
|
||||
> .cancel
|
||||
width 50px
|
||||
line-height 50px
|
||||
font-size 24px
|
||||
color #555
|
||||
|
||||
> div
|
||||
position absolute
|
||||
top 0
|
||||
right 0
|
||||
|
||||
> .text-count
|
||||
line-height 50px
|
||||
color #657786
|
||||
|
||||
> .submit
|
||||
margin 8px
|
||||
padding 0 16px
|
||||
line-height 34px
|
||||
color $theme-color-foreground
|
||||
background $theme-color
|
||||
border-radius 4px
|
||||
|
||||
&:disabled
|
||||
opacity 0.7
|
||||
|
||||
> .form
|
||||
max-width 500px
|
||||
margin 0 auto
|
||||
|
||||
> mk-post-preview
|
||||
padding 16px
|
||||
|
||||
> .attaches
|
||||
|
||||
> .files
|
||||
display block
|
||||
margin 0
|
||||
padding 4px
|
||||
list-style none
|
||||
|
||||
&:after
|
||||
content ""
|
||||
display block
|
||||
clear both
|
||||
|
||||
> .file
|
||||
display block
|
||||
float left
|
||||
margin 4px
|
||||
padding 0
|
||||
cursor move
|
||||
|
||||
&:hover > .remove
|
||||
display block
|
||||
|
||||
> .img
|
||||
width 64px
|
||||
height 64px
|
||||
background-size cover
|
||||
background-position center center
|
||||
|
||||
> .remove
|
||||
display none
|
||||
position absolute
|
||||
top -6px
|
||||
right -6px
|
||||
width 16px
|
||||
height 16px
|
||||
cursor pointer
|
||||
|
||||
> .add
|
||||
display block
|
||||
float left
|
||||
margin 4px
|
||||
padding 0
|
||||
border dashed 2px rgba($theme-color, 0.2)
|
||||
cursor pointer
|
||||
|
||||
&:hover
|
||||
border-color rgba($theme-color, 0.3)
|
||||
|
||||
> i
|
||||
color rgba($theme-color, 0.4)
|
||||
|
||||
> i
|
||||
display block
|
||||
width 60px
|
||||
height 60px
|
||||
line-height 60px
|
||||
text-align center
|
||||
font-size 1.2em
|
||||
color rgba($theme-color, 0.2)
|
||||
|
||||
> mk-uploader
|
||||
margin 8px 0 0 0
|
||||
padding 8px
|
||||
|
||||
> [ref='file']
|
||||
display none
|
||||
|
||||
> [ref='text']
|
||||
display block
|
||||
padding 12px
|
||||
margin 0
|
||||
width 100%
|
||||
max-width 100%
|
||||
min-width 100%
|
||||
min-height 80px
|
||||
font-size 16px
|
||||
color #333
|
||||
border none
|
||||
border-bottom solid 1px #ddd
|
||||
border-radius 0
|
||||
|
||||
&:disabled
|
||||
opacity 0.5
|
||||
|
||||
> [ref='upload']
|
||||
> [ref='drive']
|
||||
display inline-block
|
||||
padding 0
|
||||
margin 0
|
||||
width 48px
|
||||
height 48px
|
||||
font-size 20px
|
||||
color #657786
|
||||
background transparent
|
||||
outline none
|
||||
border none
|
||||
border-radius 0
|
||||
box-shadow none
|
||||
|
||||
script.
|
||||
@mixin \api
|
||||
|
||||
@wait = false
|
||||
@uploadings = []
|
||||
@files = []
|
||||
|
||||
@on \mount ~>
|
||||
@refs.uploader.on \uploaded (file) ~>
|
||||
@add-file file
|
||||
|
||||
@refs.uploader.on \change-uploads (uploads) ~>
|
||||
@trigger \change-uploading-files uploads
|
||||
|
||||
@refs.text.focus!
|
||||
|
||||
@onkeypress = (e) ~>
|
||||
if (e.char-code == 10 || e.char-code == 13) && e.ctrl-key
|
||||
@post!
|
||||
else
|
||||
return true
|
||||
|
||||
@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!
|
||||
return true
|
||||
|
||||
@select-file = ~>
|
||||
@refs.file.click!
|
||||
|
||||
@select-file-from-drive = ~>
|
||||
browser = document.body.append-child document.create-element \mk-drive-selector
|
||||
browser = riot.mount browser, do
|
||||
multiple: true
|
||||
.0
|
||||
browser.on \selected (files) ~>
|
||||
files.for-each @add-file
|
||||
|
||||
@change-file = ~>
|
||||
files = @refs.file.files
|
||||
for i from 0 to files.length - 1
|
||||
file = files.item i
|
||||
@upload file
|
||||
|
||||
@upload = (file) ~>
|
||||
@refs.uploader.upload file
|
||||
|
||||
@add-file = (file) ~>
|
||||
file._remove = ~>
|
||||
@files = @files.filter (x) -> x.id != file.id
|
||||
@trigger \change-files @files
|
||||
@update!
|
||||
|
||||
@files.push file
|
||||
@trigger \change-files @files
|
||||
@update!
|
||||
|
||||
@post = ~>
|
||||
@wait = true
|
||||
|
||||
files = if @files? and @files.length > 0
|
||||
then @files.map (f) -> f.id
|
||||
else undefined
|
||||
|
||||
@api \posts/create do
|
||||
text: @refs.text.value
|
||||
media_ids: files
|
||||
reply_to_id: if @opts.reply? then @opts.reply.id else undefined
|
||||
.then (data) ~>
|
||||
@trigger \post
|
||||
@unmount!
|
||||
.catch (err) ~>
|
||||
console.error err
|
||||
#@opts.ui.trigger \notification 'Error!'
|
||||
@wait = false
|
||||
@update!
|
||||
|
||||
@cancel = ~>
|
||||
@trigger \cancel
|
||||
@unmount!
|
89
src/web/app/mobile/tags/post-preview.tag
Normal file
89
src/web/app/mobile/tags/post-preview.tag
Normal file
@@ -0,0 +1,89 @@
|
||||
mk-post-preview
|
||||
article
|
||||
a.avatar-anchor(href={ CONFIG.url + '/' + post.user.username })
|
||||
img.avatar(src={ post.user.avatar_url + '?thumbnail&size=64' }, alt='avatar')
|
||||
div.main
|
||||
header
|
||||
a.name(href={ CONFIG.url + '/' + post.user.username })
|
||||
| { post.user.name }
|
||||
span.username
|
||||
| @{ post.user.username }
|
||||
a.time(href={ CONFIG.url + '/' + post.user.username + '/' + post.id })
|
||||
mk-time(time={ post.created_at })
|
||||
div.body
|
||||
mk-sub-post-content.text(post={ post })
|
||||
|
||||
style.
|
||||
display block
|
||||
margin 0
|
||||
padding 0
|
||||
font-size 0.9em
|
||||
background #fff
|
||||
|
||||
> article
|
||||
|
||||
&:after
|
||||
content ""
|
||||
display block
|
||||
clear both
|
||||
|
||||
&:hover
|
||||
> .main > footer > button
|
||||
color #888
|
||||
|
||||
> .avatar-anchor
|
||||
display block
|
||||
float left
|
||||
margin 0 12px 0 0
|
||||
|
||||
> .avatar
|
||||
display block
|
||||
width 48px
|
||||
height 48px
|
||||
margin 0
|
||||
border-radius 8px
|
||||
vertical-align bottom
|
||||
|
||||
> .main
|
||||
float left
|
||||
width calc(100% - 60px)
|
||||
|
||||
> header
|
||||
margin-bottom 4px
|
||||
white-space nowrap
|
||||
|
||||
> .name
|
||||
display inline
|
||||
margin 0
|
||||
padding 0
|
||||
color #607073
|
||||
font-size 1em
|
||||
font-weight 700
|
||||
text-align left
|
||||
text-decoration none
|
||||
|
||||
&:hover
|
||||
text-decoration underline
|
||||
|
||||
> .username
|
||||
text-align left
|
||||
margin 0 0 0 8px
|
||||
color #d1d8da
|
||||
|
||||
> .time
|
||||
position absolute
|
||||
top 0
|
||||
right 0
|
||||
color #b2b8bb
|
||||
|
||||
> .body
|
||||
|
||||
> .text
|
||||
cursor default
|
||||
margin 0
|
||||
padding 0
|
||||
font-size 1.1em
|
||||
color #717171
|
||||
|
||||
script.
|
||||
@post = @opts.post
|
29
src/web/app/mobile/tags/search-posts.tag
Normal file
29
src/web/app/mobile/tags/search-posts.tag
Normal file
@@ -0,0 +1,29 @@
|
||||
mk-search-posts
|
||||
mk-timeline(init={ init }, more={ more }, empty={ '「' + query + '」に関する投稿は見つかりませんでした。' })
|
||||
|
||||
style.
|
||||
display block
|
||||
background #fff
|
||||
|
||||
script.
|
||||
@mixin \api
|
||||
|
||||
@max = 30
|
||||
@offset = 0
|
||||
|
||||
@query = @opts.query
|
||||
@with-media = @opts.with-media
|
||||
|
||||
@init = new Promise (res, rej) ~>
|
||||
@api \posts/search do
|
||||
query: @query
|
||||
.then (posts) ~>
|
||||
res posts
|
||||
@trigger \loaded
|
||||
|
||||
@more = ~>
|
||||
@offset += @max
|
||||
@api \posts/search do
|
||||
query: @query
|
||||
max: @max
|
||||
offset: @offset
|
12
src/web/app/mobile/tags/search.tag
Normal file
12
src/web/app/mobile/tags/search.tag
Normal file
@@ -0,0 +1,12 @@
|
||||
mk-search
|
||||
mk-search-posts@posts(query={ query })
|
||||
|
||||
style.
|
||||
display block
|
||||
|
||||
script.
|
||||
@query = @opts.query
|
||||
|
||||
@on \mount ~>
|
||||
@refs.posts.on \loaded ~>
|
||||
@trigger \loaded
|
59
src/web/app/mobile/tags/stream-indicator.tag
Normal file
59
src/web/app/mobile/tags/stream-indicator.tag
Normal file
@@ -0,0 +1,59 @@
|
||||
mk-stream-indicator
|
||||
p(if={ state == 'initializing' })
|
||||
i.fa.fa-spinner.fa-spin
|
||||
span
|
||||
| 接続中
|
||||
mk-ellipsis
|
||||
p(if={ state == 'reconnecting' })
|
||||
i.fa.fa-spinner.fa-spin
|
||||
span
|
||||
| 切断されました 接続中
|
||||
mk-ellipsis
|
||||
p(if={ state == 'connected' })
|
||||
i.fa.fa-check
|
||||
span 接続完了
|
||||
|
||||
style.
|
||||
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
|
||||
|
||||
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
|
36
src/web/app/mobile/tags/sub-post-content.tag
Normal file
36
src/web/app/mobile/tags/sub-post-content.tag
Normal file
@@ -0,0 +1,36 @@
|
||||
mk-sub-post-content
|
||||
div.body
|
||||
a.reply(if={ post.reply_to_id }): i.fa.fa-reply
|
||||
span@text
|
||||
a.quote(if={ post.repost_id }, href={ '/post:' + post.repost_id }) RP: ...
|
||||
details(if={ post.media })
|
||||
summary ({ post.media.length }枚の画像)
|
||||
mk-images-viewer(images={ post.media })
|
||||
|
||||
style.
|
||||
display block
|
||||
word-wrap break-word
|
||||
|
||||
> .body
|
||||
> .reply
|
||||
margin-right 6px
|
||||
color #717171
|
||||
|
||||
> .quote
|
||||
margin-left 4px
|
||||
font-style oblique
|
||||
color #a0bf46
|
||||
|
||||
script.
|
||||
@mixin \text
|
||||
|
||||
@post = @opts.post
|
||||
|
||||
@on \mount ~>
|
||||
if @post.text?
|
||||
tokens = @analyze @post.text
|
||||
@refs.text.innerHTML = @compile tokens, false
|
||||
|
||||
@refs.text.children.for-each (e) ~>
|
||||
if e.tag-name == \MK-URL
|
||||
riot.mount e
|
99
src/web/app/mobile/tags/timeline-post-sub.tag
Normal file
99
src/web/app/mobile/tags/timeline-post-sub.tag
Normal file
@@ -0,0 +1,99 @@
|
||||
mk-timeline-post-sub
|
||||
article
|
||||
a.avatar-anchor(href={ '/' + post.user.username })
|
||||
img.avatar(src={ post.user.avatar_url + '?thumbnail&size=96' }, alt='avatar')
|
||||
div.main
|
||||
header
|
||||
a.name(href={ '/' + post.user.username })
|
||||
| { post.user.name }
|
||||
span.username
|
||||
| @{ post.user.username }
|
||||
a.created-at(href={ '/' + post.user.username + '/' + post.id })
|
||||
mk-time(time={ post.created_at })
|
||||
div.body
|
||||
mk-sub-post-content.text(post={ post })
|
||||
|
||||
style.
|
||||
display block
|
||||
margin 0
|
||||
padding 0
|
||||
font-size 0.9em
|
||||
|
||||
> article
|
||||
padding 16px
|
||||
|
||||
&:after
|
||||
content ""
|
||||
display block
|
||||
clear both
|
||||
|
||||
&:hover
|
||||
> .main > footer > button
|
||||
color #888
|
||||
|
||||
> .avatar-anchor
|
||||
display block
|
||||
float left
|
||||
margin 0 10px 0 0
|
||||
|
||||
@media (min-width 500px)
|
||||
margin-right 16px
|
||||
|
||||
> .avatar
|
||||
display block
|
||||
width 44px
|
||||
height 44px
|
||||
margin 0
|
||||
border-radius 8px
|
||||
vertical-align bottom
|
||||
|
||||
@media (min-width 500px)
|
||||
width 52px
|
||||
height 52px
|
||||
|
||||
> .main
|
||||
float left
|
||||
width calc(100% - 54px)
|
||||
|
||||
@media (min-width 500px)
|
||||
width calc(100% - 68px)
|
||||
|
||||
> header
|
||||
margin-bottom 4px
|
||||
white-space nowrap
|
||||
|
||||
> .name
|
||||
display inline
|
||||
margin 0
|
||||
padding 0
|
||||
color #607073
|
||||
font-size 1em
|
||||
font-weight 700
|
||||
text-align left
|
||||
text-decoration none
|
||||
|
||||
&:hover
|
||||
text-decoration underline
|
||||
|
||||
> .username
|
||||
text-align left
|
||||
margin 0 0 0 8px
|
||||
color #d1d8da
|
||||
|
||||
> .created-at
|
||||
position absolute
|
||||
top 0
|
||||
right 0
|
||||
color #b2b8bb
|
||||
|
||||
> .body
|
||||
|
||||
> .text
|
||||
cursor default
|
||||
margin 0
|
||||
padding 0
|
||||
font-size 1.1em
|
||||
color #717171
|
||||
|
||||
script.
|
||||
@post = @opts.post
|
296
src/web/app/mobile/tags/timeline-post.tag
Normal file
296
src/web/app/mobile/tags/timeline-post.tag
Normal file
@@ -0,0 +1,296 @@
|
||||
mk-timeline-post(class={ repost: is-repost })
|
||||
|
||||
div.reply-to(if={ p.reply_to })
|
||||
mk-timeline-post-sub(post={ p.reply_to })
|
||||
|
||||
div.repost(if={ is-repost })
|
||||
p
|
||||
a.avatar-anchor(href={ CONFIG.url + '/' + post.user.username }): img.avatar(src={ post.user.avatar_url + '?thumbnail&size=64' }, alt='avatar')
|
||||
i.fa.fa-retweet
|
||||
a.name(href={ CONFIG.url + '/' + post.user.username }) { post.user.name }
|
||||
| がRepost
|
||||
mk-time(time={ post.created_at })
|
||||
|
||||
article
|
||||
a.avatar-anchor(href={ CONFIG.url + '/' + p.user.username })
|
||||
img.avatar(src={ p.user.avatar_url + '?thumbnail&size=96' }, alt='avatar')
|
||||
div.main
|
||||
header
|
||||
a.name(href={ CONFIG.url + '/' + p.user.username })
|
||||
| { p.user.name }
|
||||
span.username
|
||||
| @{ p.user.username }
|
||||
a.created-at(href={ url })
|
||||
mk-time(time={ p.created_at })
|
||||
div.body
|
||||
div.text
|
||||
a.reply(if={ p.reply_to }): i.fa.fa-reply
|
||||
soan@text
|
||||
a.quote(if={ p.repost != null }) RP:
|
||||
div.media(if={ p.media })
|
||||
mk-images-viewer(images={ p.media })
|
||||
div.repost(if={ p.repost })
|
||||
i.fa.fa-quote-right.fa-flip-horizontal
|
||||
mk-post-preview.repost(post={ p.repost })
|
||||
footer
|
||||
button(onclick={ reply })
|
||||
i.fa.fa-reply
|
||||
p.count(if={ p.replies_count > 0 }) { p.replies_count }
|
||||
button(onclick={ repost }, title='Repost')
|
||||
i.fa.fa-retweet
|
||||
p.count(if={ p.repost_count > 0 }) { p.repost_count }
|
||||
button(class={ liked: p.is_liked }, onclick={ like })
|
||||
i.fa.fa-thumbs-o-up
|
||||
p.count(if={ p.likes_count > 0 }) { p.likes_count }
|
||||
|
||||
style.
|
||||
display block
|
||||
margin 0
|
||||
padding 0
|
||||
font-size 12px
|
||||
|
||||
@media (min-width 350px)
|
||||
font-size 14px
|
||||
|
||||
@media (min-width 500px)
|
||||
font-size 16px
|
||||
|
||||
> .repost
|
||||
color #9dbb00
|
||||
background linear-gradient(to bottom, #edfde2 0%, #fff 100%)
|
||||
|
||||
> p
|
||||
margin 0
|
||||
padding 8px 16px
|
||||
line-height 28px
|
||||
|
||||
@media (min-width 500px)
|
||||
padding 16px
|
||||
|
||||
.avatar-anchor
|
||||
display inline-block
|
||||
|
||||
.avatar
|
||||
vertical-align bottom
|
||||
width 28px
|
||||
height 28px
|
||||
margin 0 8px 0 0
|
||||
border-radius 6px
|
||||
|
||||
i
|
||||
margin-right 4px
|
||||
|
||||
.name
|
||||
font-weight bold
|
||||
|
||||
> mk-time
|
||||
position absolute
|
||||
top 8px
|
||||
right 16px
|
||||
font-size 0.9em
|
||||
line-height 28px
|
||||
|
||||
@media (min-width 500px)
|
||||
top 16px
|
||||
|
||||
& + article
|
||||
padding-top 8px
|
||||
|
||||
> .reply-to
|
||||
background rgba(0, 0, 0, 0.0125)
|
||||
|
||||
> mk-post-preview
|
||||
background transparent
|
||||
|
||||
> article
|
||||
padding 14px 16px 9px 16px
|
||||
|
||||
&:after
|
||||
content ""
|
||||
display block
|
||||
clear both
|
||||
|
||||
> .avatar-anchor
|
||||
display block
|
||||
float left
|
||||
margin 0 10px 0 0
|
||||
|
||||
@media (min-width 500px)
|
||||
margin-right 16px
|
||||
|
||||
> .avatar
|
||||
display block
|
||||
width 48px
|
||||
height 48px
|
||||
margin 0
|
||||
border-radius 6px
|
||||
vertical-align bottom
|
||||
|
||||
@media (min-width 500px)
|
||||
width 58px
|
||||
height 58px
|
||||
border-radius 8px
|
||||
|
||||
> .main
|
||||
float left
|
||||
width calc(100% - 58px)
|
||||
|
||||
@media (min-width 500px)
|
||||
width calc(100% - 74px)
|
||||
|
||||
> header
|
||||
white-space nowrap
|
||||
|
||||
@media (min-width 500px)
|
||||
margin-bottom 2px
|
||||
|
||||
> .name
|
||||
display inline
|
||||
margin 0
|
||||
padding 0
|
||||
color #777
|
||||
font-size 1em
|
||||
font-weight 700
|
||||
text-align left
|
||||
text-decoration none
|
||||
|
||||
&:hover
|
||||
text-decoration underline
|
||||
|
||||
> .username
|
||||
text-align left
|
||||
margin 0 0 0 8px
|
||||
color #ccc
|
||||
|
||||
> .created-at
|
||||
position absolute
|
||||
top 0
|
||||
right 0
|
||||
font-size 0.9em
|
||||
color #c0c0c0
|
||||
|
||||
> .body
|
||||
|
||||
> .text
|
||||
cursor default
|
||||
display block
|
||||
margin 0
|
||||
padding 0
|
||||
word-wrap break-word
|
||||
font-size 1.1em
|
||||
color #717171
|
||||
|
||||
mk-url-preview
|
||||
margin-top 8px
|
||||
|
||||
> .reply
|
||||
margin-right 8px
|
||||
color #717171
|
||||
|
||||
> .quote
|
||||
margin-left 4px
|
||||
font-style oblique
|
||||
color #a0bf46
|
||||
|
||||
> .media
|
||||
> img
|
||||
display block
|
||||
max-width 100%
|
||||
|
||||
> .repost
|
||||
margin 8px 0
|
||||
|
||||
> i:first-child
|
||||
position absolute
|
||||
top -8px
|
||||
left -8px
|
||||
z-index 1
|
||||
color #c0dac6
|
||||
font-size 28px
|
||||
background #fff
|
||||
|
||||
> mk-post-preview
|
||||
padding 16px
|
||||
border dashed 1px #c0dac6
|
||||
border-radius 8px
|
||||
|
||||
> footer
|
||||
> button
|
||||
margin 0 28px 0 0
|
||||
padding 8px
|
||||
background transparent
|
||||
border none
|
||||
box-shadow none
|
||||
font-size 1em
|
||||
color #ddd
|
||||
cursor pointer
|
||||
|
||||
&:hover
|
||||
color #666
|
||||
|
||||
> .count
|
||||
display inline
|
||||
margin 0 0 0 8px
|
||||
color #999
|
||||
|
||||
&.liked
|
||||
color $theme-color
|
||||
|
||||
script.
|
||||
@mixin \api
|
||||
@mixin \text
|
||||
@mixin \get-post-summary
|
||||
@mixin \open-post-form
|
||||
|
||||
@post = @opts.post
|
||||
@is-repost = @post.repost? and !@post.text?
|
||||
@p = if @is-repost then @post.repost else @post
|
||||
@summary = @get-post-summary @p
|
||||
@url = CONFIG.url + '/' + @p.user.username + '/' + @p.id
|
||||
|
||||
@on \mount ~>
|
||||
if @p.text?
|
||||
tokens = if @p._highlight?
|
||||
then @analyze @p._highlight
|
||||
else @analyze @p.text
|
||||
|
||||
@refs.text.innerHTML = if @p._highlight?
|
||||
then @compile tokens, true, false
|
||||
else @compile tokens
|
||||
|
||||
@refs.text.children.for-each (e) ~>
|
||||
if e.tag-name == \MK-URL
|
||||
riot.mount e
|
||||
|
||||
# URLをプレビュー
|
||||
tokens
|
||||
.filter (t) -> t.type == \link
|
||||
.map (t) ~>
|
||||
@preview = @refs.text.append-child document.create-element \mk-url-preview
|
||||
riot.mount @preview, do
|
||||
url: t.content
|
||||
|
||||
@reply = ~>
|
||||
@open-post-form do
|
||||
reply: @p
|
||||
|
||||
@repost = ~>
|
||||
text = window.prompt '「' + @summary + '」をRepost'
|
||||
if text?
|
||||
@api \posts/create do
|
||||
repost_id: @p.id
|
||||
text: if text == '' then undefined else text
|
||||
|
||||
@like = ~>
|
||||
if @p.is_liked
|
||||
@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!
|
128
src/web/app/mobile/tags/timeline.tag
Normal file
128
src/web/app/mobile/tags/timeline.tag
Normal file
@@ -0,0 +1,128 @@
|
||||
mk-timeline
|
||||
div.init(if={ init })
|
||||
i.fa.fa-spinner.fa-pulse
|
||||
| 読み込んでいます
|
||||
div.empty(if={ !init && posts.length == 0 })
|
||||
i.fa.fa-comments-o
|
||||
| { opts.empty || '表示するものがありません' }
|
||||
virtual(each={ post, i in posts })
|
||||
mk-timeline-post(post={ post })
|
||||
p.date(if={ i != posts.length - 1 && post._date != posts[i + 1]._date })
|
||||
span
|
||||
i.fa.fa-angle-up
|
||||
| { post._datetext }
|
||||
span
|
||||
i.fa.fa-angle-down
|
||||
| { posts[i + 1]._datetext }
|
||||
footer(if={ !init })
|
||||
button(if={ can-fetch-more }, onclick={ more }, disabled={ fetching })
|
||||
span(if={ !fetching }) もっとみる
|
||||
span(if={ fetching })
|
||||
| 読み込み中
|
||||
mk-ellipsis
|
||||
|
||||
style.
|
||||
display block
|
||||
background #fff
|
||||
background-clip content-box
|
||||
overflow hidden
|
||||
|
||||
> .init
|
||||
padding 64px 0
|
||||
text-align center
|
||||
color #999
|
||||
|
||||
> i
|
||||
margin-right 4px
|
||||
|
||||
> .empty
|
||||
margin 0 auto
|
||||
padding 32px
|
||||
max-width 400px
|
||||
text-align center
|
||||
color #999
|
||||
|
||||
> i
|
||||
display block
|
||||
margin-bottom 16px
|
||||
font-size 3em
|
||||
color #ccc
|
||||
|
||||
> mk-timeline-post
|
||||
border-bottom solid 1px #eaeaea
|
||||
|
||||
&:last-of-type
|
||||
border-bottom none
|
||||
|
||||
> .date
|
||||
display block
|
||||
margin 0
|
||||
line-height 32px
|
||||
text-align center
|
||||
font-size 0.9em
|
||||
color #aaa
|
||||
background #fdfdfd
|
||||
border-bottom solid 1px #eaeaea
|
||||
|
||||
span
|
||||
margin 0 16px
|
||||
|
||||
i
|
||||
margin-right 8px
|
||||
|
||||
> footer
|
||||
text-align center
|
||||
border-top solid 1px #eaeaea
|
||||
border-bottom-left-radius 4px
|
||||
border-bottom-right-radius 4px
|
||||
|
||||
> button
|
||||
margin 0
|
||||
padding 16px
|
||||
width 100%
|
||||
color $theme-color
|
||||
|
||||
&:disabled
|
||||
opacity 0.7
|
||||
|
||||
script.
|
||||
@posts = []
|
||||
@init = true
|
||||
@fetching = false
|
||||
@can-fetch-more = true
|
||||
|
||||
@on \mount ~>
|
||||
@opts.init.then (posts) ~>
|
||||
@init = false
|
||||
@set-posts posts
|
||||
|
||||
@on \update ~>
|
||||
@posts.for-each (post) ~>
|
||||
date = (new Date post.created_at).get-date!
|
||||
month = (new Date post.created_at).get-month! + 1
|
||||
post._date = date
|
||||
post._datetext = month + '月 ' + date + '日'
|
||||
|
||||
@more = ~>
|
||||
if @init or @fetching or @posts.length == 0 then return
|
||||
@fetching = true
|
||||
@update!
|
||||
@opts.more!.then (posts) ~>
|
||||
@fetching = false
|
||||
@prepend-posts posts
|
||||
|
||||
@set-posts = (posts) ~>
|
||||
@posts = posts
|
||||
@update!
|
||||
|
||||
@prepend-posts = (posts) ~>
|
||||
posts.for-each (post) ~>
|
||||
@posts.push post
|
||||
@update!
|
||||
|
||||
@add-post = (post) ~>
|
||||
@posts.unshift post
|
||||
@update!
|
||||
|
||||
@tail = ~>
|
||||
@posts[@posts.length - 1]
|
98
src/web/app/mobile/tags/ui-header.tag
Normal file
98
src/web/app/mobile/tags/ui-header.tag
Normal file
@@ -0,0 +1,98 @@
|
||||
mk-ui-header
|
||||
mk-special-message
|
||||
div.main
|
||||
div.backdrop
|
||||
div.content
|
||||
button.nav#hamburger: i.fa.fa-bars
|
||||
h1@title Misskey
|
||||
button.post(onclick={ post }): i.fa.fa-pencil
|
||||
|
||||
style.
|
||||
$height = 48px
|
||||
|
||||
display block
|
||||
position fixed
|
||||
top 0
|
||||
z-index 1024
|
||||
width 100%
|
||||
box-shadow 0 1px 0 rgba(#000, 0.075)
|
||||
|
||||
> .main
|
||||
color rgba(#000, 0.6)
|
||||
|
||||
> .backdrop
|
||||
position absolute
|
||||
top 0
|
||||
z-index 1023
|
||||
width 100%
|
||||
height $height
|
||||
-webkit-backdrop-filter blur(12px)
|
||||
backdrop-filter blur(12px)
|
||||
background-color rgba(#fff, 0.75)
|
||||
|
||||
> .content
|
||||
z-index 1024
|
||||
|
||||
> h1
|
||||
display block
|
||||
margin 0 auto
|
||||
padding 0
|
||||
width 100%
|
||||
max-width calc(100% - 112px)
|
||||
text-align center
|
||||
font-size 1.1em
|
||||
font-weight normal
|
||||
line-height $height
|
||||
white-space nowrap
|
||||
overflow hidden
|
||||
text-overflow ellipsis
|
||||
|
||||
> i
|
||||
margin-right 8px
|
||||
|
||||
> img
|
||||
display inline-block
|
||||
vertical-align bottom
|
||||
width ($height - 16px)
|
||||
height ($height - 16px)
|
||||
margin 8px
|
||||
border-radius 6px
|
||||
|
||||
> .nav
|
||||
display block
|
||||
position absolute
|
||||
top 0
|
||||
left 0
|
||||
width $height
|
||||
font-size 1.4em
|
||||
line-height $height
|
||||
border-right solid 1px rgba(#000, 0.1)
|
||||
|
||||
> i
|
||||
transition all 0.2s ease
|
||||
|
||||
> .post
|
||||
display block
|
||||
position absolute
|
||||
top 0
|
||||
right 0
|
||||
width $height
|
||||
text-align center
|
||||
font-size 1.4em
|
||||
color inherit
|
||||
line-height $height
|
||||
border-left solid 1px rgba(#000, 0.1)
|
||||
|
||||
script.
|
||||
@mixin \ui
|
||||
@mixin \open-post-form
|
||||
|
||||
@on \mount ~>
|
||||
@opts.ready!
|
||||
|
||||
@ui.one \title (title) ~>
|
||||
if @refs.title?
|
||||
@refs.title.innerHTML = title
|
||||
|
||||
@post = ~>
|
||||
@open-post-form!
|
169
src/web/app/mobile/tags/ui-nav.tag
Normal file
169
src/web/app/mobile/tags/ui-nav.tag
Normal file
@@ -0,0 +1,169 @@
|
||||
mk-ui-nav
|
||||
div.body: div.content
|
||||
a.me(if={ SIGNIN }, href={ CONFIG.url + '/' + I.username })
|
||||
img.avatar(src={ I.avatar_url + '?thumbnail&size=128' }, alt='avatar')
|
||||
p.name { I.name }
|
||||
div.links
|
||||
ul
|
||||
li.post: a(href='/i/post')
|
||||
i.icon.fa.fa-pencil-square-o
|
||||
| 新規投稿
|
||||
i.angle.fa.fa-angle-right
|
||||
ul
|
||||
li.home: a(href='/')
|
||||
i.icon.fa.fa-home
|
||||
| ホーム
|
||||
i.angle.fa.fa-angle-right
|
||||
li.mentions: a(href='/i/mentions')
|
||||
i.icon.fa.fa-at
|
||||
| あなた宛て
|
||||
i.angle.fa.fa-angle-right
|
||||
li.notifications: a(href='/i/notifications')
|
||||
i.icon.fa.fa-bell-o
|
||||
| 通知
|
||||
i.angle.fa.fa-angle-right
|
||||
li.messaging: a
|
||||
i.icon.fa.fa-comments-o
|
||||
| メッセージ
|
||||
i.angle.fa.fa-angle-right
|
||||
ul
|
||||
li.settings: a(onclick={ search })
|
||||
i.icon.fa.fa-search
|
||||
| 検索
|
||||
i.angle.fa.fa-angle-right
|
||||
ul
|
||||
li.settings: a(href='/i/drive')
|
||||
i.icon.fa.fa-cloud
|
||||
| ドライブ
|
||||
i.angle.fa.fa-angle-right
|
||||
li.settings: a(href='/i/upload')
|
||||
i.icon.fa.fa-upload
|
||||
| アップロード
|
||||
i.angle.fa.fa-angle-right
|
||||
ul
|
||||
li.settings: a(href='/i/settings')
|
||||
i.icon.fa.fa-cog
|
||||
| 設定
|
||||
i.angle.fa.fa-angle-right
|
||||
p.about
|
||||
a Misskeyについて
|
||||
|
||||
style.
|
||||
display block
|
||||
position fixed
|
||||
top 0
|
||||
left 0
|
||||
z-index -1
|
||||
width 240px
|
||||
color #fff
|
||||
background #313538
|
||||
visibility hidden
|
||||
|
||||
.body
|
||||
height 100%
|
||||
overflow hidden
|
||||
|
||||
.content
|
||||
min-height 100%
|
||||
|
||||
.me
|
||||
display block
|
||||
margin 0
|
||||
padding 16px
|
||||
|
||||
.avatar
|
||||
display inline
|
||||
max-width 64px
|
||||
border-radius 32px
|
||||
vertical-align middle
|
||||
|
||||
.name
|
||||
display block
|
||||
margin 0 16px
|
||||
position absolute
|
||||
top 0
|
||||
left 80px
|
||||
padding 0
|
||||
width calc(100% - 112px)
|
||||
color #fff
|
||||
line-height 96px
|
||||
overflow hidden
|
||||
text-overflow ellipsis
|
||||
white-space nowrap
|
||||
|
||||
ul
|
||||
display block
|
||||
margin 16px 0
|
||||
padding 0
|
||||
list-style none
|
||||
|
||||
&:first-child
|
||||
margin-top 0
|
||||
|
||||
li
|
||||
display block
|
||||
font-size 1em
|
||||
line-height 1em
|
||||
border-top solid 1px rgba(0, 0, 0, 0.2)
|
||||
background #353A3E
|
||||
background-clip content-box
|
||||
|
||||
&:last-child
|
||||
border-bottom solid 1px rgba(0, 0, 0, 0.2)
|
||||
|
||||
a
|
||||
display block
|
||||
padding 0 20px
|
||||
line-height 3rem
|
||||
line-height calc(1rem + 30px)
|
||||
color #eee
|
||||
text-decoration none
|
||||
|
||||
> .icon
|
||||
margin-right 0.5em
|
||||
|
||||
> .angle
|
||||
position absolute
|
||||
top 0
|
||||
right 0
|
||||
padding 0 20px
|
||||
font-size 1.2em
|
||||
line-height calc(1rem + 30px)
|
||||
color #ccc
|
||||
|
||||
> .unread-count
|
||||
position absolute
|
||||
height calc(0.9em + 10px)
|
||||
line-height calc(0.9em + 10px)
|
||||
top 0
|
||||
bottom 0
|
||||
right 38px
|
||||
margin auto 0
|
||||
padding 0px 8px
|
||||
min-width 2em
|
||||
font-size 0.9em
|
||||
text-align center
|
||||
color #fff
|
||||
background rgba(255, 255, 255, 0.1)
|
||||
border-radius 1em
|
||||
|
||||
.about
|
||||
margin 1em 1em 2em 1em
|
||||
text-align center
|
||||
font-size 0.6em
|
||||
opacity 0.3
|
||||
|
||||
a
|
||||
color #fff
|
||||
|
||||
script.
|
||||
@mixin \i
|
||||
@mixin \page
|
||||
|
||||
@on \mount ~>
|
||||
@opts.ready!
|
||||
|
||||
@search = ~>
|
||||
query = window.prompt \検索
|
||||
if query? and query != ''
|
||||
@page '/search:' + query
|
50
src/web/app/mobile/tags/ui.tag
Normal file
50
src/web/app/mobile/tags/ui.tag
Normal file
@@ -0,0 +1,50 @@
|
||||
mk-ui
|
||||
div.global@global
|
||||
mk-ui-header@header(ready={ ready })
|
||||
mk-ui-nav@nav(ready={ ready })
|
||||
|
||||
div.content@main
|
||||
<yield />
|
||||
|
||||
mk-stream-indicator
|
||||
|
||||
style.
|
||||
display block
|
||||
|
||||
> .global
|
||||
> .content
|
||||
background #fff
|
||||
|
||||
script.
|
||||
@mixin \stream
|
||||
|
||||
@ready-count = 0
|
||||
|
||||
#@ui.on \notification (text) ~>
|
||||
# alert text
|
||||
|
||||
@on \mount ~>
|
||||
@stream.on \notification @on-stream-notification
|
||||
@ready!
|
||||
|
||||
@on \unmount ~>
|
||||
@stream.off \notification @on-stream-notification
|
||||
@slide.slide-close!
|
||||
|
||||
@ready = ~>
|
||||
@ready-count++
|
||||
|
||||
if @ready-count == 2
|
||||
@slide = SpSlidemenu @refs.main, @refs.nav.root, \#hamburger {direction: \left}
|
||||
@init-view-position!
|
||||
|
||||
@init-view-position = ~>
|
||||
top = @refs.header.root.offset-height
|
||||
@refs.main.style.padding-top = top + \px
|
||||
@refs.nav.root.style.margin-top = top + \px
|
||||
@refs.nav.root.query-selector '.body > .content' .style.padding-bottom = top + \px
|
||||
|
||||
@on-stream-notification = (notification) ~>
|
||||
el = document.body.append-child document.create-element \mk-notify
|
||||
riot.mount el, do
|
||||
notification: notification
|
22
src/web/app/mobile/tags/user-followers.tag
Normal file
22
src/web/app/mobile/tags/user-followers.tag
Normal file
@@ -0,0 +1,22 @@
|
||||
mk-user-followers
|
||||
mk-users-list@list(fetch={ fetch }, count={ user.followers_count }, you-know-count={ user.followers_you_know_count }, no-users={ 'フォロワーはいないようです。' })
|
||||
|
||||
style.
|
||||
display block
|
||||
|
||||
script.
|
||||
@mixin \api
|
||||
|
||||
@user = @opts.user
|
||||
|
||||
@fetch = (iknow, limit, cursor, cb) ~>
|
||||
@api \users/followers do
|
||||
user_id: @user.id
|
||||
iknow: iknow
|
||||
limit: limit
|
||||
cursor: if cursor? then cursor else undefined
|
||||
.then cb
|
||||
|
||||
@on \mount ~>
|
||||
@refs.list.on \loaded ~>
|
||||
@trigger \loaded
|
22
src/web/app/mobile/tags/user-following.tag
Normal file
22
src/web/app/mobile/tags/user-following.tag
Normal file
@@ -0,0 +1,22 @@
|
||||
mk-user-following
|
||||
mk-users-list@list(fetch={ fetch }, count={ user.following_count }, you-know-count={ user.following_you_know_count }, no-users={ 'フォロー中のユーザーはいないようです。' })
|
||||
|
||||
style.
|
||||
display block
|
||||
|
||||
script.
|
||||
@mixin \api
|
||||
|
||||
@user = @opts.user
|
||||
|
||||
@fetch = (iknow, limit, cursor, cb) ~>
|
||||
@api \users/following do
|
||||
user_id: @user.id
|
||||
iknow: iknow
|
||||
limit: limit
|
||||
cursor: if cursor? then cursor else undefined
|
||||
.then cb
|
||||
|
||||
@on \mount ~>
|
||||
@refs.list.on \loaded ~>
|
||||
@trigger \loaded
|
103
src/web/app/mobile/tags/user-preview.tag
Normal file
103
src/web/app/mobile/tags/user-preview.tag
Normal file
@@ -0,0 +1,103 @@
|
||||
mk-user-preview
|
||||
a.avatar-anchor(href={ CONFIG.url + '/' + user.username })
|
||||
img.avatar(src={ user.avatar_url + '?thumbnail&size=64' }, alt='avatar')
|
||||
div.main
|
||||
header
|
||||
div.left
|
||||
a.name(href={ CONFIG.url + '/' + user.username })
|
||||
| { user.name }
|
||||
span.username
|
||||
| @{ user.username }
|
||||
div.body
|
||||
div.bio { user.bio }
|
||||
|
||||
style.
|
||||
display block
|
||||
margin 0
|
||||
padding 16px
|
||||
font-size 12px
|
||||
|
||||
@media (min-width 350px)
|
||||
font-size 14px
|
||||
|
||||
@media (min-width 500px)
|
||||
font-size 16px
|
||||
|
||||
&:after
|
||||
content ""
|
||||
display block
|
||||
clear both
|
||||
|
||||
> .avatar-anchor
|
||||
display block
|
||||
float left
|
||||
margin 0 10px 0 0
|
||||
|
||||
@media (min-width 500px)
|
||||
margin-right 16px
|
||||
|
||||
> .avatar
|
||||
display block
|
||||
width 48px
|
||||
height 48px
|
||||
margin 0
|
||||
border-radius 6px
|
||||
vertical-align bottom
|
||||
|
||||
@media (min-width 500px)
|
||||
width 58px
|
||||
height 58px
|
||||
border-radius 8px
|
||||
|
||||
> .main
|
||||
float left
|
||||
width calc(100% - 58px)
|
||||
|
||||
@media (min-width 500px)
|
||||
width calc(100% - 74px)
|
||||
|
||||
> header
|
||||
white-space nowrap
|
||||
|
||||
@media (min-width 500px)
|
||||
margin-bottom 2px
|
||||
|
||||
&:after
|
||||
content ""
|
||||
display block
|
||||
clear both
|
||||
|
||||
> .left
|
||||
float left
|
||||
|
||||
> .name
|
||||
display inline
|
||||
margin 0
|
||||
padding 0
|
||||
color #777
|
||||
font-size 1em
|
||||
font-weight 700
|
||||
text-align left
|
||||
text-decoration none
|
||||
|
||||
&:hover
|
||||
text-decoration underline
|
||||
|
||||
> .username
|
||||
text-align left
|
||||
margin 0 0 0 8px
|
||||
color #ccc
|
||||
|
||||
> .body
|
||||
|
||||
> .bio
|
||||
cursor default
|
||||
display block
|
||||
margin 0
|
||||
padding 0
|
||||
word-wrap break-word
|
||||
font-size 1.1em
|
||||
color #717171
|
||||
|
||||
script.
|
||||
@user = @opts.user
|
28
src/web/app/mobile/tags/user-timeline.tag
Normal file
28
src/web/app/mobile/tags/user-timeline.tag
Normal file
@@ -0,0 +1,28 @@
|
||||
mk-user-timeline
|
||||
mk-timeline(init={ init }, more={ more }, empty={ with-media ? 'メディア付き投稿はありません。' : 'このユーザーはまだ投稿していないようです。' })
|
||||
|
||||
style.
|
||||
display block
|
||||
max-width 600px
|
||||
margin 0 auto
|
||||
background #fff
|
||||
|
||||
script.
|
||||
@mixin \api
|
||||
|
||||
@user = @opts.user
|
||||
@with-media = @opts.with-media
|
||||
|
||||
@init = new Promise (res, rej) ~>
|
||||
@api \users/posts do
|
||||
user_id: @user.id
|
||||
with_media: @with-media
|
||||
.then (posts) ~>
|
||||
res posts
|
||||
@trigger \loaded
|
||||
|
||||
@more = ~>
|
||||
@api \users/posts do
|
||||
user_id: @user.id
|
||||
with_media: @with-media
|
||||
max_id: @refs.timeline.tail!.id
|
198
src/web/app/mobile/tags/user.tag
Normal file
198
src/web/app/mobile/tags/user.tag
Normal file
@@ -0,0 +1,198 @@
|
||||
mk-user
|
||||
div.user(if={ !fetching })
|
||||
header
|
||||
div.banner(style={ user.banner_url ? 'background-image: url(' + user.banner_url + '?thumbnail&size=1024)' : '' })
|
||||
div.body
|
||||
div.top
|
||||
a.avatar: img(src={ user.avatar_url + '?thumbnail&size=160' }, alt='avatar')
|
||||
mk-follow-button(if={ SIGNIN && I.id != user.id }, user={ user })
|
||||
|
||||
div.title
|
||||
h1 { user.name }
|
||||
span.username @{ user.username }
|
||||
span.followed(if={ user.is_followed }) フォローされています
|
||||
|
||||
div.bio { user.bio }
|
||||
|
||||
div.info
|
||||
p.location(if={ user.location })
|
||||
i.fa.fa-map-marker
|
||||
| { user.location }
|
||||
|
||||
div.friends
|
||||
a(href='{ user.username }/following')
|
||||
b { user.following_count }
|
||||
i フォロー
|
||||
a(href='{ user.username }/followers')
|
||||
b { user.followers_count }
|
||||
i フォロワー
|
||||
nav
|
||||
a(data-is-active={ page == 'posts' }, onclick={ go-posts }) 投稿
|
||||
a(data-is-active={ page == 'media' }, onclick={ go-media }) メディア
|
||||
a(data-is-active={ page == 'graphs' }, onclick={ go-graphs }) グラフ
|
||||
a(data-is-active={ page == 'likes' }, onclick={ go-likes }) いいね
|
||||
|
||||
div.body
|
||||
mk-user-timeline(if={ page == 'posts' }, user={ user })
|
||||
mk-user-timeline(if={ page == 'media' }, user={ user }, with-media={ true })
|
||||
mk-user-graphs(if={ page == 'graphs' }, user={ user })
|
||||
|
||||
style.
|
||||
display block
|
||||
|
||||
> .user
|
||||
> header
|
||||
> .banner
|
||||
padding-bottom 33.3%
|
||||
background-color #f5f5f5
|
||||
background-size cover
|
||||
background-position center
|
||||
|
||||
> .body
|
||||
padding 8px
|
||||
margin 0 auto
|
||||
max-width 600px
|
||||
|
||||
> .top
|
||||
&:after
|
||||
content ''
|
||||
display block
|
||||
clear both
|
||||
|
||||
> .avatar
|
||||
display block
|
||||
float left
|
||||
width 25%
|
||||
height 40px
|
||||
|
||||
> img
|
||||
display block
|
||||
position absolute
|
||||
left -2px
|
||||
bottom -2px
|
||||
width 100%
|
||||
border 2px solid #fff
|
||||
border-radius 6px
|
||||
|
||||
@media (min-width 500px)
|
||||
left -4px
|
||||
bottom -4px
|
||||
border 4px solid #fff
|
||||
border-radius 12px
|
||||
|
||||
> mk-follow-button
|
||||
float right
|
||||
height 40px
|
||||
|
||||
> .title
|
||||
margin 8px 0
|
||||
|
||||
> h1
|
||||
margin 0
|
||||
line-height 22px
|
||||
font-size 20px
|
||||
color #222
|
||||
|
||||
> .username
|
||||
display inline-block
|
||||
line-height 20px
|
||||
font-size 16px
|
||||
font-weight bold
|
||||
color #657786
|
||||
|
||||
> .followed
|
||||
margin-left 8px
|
||||
padding 2px 4px
|
||||
font-size 12px
|
||||
color #657786
|
||||
background #f8f8f8
|
||||
border-radius 4px
|
||||
|
||||
> .bio
|
||||
margin 8px 0
|
||||
color #333
|
||||
|
||||
> .info
|
||||
margin 8px 0
|
||||
|
||||
> .location
|
||||
display inline
|
||||
margin 0
|
||||
color #555
|
||||
|
||||
> i
|
||||
margin-right 4px
|
||||
|
||||
> .friends
|
||||
> a
|
||||
color #657786
|
||||
|
||||
&:first-child
|
||||
margin-right 16px
|
||||
|
||||
> b
|
||||
margin-right 4px
|
||||
font-size 16px
|
||||
color #14171a
|
||||
|
||||
> i
|
||||
font-size 14px
|
||||
|
||||
> nav
|
||||
display flex
|
||||
justify-content center
|
||||
margin 0 auto
|
||||
max-width 600px
|
||||
border-bottom solid 1px #ddd
|
||||
|
||||
> a
|
||||
display block
|
||||
flex 1 1
|
||||
text-align center
|
||||
line-height 52px
|
||||
font-size 14px
|
||||
text-decoration none
|
||||
color #657786
|
||||
border-bottom solid 2px transparent
|
||||
|
||||
&[data-is-active]
|
||||
font-weight bold
|
||||
color $theme-color
|
||||
border-color $theme-color
|
||||
|
||||
> .body
|
||||
@media (min-width 500px)
|
||||
padding 16px 0 0 0
|
||||
|
||||
script.
|
||||
@mixin \i
|
||||
@mixin \api
|
||||
|
||||
@username = @opts.user
|
||||
@page = if @opts.page? then @opts.page else \posts
|
||||
@fetching = true
|
||||
|
||||
@on \mount ~>
|
||||
@api \users/show do
|
||||
username: @username
|
||||
.then (user) ~>
|
||||
@fetching = false
|
||||
@user = user
|
||||
@trigger \loaded user
|
||||
@update!
|
||||
|
||||
@go-posts = ~>
|
||||
@page = \posts
|
||||
@update!
|
||||
|
||||
@go-media = ~>
|
||||
@page = \media
|
||||
@update!
|
||||
|
||||
@go-graphs = ~>
|
||||
@page = \graphs
|
||||
@update!
|
||||
|
||||
@go-likes = ~>
|
||||
@page = \likes
|
||||
@update!
|
125
src/web/app/mobile/tags/users-list.tag
Normal file
125
src/web/app/mobile/tags/users-list.tag
Normal file
@@ -0,0 +1,125 @@
|
||||
mk-users-list
|
||||
nav
|
||||
span(data-is-active={ mode == 'all' }, onclick={ set-mode.bind(this, 'all') })
|
||||
| すべて
|
||||
span { opts.count }
|
||||
// ↓ https://github.com/riot/riot/issues/2080
|
||||
span(if={ SIGNIN && opts.you-know-count != '' }, data-is-active={ mode == 'iknow' }, onclick={ set-mode.bind(this, 'iknow') })
|
||||
| 知り合い
|
||||
span { opts.you-know-count }
|
||||
|
||||
div.users(if={ !fetching && users.length != 0 })
|
||||
mk-user-preview(each={ users }, user={ this })
|
||||
|
||||
button.more(if={ !fetching && next != null }, onclick={ more }, disabled={ more-fetching })
|
||||
span(if={ !more-fetching }) もっと
|
||||
span(if={ more-fetching })
|
||||
| 読み込み中
|
||||
mk-ellipsis
|
||||
|
||||
p.no(if={ !fetching && users.length == 0 })
|
||||
| { opts.no-users }
|
||||
p.fetching(if={ fetching })
|
||||
i.fa.fa-spinner.fa-pulse.fa-fw
|
||||
| 読み込んでいます
|
||||
mk-ellipsis
|
||||
|
||||
style.
|
||||
display block
|
||||
background #fff
|
||||
|
||||
> nav
|
||||
display flex
|
||||
justify-content center
|
||||
margin 0 auto
|
||||
max-width 600px
|
||||
border-bottom solid 1px #ddd
|
||||
|
||||
> span
|
||||
display block
|
||||
flex 1 1
|
||||
text-align center
|
||||
line-height 52px
|
||||
font-size 14px
|
||||
color #657786
|
||||
border-bottom solid 2px transparent
|
||||
|
||||
&[data-is-active]
|
||||
font-weight bold
|
||||
color $theme-color
|
||||
border-color $theme-color
|
||||
|
||||
> span
|
||||
display inline-block
|
||||
margin-left 4px
|
||||
padding 2px 5px
|
||||
font-size 12px
|
||||
line-height 1
|
||||
color #888
|
||||
background #eee
|
||||
border-radius 20px
|
||||
|
||||
> .users
|
||||
> *
|
||||
max-width 600px
|
||||
margin 0 auto
|
||||
border-bottom solid 1px rgba(0, 0, 0, 0.05)
|
||||
|
||||
> .no
|
||||
margin 0
|
||||
padding 16px
|
||||
text-align center
|
||||
color #aaa
|
||||
|
||||
> .fetching
|
||||
margin 0
|
||||
padding 16px
|
||||
text-align center
|
||||
color #aaa
|
||||
|
||||
> i
|
||||
margin-right 4px
|
||||
|
||||
script.
|
||||
@mixin \i
|
||||
|
||||
@limit = 30users
|
||||
@mode = \all
|
||||
|
||||
@fetching = true
|
||||
@more-fetching = false
|
||||
|
||||
@on \mount ~>
|
||||
@fetch ~>
|
||||
@trigger \loaded
|
||||
|
||||
@fetch = (cb) ~>
|
||||
@fetching = true
|
||||
@update!
|
||||
obj <~ @opts.fetch do
|
||||
@mode == \iknow
|
||||
@limit
|
||||
null
|
||||
@users = obj.users
|
||||
@next = obj.next
|
||||
@fetching = false
|
||||
@update!
|
||||
if cb? then cb!
|
||||
|
||||
@more = ~>
|
||||
@more-fetching = true
|
||||
@update!
|
||||
obj <~ @opts.fetch do
|
||||
@mode == \iknow
|
||||
@limit
|
||||
@cursor
|
||||
@users = @users.concat obj.users
|
||||
@next = obj.next
|
||||
@more-fetching = false
|
||||
@update!
|
||||
|
||||
@set-mode = (mode) ~>
|
||||
@update do
|
||||
mode: mode
|
||||
|
||||
@fetch!
|
Reference in New Issue
Block a user