forked from FoundKeyGang/FoundKey
wip
This commit is contained in:
parent
b2a6257f93
commit
a1e57841e7
22 changed files with 304 additions and 372 deletions
|
@ -13,6 +13,7 @@
|
||||||
"vue/html-self-closing": false,
|
"vue/html-self-closing": false,
|
||||||
"vue/no-unused-vars": false,
|
"vue/no-unused-vars": false,
|
||||||
"no-console": 0,
|
"no-console": 0,
|
||||||
"no-unused-vars": 0
|
"no-unused-vars": 0,
|
||||||
|
"no-empty": 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,38 @@ declare const _API_URL_: string;
|
||||||
declare const _SW_PUBLICKEY_: string;
|
declare const _SW_PUBLICKEY_: string;
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
|
export type API = {
|
||||||
|
chooseDriveFile: (opts: {
|
||||||
|
title?: string;
|
||||||
|
currentFolder?: any;
|
||||||
|
multiple?: boolean;
|
||||||
|
}) => Promise<any>;
|
||||||
|
|
||||||
|
chooseDriveFolder: (opts: {
|
||||||
|
title?: string;
|
||||||
|
currentFolder?: any;
|
||||||
|
}) => Promise<any>;
|
||||||
|
|
||||||
|
dialog: (opts: {
|
||||||
|
title: string;
|
||||||
|
text: string;
|
||||||
|
actions: Array<{
|
||||||
|
text: string;
|
||||||
|
id?: string;
|
||||||
|
}>;
|
||||||
|
}) => Promise<string>;
|
||||||
|
|
||||||
|
input: (opts: {
|
||||||
|
title: string;
|
||||||
|
placeholder?: string;
|
||||||
|
default?: string;
|
||||||
|
}) => Promise<string>;
|
||||||
|
|
||||||
|
post: () => void;
|
||||||
|
|
||||||
|
notify: (message: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Misskey Operating System
|
* Misskey Operating System
|
||||||
*/
|
*/
|
||||||
|
@ -49,6 +81,8 @@ export default class MiOS extends EventEmitter {
|
||||||
return localStorage.getItem('debug') == 'true';
|
return localStorage.getItem('debug') == 'true';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public apis: API;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A connection manager of home stream
|
* A connection manager of home stream
|
||||||
*/
|
*/
|
||||||
|
|
21
src/web/app/common/scripts/fuck-ad-block.ts
Normal file
21
src/web/app/common/scripts/fuck-ad-block.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
require('fuckadblock');
|
||||||
|
|
||||||
|
declare const fuckAdBlock: any;
|
||||||
|
|
||||||
|
export default (os) => {
|
||||||
|
function adBlockDetected() {
|
||||||
|
os.apis.dialog({
|
||||||
|
title: '%fa:exclamation-triangle%広告ブロッカーを無効にしてください',
|
||||||
|
text: '<strong>Misskeyは広告を掲載していません</strong>が、広告をブロックする機能が有効だと一部の機能が利用できなかったり、不具合が発生する場合があります。',
|
||||||
|
actins: [{
|
||||||
|
text: 'OK'
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fuckAdBlock === undefined) {
|
||||||
|
adBlockDetected();
|
||||||
|
} else {
|
||||||
|
fuckAdBlock.onDetected(adBlockDetected);
|
||||||
|
}
|
||||||
|
};
|
10
src/web/app/desktop/api/notify.ts
Normal file
10
src/web/app/desktop/api/notify.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import Notification from '../views/components/ui-notification.vue';
|
||||||
|
|
||||||
|
export default function(message) {
|
||||||
|
const vm = new Notification({
|
||||||
|
propsData: {
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}).$mount();
|
||||||
|
document.body.appendChild(vm.$el);
|
||||||
|
}
|
95
src/web/app/desktop/api/update-avatar.ts
Normal file
95
src/web/app/desktop/api/update-avatar.ts
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
import OS from '../../common/mios';
|
||||||
|
import { apiUrl } from '../../config';
|
||||||
|
import CropWindow from '../views/components/crop-window.vue';
|
||||||
|
import ProgressDialog from '../views/components/progress-dialog.vue';
|
||||||
|
|
||||||
|
export default (os: OS) => (cb, file = null) => {
|
||||||
|
const fileSelected = file => {
|
||||||
|
|
||||||
|
const w = new CropWindow({
|
||||||
|
propsData: {
|
||||||
|
file: file,
|
||||||
|
title: 'アバターとして表示する部分を選択',
|
||||||
|
aspectRatio: 1 / 1
|
||||||
|
}
|
||||||
|
}).$mount();
|
||||||
|
|
||||||
|
w.$once('cropped', blob => {
|
||||||
|
const data = new FormData();
|
||||||
|
data.append('i', os.i.token);
|
||||||
|
data.append('file', blob, file.name + '.cropped.png');
|
||||||
|
|
||||||
|
os.api('drive/folders/find', {
|
||||||
|
name: 'アイコン'
|
||||||
|
}).then(iconFolder => {
|
||||||
|
if (iconFolder.length === 0) {
|
||||||
|
os.api('drive/folders/create', {
|
||||||
|
name: 'アイコン'
|
||||||
|
}).then(iconFolder => {
|
||||||
|
upload(data, iconFolder);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
upload(data, iconFolder[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
w.$once('skipped', () => {
|
||||||
|
set(file);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.appendChild(w.$el);
|
||||||
|
};
|
||||||
|
|
||||||
|
const upload = (data, folder) => {
|
||||||
|
const dialog = new ProgressDialog({
|
||||||
|
propsData: {
|
||||||
|
title: '新しいアバターをアップロードしています'
|
||||||
|
}
|
||||||
|
}).$mount();
|
||||||
|
document.body.appendChild(dialog.$el);
|
||||||
|
|
||||||
|
if (folder) data.append('folder_id', folder.id);
|
||||||
|
|
||||||
|
const xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('POST', apiUrl + '/drive/files/create', true);
|
||||||
|
xhr.onload = e => {
|
||||||
|
const file = JSON.parse((e.target as any).response);
|
||||||
|
(dialog as any).close();
|
||||||
|
set(file);
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.upload.onprogress = e => {
|
||||||
|
if (e.lengthComputable) (dialog as any).updateProgress(e.loaded, e.total);
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.send(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
const set = file => {
|
||||||
|
os.api('i/update', {
|
||||||
|
avatar_id: file.id
|
||||||
|
}).then(i => {
|
||||||
|
os.apis.dialog({
|
||||||
|
title: '%fa:info-circle%アバターを更新しました',
|
||||||
|
text: '新しいアバターが反映されるまで時間がかかる場合があります。',
|
||||||
|
actions: [{
|
||||||
|
text: 'わかった'
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
if (cb) cb(i);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
fileSelected(file);
|
||||||
|
} else {
|
||||||
|
os.apis.chooseDriveFile({
|
||||||
|
multiple: false,
|
||||||
|
title: '%fa:image%アバターにする画像を選択'
|
||||||
|
}).then(file => {
|
||||||
|
fileSelected(file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
95
src/web/app/desktop/api/update-banner.ts
Normal file
95
src/web/app/desktop/api/update-banner.ts
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
import OS from '../../common/mios';
|
||||||
|
import { apiUrl } from '../../config';
|
||||||
|
import CropWindow from '../views/components/crop-window.vue';
|
||||||
|
import ProgressDialog from '../views/components/progress-dialog.vue';
|
||||||
|
|
||||||
|
export default (os: OS) => (cb, file = null) => {
|
||||||
|
const fileSelected = file => {
|
||||||
|
|
||||||
|
const w = new CropWindow({
|
||||||
|
propsData: {
|
||||||
|
file: file,
|
||||||
|
title: 'バナーとして表示する部分を選択',
|
||||||
|
aspectRatio: 16 / 9
|
||||||
|
}
|
||||||
|
}).$mount();
|
||||||
|
|
||||||
|
w.$once('cropped', blob => {
|
||||||
|
const data = new FormData();
|
||||||
|
data.append('i', os.i.token);
|
||||||
|
data.append('file', blob, file.name + '.cropped.png');
|
||||||
|
|
||||||
|
os.api('drive/folders/find', {
|
||||||
|
name: 'バナー'
|
||||||
|
}).then(bannerFolder => {
|
||||||
|
if (bannerFolder.length === 0) {
|
||||||
|
os.api('drive/folders/create', {
|
||||||
|
name: 'バナー'
|
||||||
|
}).then(iconFolder => {
|
||||||
|
upload(data, iconFolder);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
upload(data, bannerFolder[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
w.$once('skipped', () => {
|
||||||
|
set(file);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.appendChild(w.$el);
|
||||||
|
};
|
||||||
|
|
||||||
|
const upload = (data, folder) => {
|
||||||
|
const dialog = new ProgressDialog({
|
||||||
|
propsData: {
|
||||||
|
title: '新しいバナーをアップロードしています'
|
||||||
|
}
|
||||||
|
}).$mount();
|
||||||
|
document.body.appendChild(dialog.$el);
|
||||||
|
|
||||||
|
if (folder) data.append('folder_id', folder.id);
|
||||||
|
|
||||||
|
const xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('POST', apiUrl + '/drive/files/create', true);
|
||||||
|
xhr.onload = e => {
|
||||||
|
const file = JSON.parse((e.target as any).response);
|
||||||
|
(dialog as any).close();
|
||||||
|
set(file);
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.upload.onprogress = e => {
|
||||||
|
if (e.lengthComputable) (dialog as any).updateProgress(e.loaded, e.total);
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.send(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
const set = file => {
|
||||||
|
os.api('i/update', {
|
||||||
|
avatar_id: file.id
|
||||||
|
}).then(i => {
|
||||||
|
os.apis.dialog({
|
||||||
|
title: '%fa:info-circle%バナーを更新しました',
|
||||||
|
text: '新しいバナーが反映されるまで時間がかかる場合があります。',
|
||||||
|
actions: [{
|
||||||
|
text: 'わかった'
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
if (cb) cb(i);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
fileSelected(file);
|
||||||
|
} else {
|
||||||
|
os.apis.chooseDriveFile({
|
||||||
|
multiple: false,
|
||||||
|
title: '%fa:image%バナーにする画像を選択'
|
||||||
|
}).then(file => {
|
||||||
|
fileSelected(file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
|
@ -6,7 +6,7 @@
|
||||||
import './style.styl';
|
import './style.styl';
|
||||||
|
|
||||||
import init from '../init';
|
import init from '../init';
|
||||||
import fuckAdBlock from './scripts/fuck-ad-block';
|
import fuckAdBlock from '../common/scripts/fuck-ad-block';
|
||||||
import HomeStreamManager from '../common/scripts/streaming/home-stream-manager';
|
import HomeStreamManager from '../common/scripts/streaming/home-stream-manager';
|
||||||
import composeNotification from '../common/scripts/compose-notification';
|
import composeNotification from '../common/scripts/compose-notification';
|
||||||
|
|
||||||
|
@ -15,6 +15,9 @@ import chooseDriveFile from './api/choose-drive-file';
|
||||||
import dialog from './api/dialog';
|
import dialog from './api/dialog';
|
||||||
import input from './api/input';
|
import input from './api/input';
|
||||||
import post from './api/post';
|
import post from './api/post';
|
||||||
|
import notify from './api/notify';
|
||||||
|
import updateAvatar from './api/update-avatar';
|
||||||
|
import updateBanner from './api/update-banner';
|
||||||
|
|
||||||
import MkIndex from './views/pages/index.vue';
|
import MkIndex from './views/pages/index.vue';
|
||||||
import MkUser from './views/pages/user/user.vue';
|
import MkUser from './views/pages/user/user.vue';
|
||||||
|
@ -25,24 +28,27 @@ import MkDrive from './views/pages/drive.vue';
|
||||||
* init
|
* init
|
||||||
*/
|
*/
|
||||||
init(async (launch) => {
|
init(async (launch) => {
|
||||||
/**
|
|
||||||
* Fuck AD Block
|
|
||||||
*/
|
|
||||||
fuckAdBlock();
|
|
||||||
|
|
||||||
// Register directives
|
// Register directives
|
||||||
require('./views/directives');
|
require('./views/directives');
|
||||||
|
|
||||||
// Register components
|
// Register components
|
||||||
require('./views/components');
|
require('./views/components');
|
||||||
|
|
||||||
const app = launch({
|
const [app, os] = launch(os => ({
|
||||||
chooseDriveFolder,
|
chooseDriveFolder,
|
||||||
chooseDriveFile,
|
chooseDriveFile,
|
||||||
dialog,
|
dialog,
|
||||||
input,
|
input,
|
||||||
post
|
post,
|
||||||
});
|
notify,
|
||||||
|
updateAvatar: updateAvatar(os),
|
||||||
|
updateBanner: updateBanner(os)
|
||||||
|
}));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fuck AD Block
|
||||||
|
*/
|
||||||
|
fuckAdBlock(os);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Init Notification
|
* Init Notification
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
import * as riot from 'riot';
|
|
||||||
|
|
||||||
export default (title, text, buttons, canThrough?, onThrough?) => {
|
|
||||||
const dialog = document.body.appendChild(document.createElement('mk-dialog'));
|
|
||||||
const controller = riot.observable();
|
|
||||||
(riot as any).mount(dialog, {
|
|
||||||
controller: controller,
|
|
||||||
title: title,
|
|
||||||
text: text,
|
|
||||||
buttons: buttons,
|
|
||||||
canThrough: canThrough,
|
|
||||||
onThrough: onThrough
|
|
||||||
});
|
|
||||||
controller.trigger('open');
|
|
||||||
return controller;
|
|
||||||
};
|
|
|
@ -1,20 +0,0 @@
|
||||||
require('fuckadblock');
|
|
||||||
import dialog from './dialog';
|
|
||||||
|
|
||||||
declare const fuckAdBlock: any;
|
|
||||||
|
|
||||||
export default () => {
|
|
||||||
if (fuckAdBlock === undefined) {
|
|
||||||
adBlockDetected();
|
|
||||||
} else {
|
|
||||||
fuckAdBlock.onDetected(adBlockDetected);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function adBlockDetected() {
|
|
||||||
dialog('%fa:exclamation-triangle%広告ブロッカーを無効にしてください',
|
|
||||||
'<strong>Misskeyは広告を掲載していません</strong>が、広告をブロックする機能が有効だと一部の機能が利用できなかったり、不具合が発生する場合があります。',
|
|
||||||
[{
|
|
||||||
text: 'OK'
|
|
||||||
}]);
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
import * as riot from 'riot';
|
|
||||||
|
|
||||||
export default (title, placeholder, defaultValue, onOk, onCancel) => {
|
|
||||||
const dialog = document.body.appendChild(document.createElement('mk-input-dialog'));
|
|
||||||
return (riot as any).mount(dialog, {
|
|
||||||
title: title,
|
|
||||||
placeholder: placeholder,
|
|
||||||
'default': defaultValue,
|
|
||||||
onOk: onOk,
|
|
||||||
onCancel: onCancel
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -1,8 +0,0 @@
|
||||||
import dialog from './dialog';
|
|
||||||
|
|
||||||
export default () => {
|
|
||||||
dialog('%fa:exclamation-triangle%Not implemented yet',
|
|
||||||
'要求された操作は実装されていません。<br>→<a href="https://github.com/syuilo/misskey" target="_blank">Misskeyの開発に参加する</a>', [{
|
|
||||||
text: 'OK'
|
|
||||||
}]);
|
|
||||||
};
|
|
|
@ -1,8 +0,0 @@
|
||||||
import * as riot from 'riot';
|
|
||||||
|
|
||||||
export default message => {
|
|
||||||
const notification = document.body.appendChild(document.createElement('mk-ui-notification'));
|
|
||||||
(riot as any).mount(notification, {
|
|
||||||
message: message
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -1,61 +0,0 @@
|
||||||
/**
|
|
||||||
* 要素をスクロールに追従させる
|
|
||||||
*/
|
|
||||||
export default class ScrollFollower {
|
|
||||||
private follower: Element;
|
|
||||||
private containerTop: number;
|
|
||||||
private topPadding: number;
|
|
||||||
|
|
||||||
constructor(follower: Element, topPadding: number) {
|
|
||||||
//#region
|
|
||||||
this.follow = this.follow.bind(this);
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
this.follower = follower;
|
|
||||||
this.containerTop = follower.getBoundingClientRect().top;
|
|
||||||
this.topPadding = topPadding;
|
|
||||||
|
|
||||||
window.addEventListener('scroll', this.follow);
|
|
||||||
window.addEventListener('resize', this.follow);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 追従解除
|
|
||||||
*/
|
|
||||||
public dispose() {
|
|
||||||
window.removeEventListener('scroll', this.follow);
|
|
||||||
window.removeEventListener('resize', this.follow);
|
|
||||||
}
|
|
||||||
|
|
||||||
private follow() {
|
|
||||||
const windowBottom = window.scrollY + window.innerHeight;
|
|
||||||
const windowTop = window.scrollY + this.topPadding;
|
|
||||||
|
|
||||||
const rect = this.follower.getBoundingClientRect();
|
|
||||||
const followerBottom = (rect.top + window.scrollY) + rect.height;
|
|
||||||
const screenHeight = window.innerHeight - this.topPadding;
|
|
||||||
|
|
||||||
// スクロールの上部(+余白)がフォロワーコンテナの上部よりも上方にある
|
|
||||||
if (window.scrollY + this.topPadding < this.containerTop) {
|
|
||||||
// フォロワーをコンテナの最上部に合わせる
|
|
||||||
(this.follower.parentNode as any).style.marginTop = '0px';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// スクロールの下部がフォロワーの下部よりも下方にある かつ 表示領域の縦幅がフォロワーの縦幅よりも狭い
|
|
||||||
if (windowBottom > followerBottom && rect.height > screenHeight) {
|
|
||||||
// フォロワーの下部をスクロール下部に合わせる
|
|
||||||
const top = (windowBottom - rect.height) - this.containerTop;
|
|
||||||
(this.follower.parentNode as any).style.marginTop = `${top}px`;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// スクロールの上部(+余白)がフォロワーの上部よりも上方にある または 表示領域の縦幅がフォロワーの縦幅よりも広い
|
|
||||||
if (windowTop < rect.top + window.scrollY || rect.height < screenHeight) {
|
|
||||||
// フォロワーの上部をスクロール上部(+余白)に合わせる
|
|
||||||
const top = windowTop - this.containerTop;
|
|
||||||
(this.follower.parentNode as any).style.marginTop = `${top}px`;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,88 +0,0 @@
|
||||||
declare const _API_URL_: string;
|
|
||||||
|
|
||||||
import * as riot from 'riot';
|
|
||||||
import dialog from './dialog';
|
|
||||||
import api from '../../common/scripts/api';
|
|
||||||
|
|
||||||
export default (I, cb, file = null) => {
|
|
||||||
const fileSelected = file => {
|
|
||||||
const cropper = (riot as any).mount(document.body.appendChild(document.createElement('mk-crop-window')), {
|
|
||||||
file: file,
|
|
||||||
title: 'アバターとして表示する部分を選択',
|
|
||||||
aspectRatio: 1 / 1
|
|
||||||
})[0];
|
|
||||||
|
|
||||||
cropper.on('cropped', blob => {
|
|
||||||
const data = new FormData();
|
|
||||||
data.append('i', I.token);
|
|
||||||
data.append('file', blob, file.name + '.cropped.png');
|
|
||||||
|
|
||||||
api(I, 'drive/folders/find', {
|
|
||||||
name: 'アイコン'
|
|
||||||
}).then(iconFolder => {
|
|
||||||
if (iconFolder.length === 0) {
|
|
||||||
api(I, 'drive/folders/create', {
|
|
||||||
name: 'アイコン'
|
|
||||||
}).then(iconFolder => {
|
|
||||||
upload(data, iconFolder);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
upload(data, iconFolder[0]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
cropper.on('skipped', () => {
|
|
||||||
set(file);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const upload = (data, folder) => {
|
|
||||||
const progress = (riot as any).mount(document.body.appendChild(document.createElement('mk-progress-dialog')), {
|
|
||||||
title: '新しいアバターをアップロードしています'
|
|
||||||
})[0];
|
|
||||||
|
|
||||||
if (folder) data.append('folder_id', folder.id);
|
|
||||||
|
|
||||||
const xhr = new XMLHttpRequest();
|
|
||||||
xhr.open('POST', _API_URL_ + '/drive/files/create', true);
|
|
||||||
xhr.onload = e => {
|
|
||||||
const file = JSON.parse((e.target as any).response);
|
|
||||||
progress.close();
|
|
||||||
set(file);
|
|
||||||
};
|
|
||||||
|
|
||||||
xhr.upload.onprogress = e => {
|
|
||||||
if (e.lengthComputable) progress.updateProgress(e.loaded, e.total);
|
|
||||||
};
|
|
||||||
|
|
||||||
xhr.send(data);
|
|
||||||
};
|
|
||||||
|
|
||||||
const set = file => {
|
|
||||||
api(I, 'i/update', {
|
|
||||||
avatar_id: file.id
|
|
||||||
}).then(i => {
|
|
||||||
dialog('%fa:info-circle%アバターを更新しました',
|
|
||||||
'新しいアバターが反映されるまで時間がかかる場合があります。',
|
|
||||||
[{
|
|
||||||
text: 'わかった'
|
|
||||||
}]);
|
|
||||||
|
|
||||||
if (cb) cb(i);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
if (file) {
|
|
||||||
fileSelected(file);
|
|
||||||
} else {
|
|
||||||
const browser = (riot as any).mount(document.body.appendChild(document.createElement('mk-select-file-from-drive-window')), {
|
|
||||||
multiple: false,
|
|
||||||
title: '%fa:image%アバターにする画像を選択'
|
|
||||||
})[0];
|
|
||||||
|
|
||||||
browser.one('selected', file => {
|
|
||||||
fileSelected(file);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,88 +0,0 @@
|
||||||
declare const _API_URL_: string;
|
|
||||||
|
|
||||||
import * as riot from 'riot';
|
|
||||||
import dialog from './dialog';
|
|
||||||
import api from '../../common/scripts/api';
|
|
||||||
|
|
||||||
export default (I, cb, file = null) => {
|
|
||||||
const fileSelected = file => {
|
|
||||||
const cropper = (riot as any).mount(document.body.appendChild(document.createElement('mk-crop-window')), {
|
|
||||||
file: file,
|
|
||||||
title: 'バナーとして表示する部分を選択',
|
|
||||||
aspectRatio: 16 / 9
|
|
||||||
})[0];
|
|
||||||
|
|
||||||
cropper.on('cropped', blob => {
|
|
||||||
const data = new FormData();
|
|
||||||
data.append('i', I.token);
|
|
||||||
data.append('file', blob, file.name + '.cropped.png');
|
|
||||||
|
|
||||||
api(I, 'drive/folders/find', {
|
|
||||||
name: 'バナー'
|
|
||||||
}).then(iconFolder => {
|
|
||||||
if (iconFolder.length === 0) {
|
|
||||||
api(I, 'drive/folders/create', {
|
|
||||||
name: 'バナー'
|
|
||||||
}).then(iconFolder => {
|
|
||||||
upload(data, iconFolder);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
upload(data, iconFolder[0]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
cropper.on('skipped', () => {
|
|
||||||
set(file);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const upload = (data, folder) => {
|
|
||||||
const progress = (riot as any).mount(document.body.appendChild(document.createElement('mk-progress-dialog')), {
|
|
||||||
title: '新しいバナーをアップロードしています'
|
|
||||||
})[0];
|
|
||||||
|
|
||||||
if (folder) data.append('folder_id', folder.id);
|
|
||||||
|
|
||||||
const xhr = new XMLHttpRequest();
|
|
||||||
xhr.open('POST', _API_URL_ + '/drive/files/create', true);
|
|
||||||
xhr.onload = e => {
|
|
||||||
const file = JSON.parse((e.target as any).response);
|
|
||||||
progress.close();
|
|
||||||
set(file);
|
|
||||||
};
|
|
||||||
|
|
||||||
xhr.upload.onprogress = e => {
|
|
||||||
if (e.lengthComputable) progress.updateProgress(e.loaded, e.total);
|
|
||||||
};
|
|
||||||
|
|
||||||
xhr.send(data);
|
|
||||||
};
|
|
||||||
|
|
||||||
const set = file => {
|
|
||||||
api(I, 'i/update', {
|
|
||||||
banner_id: file.id
|
|
||||||
}).then(i => {
|
|
||||||
dialog('%fa:info-circle%バナーを更新しました',
|
|
||||||
'新しいバナーが反映されるまで時間がかかる場合があります。',
|
|
||||||
[{
|
|
||||||
text: 'わかりました。'
|
|
||||||
}]);
|
|
||||||
|
|
||||||
if (cb) cb(i);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
if (file) {
|
|
||||||
fileSelected(file);
|
|
||||||
} else {
|
|
||||||
const browser = (riot as any).mount(document.body.appendChild(document.createElement('mk-select-file-from-drive-window')), {
|
|
||||||
multiple: false,
|
|
||||||
title: '%fa:image%バナーにする画像を選択'
|
|
||||||
})[0];
|
|
||||||
|
|
||||||
browser.one('selected', file => {
|
|
||||||
fileSelected(file);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -5,7 +5,7 @@
|
||||||
<header v-html="title"></header>
|
<header v-html="title"></header>
|
||||||
<div class="body" v-html="text"></div>
|
<div class="body" v-html="text"></div>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<button v-for="button in buttons" @click="click(button)" :key="button.id">{{ button.text }}</button>
|
<button v-for="button in buttons" @click="click(button)">{{ button.text }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -40,7 +40,6 @@ import Vue from 'vue';
|
||||||
import * as Sortable from 'sortablejs';
|
import * as Sortable from 'sortablejs';
|
||||||
import Autocomplete from '../../scripts/autocomplete';
|
import Autocomplete from '../../scripts/autocomplete';
|
||||||
import getKao from '../../../common/scripts/get-kao';
|
import getKao from '../../../common/scripts/get-kao';
|
||||||
import notify from '../../scripts/notify';
|
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
props: ['reply', 'repost'],
|
props: ['reply', 'repost'],
|
||||||
|
@ -200,13 +199,13 @@ export default Vue.extend({
|
||||||
this.clear();
|
this.clear();
|
||||||
this.deleteDraft();
|
this.deleteDraft();
|
||||||
this.$emit('posted');
|
this.$emit('posted');
|
||||||
notify(this.repost
|
(this as any).apis.notify(this.repost
|
||||||
? '%i18n:desktop.tags.mk-post-form.reposted%'
|
? '%i18n:desktop.tags.mk-post-form.reposted%'
|
||||||
: this.reply
|
: this.reply
|
||||||
? '%i18n:desktop.tags.mk-post-form.replied%'
|
? '%i18n:desktop.tags.mk-post-form.replied%'
|
||||||
: '%i18n:desktop.tags.mk-post-form.posted%');
|
: '%i18n:desktop.tags.mk-post-form.posted%');
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
notify(this.repost
|
(this as any).apis.notify(this.repost
|
||||||
? '%i18n:desktop.tags.mk-post-form.repost-failed%'
|
? '%i18n:desktop.tags.mk-post-form.repost-failed%'
|
||||||
: this.reply
|
: this.reply
|
||||||
? '%i18n:desktop.tags.mk-post-form.reply-failed%'
|
? '%i18n:desktop.tags.mk-post-form.reply-failed%'
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import notify from '../../scripts/notify';
|
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
props: ['post'],
|
props: ['post'],
|
||||||
|
@ -33,9 +32,9 @@ export default Vue.extend({
|
||||||
repost_id: this.post.id
|
repost_id: this.post.id
|
||||||
}).then(data => {
|
}).then(data => {
|
||||||
this.$emit('posted');
|
this.$emit('posted');
|
||||||
notify('%i18n:desktop.tags.mk-repost-form.success%');
|
(this as any).apis.notify('%i18n:desktop.tags.mk-repost-form.success%');
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
notify('%i18n:desktop.tags.mk-repost-form.failure%');
|
(this as any).apis.notify('%i18n:desktop.tags.mk-repost-form.failure%');
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
this.wait = false;
|
this.wait = false;
|
||||||
});
|
});
|
||||||
|
|
|
@ -27,7 +27,6 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import notify from '../../scripts/notify';
|
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
data() {
|
data() {
|
||||||
|
@ -59,7 +58,7 @@ export default Vue.extend({
|
||||||
description: this.description || null,
|
description: this.description || null,
|
||||||
birthday: this.birthday || null
|
birthday: this.birthday || null
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
notify('プロフィールを更新しました');
|
(this as any).apis.notify('プロフィールを更新しました');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import * as anime from 'animejs';
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
props: ['message'],
|
props: ['message'],
|
||||||
mounted() {
|
mounted() {
|
||||||
|
this.$nextTick(() => {
|
||||||
anime({
|
anime({
|
||||||
targets: this.$el,
|
targets: this.$el,
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
|
@ -29,6 +30,7 @@ export default Vue.extend({
|
||||||
complete: () => this.$destroy()
|
complete: () => this.$destroy()
|
||||||
});
|
});
|
||||||
}, 6000);
|
}, 6000);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -22,7 +22,6 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import updateBanner from '../../../scripts/update-banner';
|
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
props: ['user'],
|
props: ['user'],
|
||||||
|
@ -53,7 +52,7 @@ export default Vue.extend({
|
||||||
onBannerClick() {
|
onBannerClick() {
|
||||||
if (!(this as any).os.isSignedIn || (this as any).os.i.id != this.user.id) return;
|
if (!(this as any).os.isSignedIn || (this as any).os.i.id != this.user.id) return;
|
||||||
|
|
||||||
updateBanner((this as any).os.i, i => {
|
(this as any).apis.updateBanner((this as any).os.i, i => {
|
||||||
this.user.banner_url = i.banner_url;
|
this.user.banner_url = i.banner_url;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ Vue.mixin({
|
||||||
import App from './app.vue';
|
import App from './app.vue';
|
||||||
|
|
||||||
import checkForUpdate from './common/scripts/check-for-update';
|
import checkForUpdate from './common/scripts/check-for-update';
|
||||||
import MiOS from './common/mios';
|
import MiOS, { API } from './common/mios';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* APP ENTRY POINT!
|
* APP ENTRY POINT!
|
||||||
|
@ -79,59 +79,32 @@ if (localStorage.getItem('should-refresh') == 'true') {
|
||||||
location.reload(true);
|
location.reload(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
type API = {
|
|
||||||
chooseDriveFile: (opts: {
|
|
||||||
title?: string;
|
|
||||||
currentFolder?: any;
|
|
||||||
multiple?: boolean;
|
|
||||||
}) => Promise<any>;
|
|
||||||
|
|
||||||
chooseDriveFolder: (opts: {
|
|
||||||
title?: string;
|
|
||||||
currentFolder?: any;
|
|
||||||
}) => Promise<any>;
|
|
||||||
|
|
||||||
dialog: (opts: {
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
actions: Array<{
|
|
||||||
text: string;
|
|
||||||
id: string;
|
|
||||||
}>;
|
|
||||||
}) => Promise<string>;
|
|
||||||
|
|
||||||
input: (opts: {
|
|
||||||
title: string;
|
|
||||||
placeholder?: string;
|
|
||||||
default?: string;
|
|
||||||
}) => Promise<string>;
|
|
||||||
|
|
||||||
post: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
// MiOSを初期化してコールバックする
|
// MiOSを初期化してコールバックする
|
||||||
export default (callback: (launch: (api: API) => Vue) => void, sw = false) => {
|
export default (callback: (launch: (api: (os: MiOS) => API) => [Vue, MiOS]) => void, sw = false) => {
|
||||||
const os = new MiOS(sw);
|
const os = new MiOS(sw);
|
||||||
|
|
||||||
os.init(() => {
|
os.init(() => {
|
||||||
// アプリ基底要素マウント
|
// アプリ基底要素マウント
|
||||||
document.body.innerHTML = '<div id="app"></div>';
|
document.body.innerHTML = '<div id="app"></div>';
|
||||||
|
|
||||||
const launch = (api: API) => {
|
const launch = (api: (os: MiOS) => API) => {
|
||||||
|
os.apis = api(os);
|
||||||
Vue.mixin({
|
Vue.mixin({
|
||||||
created() {
|
created() {
|
||||||
(this as any).os = os;
|
(this as any).os = os;
|
||||||
(this as any).api = os.api;
|
(this as any).api = os.api;
|
||||||
(this as any).apis = api;
|
(this as any).apis = os.apis;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Vue({
|
const app = new Vue({
|
||||||
router: new VueRouter({
|
router: new VueRouter({
|
||||||
mode: 'history'
|
mode: 'history'
|
||||||
}),
|
}),
|
||||||
render: createEl => createEl(App)
|
render: createEl => createEl(App)
|
||||||
}).$mount('#app');
|
}).$mount('#app');
|
||||||
|
|
||||||
|
return [app, os] as [Vue, MiOS];
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
Loading…
Reference in a new issue