summaryrefslogtreecommitdiff
path: root/src/client/app/common
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/app/common')
-rw-r--r--src/client/app/common/define-widget.ts62
-rw-r--r--src/client/app/common/mios.ts601
-rw-r--r--src/client/app/common/scripts/check-for-update.ts4
-rw-r--r--src/client/app/common/scripts/streaming/channel.ts2
-rw-r--r--src/client/app/common/scripts/streaming/drive.ts2
-rw-r--r--src/client/app/common/scripts/streaming/global-timeline.ts2
-rw-r--r--src/client/app/common/scripts/streaming/home.ts25
-rw-r--r--src/client/app/common/scripts/streaming/local-timeline.ts2
-rw-r--r--src/client/app/common/scripts/streaming/messaging-index.ts2
-rw-r--r--src/client/app/common/scripts/streaming/messaging.ts2
-rw-r--r--src/client/app/common/scripts/streaming/othello-game.ts2
-rw-r--r--src/client/app/common/scripts/streaming/othello.ts2
-rw-r--r--src/client/app/common/scripts/streaming/server.ts2
-rw-r--r--src/client/app/common/scripts/streaming/stream.ts2
-rw-r--r--src/client/app/common/scripts/streaming/user-list.ts17
-rw-r--r--src/client/app/common/views/components/autocomplete.vue12
-rw-r--r--src/client/app/common/views/components/avatar.vue42
-rw-r--r--src/client/app/common/views/components/google.vue67
-rw-r--r--src/client/app/common/views/components/index.ts2
-rw-r--r--src/client/app/common/views/components/media-list.vue17
-rw-r--r--src/client/app/common/views/components/messaging-room.message.vue32
-rw-r--r--src/client/app/common/views/components/messaging-room.vue16
-rw-r--r--src/client/app/common/views/components/messaging.vue45
-rw-r--r--src/client/app/common/views/components/nav.vue10
-rw-r--r--src/client/app/common/views/components/note-html.ts21
-rw-r--r--src/client/app/common/views/components/note-menu.vue12
-rw-r--r--src/client/app/common/views/components/othello.vue20
-rw-r--r--src/client/app/common/views/components/poll-editor.vue10
-rw-r--r--src/client/app/common/views/components/poll.vue17
-rw-r--r--src/client/app/common/views/components/reaction-picker.vue21
-rw-r--r--src/client/app/common/views/components/reactions-viewer.vue33
-rw-r--r--src/client/app/common/views/components/signin.vue4
-rw-r--r--src/client/app/common/views/components/signup.vue4
-rw-r--r--src/client/app/common/views/components/stream-indicator.vue2
-rw-r--r--src/client/app/common/views/components/switch.vue25
-rw-r--r--src/client/app/common/views/components/twitter-setting.vue2
-rw-r--r--src/client/app/common/views/components/url-preview.vue127
-rw-r--r--src/client/app/common/views/components/visibility-chooser.vue223
-rw-r--r--src/client/app/common/views/components/welcome-timeline.vue17
-rw-r--r--src/client/app/common/views/widgets/access-log.vue3
-rw-r--r--src/client/app/common/views/widgets/broadcast.vue1
-rw-r--r--src/client/app/common/views/widgets/calendar.vue21
-rw-r--r--src/client/app/common/views/widgets/donation.vue18
-rw-r--r--src/client/app/common/views/widgets/nav.vue29
-rw-r--r--src/client/app/common/views/widgets/photo-stream.vue2
-rw-r--r--src/client/app/common/views/widgets/rss.vue69
-rw-r--r--src/client/app/common/views/widgets/server.cpu-memory.vue11
-rw-r--r--src/client/app/common/views/widgets/server.cpu.vue10
-rw-r--r--src/client/app/common/views/widgets/server.disk.vue10
-rw-r--r--src/client/app/common/views/widgets/server.memory.vue10
-rw-r--r--src/client/app/common/views/widgets/server.pie.vue10
-rw-r--r--src/client/app/common/views/widgets/server.vue2
-rw-r--r--src/client/app/common/views/widgets/slideshow.vue6
-rw-r--r--src/client/app/common/views/widgets/tips.vue2
54 files changed, 814 insertions, 900 deletions
diff --git a/src/client/app/common/define-widget.ts b/src/client/app/common/define-widget.ts
index 7b98c0903f..0b2bc36566 100644
--- a/src/client/app/common/define-widget.ts
+++ b/src/client/app/common/define-widget.ts
@@ -18,61 +18,65 @@ export default function<T extends object>(data: {
default: false
}
},
+
computed: {
id(): string {
return this.widget.id;
+ },
+
+ props(): T {
+ return this.widget.data;
}
},
+
data() {
return {
- props: data.props ? data.props() : {} as T,
- bakedOldProps: null,
- preventSave: false
+ bakedOldProps: null
};
},
+
created() {
- if (this.props) {
- Object.keys(this.props).forEach(prop => {
- if (this.widget.data.hasOwnProperty(prop)) {
- this.props[prop] = this.widget.data[prop];
- }
- });
- }
+ this.mergeProps();
+
+ this.$watch('props', () => {
+ this.mergeProps();
+ });
this.bakeProps();
+ },
+
+ methods: {
+ bakeProps() {
+ this.bakedOldProps = JSON.stringify(this.props);
+ },
- this.$watch('props', newProps => {
- if (this.preventSave) {
- this.preventSave = false;
- this.bakeProps();
- return;
+ mergeProps() {
+ if (data.props) {
+ const defaultProps = data.props();
+ Object.keys(defaultProps).forEach(prop => {
+ if (!this.props.hasOwnProperty(prop)) {
+ Vue.set(this.props, prop, defaultProps[prop]);
+ }
+ });
}
- if (this.bakedOldProps == JSON.stringify(newProps)) return;
+ },
+
+ save() {
+ if (this.bakedOldProps == JSON.stringify(this.props)) return;
this.bakeProps();
if (this.isMobile) {
(this as any).api('i/update_mobile_home', {
id: this.id,
- data: newProps
- }).then(() => {
- (this as any).os.i.clientSettings.mobileHome.find(w => w.id == this.id).data = newProps;
+ data: this.props
});
} else {
(this as any).api('i/update_home', {
id: this.id,
- data: newProps
- }).then(() => {
- (this as any).os.i.clientSettings.home.find(w => w.id == this.id).data = newProps;
+ data: this.props
});
}
- }, {
- deep: true
- });
- },
- methods: {
- bakeProps() {
- this.bakedOldProps = JSON.stringify(this.props);
}
}
});
diff --git a/src/client/app/common/mios.ts b/src/client/app/common/mios.ts
deleted file mode 100644
index 6d6d6b3e68..0000000000
--- a/src/client/app/common/mios.ts
+++ /dev/null
@@ -1,601 +0,0 @@
-import Vue from 'vue';
-import { EventEmitter } from 'eventemitter3';
-import * as merge from 'object-assign-deep';
-import * as uuid from 'uuid';
-
-import { hostname, apiUrl, swPublickey, version, lang, googleMapsApiKey } from '../config';
-import Progress from './scripts/loading';
-import Connection from './scripts/streaming/stream';
-import { HomeStreamManager } from './scripts/streaming/home';
-import { DriveStreamManager } from './scripts/streaming/drive';
-import { ServerStreamManager } from './scripts/streaming/server';
-import { MessagingIndexStreamManager } from './scripts/streaming/messaging-index';
-import { OthelloStreamManager } from './scripts/streaming/othello';
-
-import Err from '../common/views/components/connect-failed.vue';
-import { LocalTimelineStreamManager } from './scripts/streaming/local-timeline';
-import { GlobalTimelineStreamManager } from './scripts/streaming/global-timeline';
-
-//#region api requests
-let spinner = null;
-let pending = 0;
-//#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: (opts?: {
- reply?: any;
- renote?: any;
- }) => void;
-
- notify: (message: string) => void;
-};
-
-/**
- * Misskey Operating System
- */
-export default class MiOS extends EventEmitter {
- /**
- * Misskeyの /meta で取得できるメタ情報
- */
- private meta: {
- data: { [x: string]: any };
- chachedAt: Date;
- };
-
- private isMetaFetching = false;
-
- public app: Vue;
-
- public new(vm, props) {
- const w = new vm({
- parent: this.app,
- propsData: props
- }).$mount();
- document.body.appendChild(w.$el);
- }
-
- /**
- * A signing user
- */
- public i: { [x: string]: any };
-
- /**
- * Whether signed in
- */
- public get isSignedIn() {
- return this.i != null;
- }
-
- /**
- * Whether is debug mode
- */
- public get debug() {
- return localStorage.getItem('debug') == 'true';
- }
-
- /**
- * Whether enable sounds
- */
- public get isEnableSounds() {
- return localStorage.getItem('enableSounds') == 'true';
- }
-
- public apis: API;
-
- /**
- * A connection manager of home stream
- */
- public stream: HomeStreamManager;
-
- /**
- * Connection managers
- */
- public streams: {
- localTimelineStream: LocalTimelineStreamManager;
- globalTimelineStream: GlobalTimelineStreamManager;
- driveStream: DriveStreamManager;
- serverStream: ServerStreamManager;
- messagingIndexStream: MessagingIndexStreamManager;
- othelloStream: OthelloStreamManager;
- } = {
- localTimelineStream: null,
- globalTimelineStream: null,
- driveStream: null,
- serverStream: null,
- messagingIndexStream: null,
- othelloStream: null
- };
-
- /**
- * A registration of service worker
- */
- private swRegistration: ServiceWorkerRegistration = null;
-
- /**
- * Whether should register ServiceWorker
- */
- private shouldRegisterSw: boolean;
-
- /**
- * ウィンドウシステム
- */
- public windows = new WindowSystem();
-
- /**
- * MiOSインスタンスを作成します
- * @param shouldRegisterSw ServiceWorkerを登録するかどうか
- */
- constructor(shouldRegisterSw = false) {
- super();
-
- this.shouldRegisterSw = shouldRegisterSw;
-
- //#region BIND
- this.log = this.log.bind(this);
- this.logInfo = this.logInfo.bind(this);
- this.logWarn = this.logWarn.bind(this);
- this.logError = this.logError.bind(this);
- this.init = this.init.bind(this);
- this.api = this.api.bind(this);
- this.getMeta = this.getMeta.bind(this);
- this.registerSw = this.registerSw.bind(this);
- //#endregion
-
- if (this.debug) {
- (window as any).os = this;
- }
- }
-
- private googleMapsIniting = false;
-
- public getGoogleMaps() {
- return new Promise((res, rej) => {
- if ((window as any).google && (window as any).google.maps) {
- res((window as any).google.maps);
- } else {
- this.once('init-google-maps', () => {
- res((window as any).google.maps);
- });
-
- //#region load google maps api
- if (!this.googleMapsIniting) {
- this.googleMapsIniting = true;
- (window as any).initGoogleMaps = () => {
- this.emit('init-google-maps');
- };
- const head = document.getElementsByTagName('head')[0];
- const script = document.createElement('script');
- script.setAttribute('src', `https://maps.googleapis.com/maps/api/js?key=${googleMapsApiKey}&callback=initGoogleMaps`);
- script.setAttribute('async', 'true');
- script.setAttribute('defer', 'true');
- head.appendChild(script);
- }
- //#endregion
- }
- });
- }
-
- public log(...args) {
- if (!this.debug) return;
- console.log.apply(null, args);
- }
-
- public logInfo(...args) {
- if (!this.debug) return;
- console.info.apply(null, args);
- }
-
- public logWarn(...args) {
- if (!this.debug) return;
- console.warn.apply(null, args);
- }
-
- public logError(...args) {
- if (!this.debug) return;
- console.error.apply(null, args);
- }
-
- public signout() {
- localStorage.removeItem('me');
- document.cookie = `i=; domain=${hostname}; expires=Thu, 01 Jan 1970 00:00:01 GMT;`;
- location.href = '/';
- }
-
- /**
- * Initialize MiOS (boot)
- * @param callback A function that call when initialized
- */
- public async init(callback) {
- //#region Init stream managers
- this.streams.serverStream = new ServerStreamManager(this);
-
- this.once('signedin', () => {
- // Init home stream manager
- this.stream = new HomeStreamManager(this, this.i);
-
- // Init other stream manager
- this.streams.localTimelineStream = new LocalTimelineStreamManager(this, this.i);
- this.streams.globalTimelineStream = new GlobalTimelineStreamManager(this, this.i);
- this.streams.driveStream = new DriveStreamManager(this, this.i);
- this.streams.messagingIndexStream = new MessagingIndexStreamManager(this, this.i);
- this.streams.othelloStream = new OthelloStreamManager(this, this.i);
- });
- //#endregion
-
- // ユーザーをフェッチしてコールバックする
- const fetchme = (token, cb) => {
- let me = null;
-
- // Return when not signed in
- if (token == null) {
- return done();
- }
-
- // Fetch user
- fetch(`${apiUrl}/i`, {
- method: 'POST',
- body: JSON.stringify({
- i: token
- })
- })
- // When success
- .then(res => {
- // When failed to authenticate user
- if (res.status !== 200) {
- return this.signout();
- }
-
- // Parse response
- res.json().then(i => {
- me = i;
- me.token = token;
- done();
- });
- })
- // When failure
- .catch(() => {
- // Render the error screen
- document.body.innerHTML = '<div id="err"></div>';
- new Vue({
- render: createEl => createEl(Err)
- }).$mount('#err');
-
- Progress.done();
- });
-
- function done() {
- if (cb) cb(me);
- }
- };
-
- // フェッチが完了したとき
- const fetched = me => {
- if (me) {
- // デフォルトの設定をマージ
- me.clientSettings = Object.assign({
- fetchOnScroll: true,
- showMaps: true,
- showPostFormOnTopOfTl: false,
- gradientWindowHeader: false
- }, me.clientSettings);
-
- // ローカルストレージにキャッシュ
- localStorage.setItem('me', JSON.stringify(me));
- }
-
- this.i = me;
-
- this.emit('signedin');
-
- // Finish init
- callback();
-
- //#region Note
-
- // Init service worker
- if (this.shouldRegisterSw) this.registerSw();
-
- //#endregion
- };
-
- // Get cached account data
- const cachedMe = JSON.parse(localStorage.getItem('me'));
-
- // キャッシュがあったとき
- if (cachedMe) {
- if (cachedMe.token == null) {
- this.signout();
- return;
- }
-
- // とりあえずキャッシュされたデータでお茶を濁して(?)おいて、
- fetched(cachedMe);
-
- // 後から新鮮なデータをフェッチ
- fetchme(cachedMe.token, freshData => {
- merge(cachedMe, freshData);
- });
- } else {
- // Get token from cookie
- const i = (document.cookie.match(/i=(!\w+)/) || [null, null])[1];
-
- fetchme(i, fetched);
- }
- }
-
- /**
- * Register service worker
- */
- private registerSw() {
- // Check whether service worker and push manager supported
- const isSwSupported =
- ('serviceWorker' in navigator) && ('PushManager' in window);
-
- // Reject when browser not service worker supported
- if (!isSwSupported) return;
-
- // Reject when not signed in to Misskey
- if (!this.isSignedIn) return;
-
- // When service worker activated
- navigator.serviceWorker.ready.then(registration => {
- this.log('[sw] ready: ', registration);
-
- this.swRegistration = registration;
-
- // Options of pushManager.subscribe
- // SEE: https://developer.mozilla.org/en-US/docs/Web/API/PushManager/subscribe#Parameters
- const opts = {
- // A boolean indicating that the returned push subscription
- // will only be used for messages whose effect is made visible to the user.
- userVisibleOnly: true,
-
- // A public key your push server will use to send
- // messages to client apps via a push server.
- applicationServerKey: urlBase64ToUint8Array(swPublickey)
- };
-
- // Subscribe push notification
- this.swRegistration.pushManager.subscribe(opts).then(subscription => {
- this.log('[sw] Subscribe OK:', subscription);
-
- function encode(buffer: ArrayBuffer) {
- return btoa(String.fromCharCode.apply(null, new Uint8Array(buffer)));
- }
-
- // Register
- this.api('sw/register', {
- endpoint: subscription.endpoint,
- auth: encode(subscription.getKey('auth')),
- publickey: encode(subscription.getKey('p256dh'))
- });
- })
- // When subscribe failed
- .catch(async (err: Error) => {
- this.logError('[sw] Subscribe Error:', err);
-
- // 通知が許可されていなかったとき
- if (err.name == 'NotAllowedError') {
- this.logError('[sw] Subscribe failed due to notification not allowed');
- return;
- }
-
- // 違うapplicationServerKey (または gcm_sender_id)のサブスクリプションが
- // 既に存在していることが原因でエラーになった可能性があるので、
- // そのサブスクリプションを解除しておく
- const subscription = await this.swRegistration.pushManager.getSubscription();
- if (subscription) subscription.unsubscribe();
- });
- });
-
- // Whether use raw version script
- const raw = (localStorage.getItem('useRawScript') == 'true' && this.debug)
- || process.env.NODE_ENV != 'production';
-
- // The path of service worker script
- const sw = `/sw.${version}.${lang}.${raw ? 'raw' : 'min'}.js`;
-
- // Register service worker
- navigator.serviceWorker.register(sw).then(registration => {
- // 登録成功
- this.logInfo('[sw] Registration successful with scope: ', registration.scope);
- }).catch(err => {
- // 登録失敗 :(
- this.logError('[sw] Registration failed: ', err);
- });
- }
-
- public requests = [];
-
- /**
- * Misskey APIにリクエストします
- * @param endpoint エンドポイント名
- * @param data パラメータ
- */
- public api(endpoint: string, data: { [x: string]: any } = {}): Promise<{ [x: string]: any }> {
- if (++pending === 1) {
- spinner = document.createElement('div');
- spinner.setAttribute('id', 'wait');
- document.body.appendChild(spinner);
- }
-
- const onFinally = () => {
- if (--pending === 0) spinner.parentNode.removeChild(spinner);
- };
-
- const promise = new Promise((resolve, reject) => {
- const viaStream = this.stream.hasConnection &&
- (localStorage.getItem('apiViaStream') ? localStorage.getItem('apiViaStream') == 'true' : true);
-
- if (viaStream) {
- const stream = this.stream.borrow();
- const id = Math.random().toString();
-
- stream.once(`api-res:${id}`, res => {
- if (res == null || Object.keys(res).length == 0) {
- resolve(null);
- } else if (res.res) {
- resolve(res.res);
- } else {
- reject(res.e);
- }
- });
-
- stream.send({
- type: 'api',
- id,
- endpoint,
- data
- });
- } else {
- // Append a credential
- if (this.isSignedIn) (data as any).i = this.i.token;
-
- const req = {
- id: uuid(),
- date: new Date(),
- name: endpoint,
- data,
- res: null,
- status: null
- };
-
- if (this.debug) {
- this.requests.push(req);
- }
-
- // Send request
- fetch(endpoint.indexOf('://') > -1 ? endpoint : `${apiUrl}/${endpoint}`, {
- method: 'POST',
- body: JSON.stringify(data),
- credentials: endpoint === 'signin' ? 'include' : 'omit',
- cache: 'no-cache'
- }).then(async (res) => {
- const body = res.status === 204 ? null : await res.json();
-
- if (this.debug) {
- req.status = res.status;
- req.res = body;
- }
-
- if (res.status === 200) {
- resolve(body);
- } else if (res.status === 204) {
- resolve();
- } else {
- reject(body.error);
- }
- }).catch(reject);
- }
- });
-
- promise.then(onFinally, onFinally);
-
- return promise;
- }
-
- /**
- * Misskeyのメタ情報を取得します
- * @param force キャッシュを無視するか否か
- */
- public getMeta(force = false) {
- return new Promise<{ [x: string]: any }>(async (res, rej) => {
- if (this.isMetaFetching) {
- this.once('_meta_fetched_', () => {
- res(this.meta.data);
- });
- return;
- }
-
- const expire = 1000 * 60; // 1min
-
- // forceが有効, meta情報を保持していない or 期限切れ
- if (force || this.meta == null || Date.now() - this.meta.chachedAt.getTime() > expire) {
- this.isMetaFetching = true;
- const meta = await this.api('meta');
- this.meta = {
- data: meta,
- chachedAt: new Date()
- };
- this.isMetaFetching = false;
- this.emit('_meta_fetched_');
- res(meta);
- } else {
- res(this.meta.data);
- }
- });
- }
-
- public connections: Connection[] = [];
-
- public registerStreamConnection(connection: Connection) {
- this.connections.push(connection);
- }
-
- public unregisterStreamConnection(connection: Connection) {
- this.connections = this.connections.filter(c => c != connection);
- }
-}
-
-class WindowSystem extends EventEmitter {
- public windows = new Set();
-
- public add(window) {
- this.windows.add(window);
- this.emit('added', window);
- }
-
- public remove(window) {
- this.windows.delete(window);
- this.emit('removed', window);
- }
-
- public getAll() {
- return this.windows;
- }
-}
-
-/**
- * Convert the URL safe base64 string to a Uint8Array
- * @param base64String base64 string
- */
-function urlBase64ToUint8Array(base64String: string): Uint8Array {
- const padding = '='.repeat((4 - base64String.length % 4) % 4);
- const base64 = (base64String + padding)
- .replace(/\-/g, '+')
- .replace(/_/g, '/');
-
- const rawData = window.atob(base64);
- const outputArray = new Uint8Array(rawData.length);
-
- for (let i = 0; i < rawData.length; ++i) {
- outputArray[i] = rawData.charCodeAt(i);
- }
- return outputArray;
-}
diff --git a/src/client/app/common/scripts/check-for-update.ts b/src/client/app/common/scripts/check-for-update.ts
index 20ce64ea85..1e303017eb 100644
--- a/src/client/app/common/scripts/check-for-update.ts
+++ b/src/client/app/common/scripts/check-for-update.ts
@@ -1,9 +1,9 @@
-import MiOS from '../mios';
+import MiOS from '../../mios';
import { version as current } from '../../config';
export default async function(mios: MiOS, force = false, silent = false) {
const meta = await mios.getMeta(force);
- const newer = meta.version;
+ const newer = meta.clientVersion;
if (newer != current) {
localStorage.setItem('should-refresh', 'true');
diff --git a/src/client/app/common/scripts/streaming/channel.ts b/src/client/app/common/scripts/streaming/channel.ts
index cab5f4edb4..be68ec0997 100644
--- a/src/client/app/common/scripts/streaming/channel.ts
+++ b/src/client/app/common/scripts/streaming/channel.ts
@@ -1,5 +1,5 @@
import Stream from './stream';
-import MiOS from '../../mios';
+import MiOS from '../../../mios';
/**
* Channel stream connection
diff --git a/src/client/app/common/scripts/streaming/drive.ts b/src/client/app/common/scripts/streaming/drive.ts
index 7ff85b5946..50fff05737 100644
--- a/src/client/app/common/scripts/streaming/drive.ts
+++ b/src/client/app/common/scripts/streaming/drive.ts
@@ -1,6 +1,6 @@
import Stream from './stream';
import StreamManager from './stream-manager';
-import MiOS from '../../mios';
+import MiOS from '../../../mios';
/**
* Drive stream connection
diff --git a/src/client/app/common/scripts/streaming/global-timeline.ts b/src/client/app/common/scripts/streaming/global-timeline.ts
index 452ddbac03..a639f1595c 100644
--- a/src/client/app/common/scripts/streaming/global-timeline.ts
+++ b/src/client/app/common/scripts/streaming/global-timeline.ts
@@ -1,6 +1,6 @@
import Stream from './stream';
import StreamManager from './stream-manager';
-import MiOS from '../../mios';
+import MiOS from '../../../mios';
/**
* Global timeline stream connection
diff --git a/src/client/app/common/scripts/streaming/home.ts b/src/client/app/common/scripts/streaming/home.ts
index 73f2c5302c..32685f3c2c 100644
--- a/src/client/app/common/scripts/streaming/home.ts
+++ b/src/client/app/common/scripts/streaming/home.ts
@@ -2,7 +2,7 @@ import * as merge from 'object-assign-deep';
import Stream from './stream';
import StreamManager from './stream-manager';
-import MiOS from '../../mios';
+import MiOS from '../../../mios';
/**
* Home stream connection
@@ -25,10 +25,31 @@ export class HomeStream extends Stream {
console.log('I updated:', i);
}
merge(me, i);
+
+ // キャッシュ更新
+ os.bakeMe();
+ });
+
+ this.on('clientSettingUpdated', x => {
+ os.store.commit('settings/set', {
+ key: x.key,
+ value: x.value
+ });
+ });
+
+ this.on('home_updated', x => {
+ if (x.home) {
+ os.store.commit('settings/setHome', x.home);
+ } else {
+ os.store.commit('settings/setHomeWidget', {
+ id: x.id,
+ data: x.data
+ });
+ }
});
// トークンが再生成されたとき
- // このままではAPIが利用できないので強制的にサインアウトさせる
+ // このままではMisskeyが利用できないので強制的にサインアウトさせる
this.on('my_token_regenerated', () => {
alert('%i18n:!common.my-token-regenerated%');
os.signout();
diff --git a/src/client/app/common/scripts/streaming/local-timeline.ts b/src/client/app/common/scripts/streaming/local-timeline.ts
index 3d04e05cd4..2834262bdc 100644
--- a/src/client/app/common/scripts/streaming/local-timeline.ts
+++ b/src/client/app/common/scripts/streaming/local-timeline.ts
@@ -1,6 +1,6 @@
import Stream from './stream';
import StreamManager from './stream-manager';
-import MiOS from '../../mios';
+import MiOS from '../../../mios';
/**
* Local timeline stream connection
diff --git a/src/client/app/common/scripts/streaming/messaging-index.ts b/src/client/app/common/scripts/streaming/messaging-index.ts
index 84e2174ec4..addcccb952 100644
--- a/src/client/app/common/scripts/streaming/messaging-index.ts
+++ b/src/client/app/common/scripts/streaming/messaging-index.ts
@@ -1,6 +1,6 @@
import Stream from './stream';
import StreamManager from './stream-manager';
-import MiOS from '../../mios';
+import MiOS from '../../../mios';
/**
* Messaging index stream connection
diff --git a/src/client/app/common/scripts/streaming/messaging.ts b/src/client/app/common/scripts/streaming/messaging.ts
index c1b5875cfb..a59377d867 100644
--- a/src/client/app/common/scripts/streaming/messaging.ts
+++ b/src/client/app/common/scripts/streaming/messaging.ts
@@ -1,5 +1,5 @@
import Stream from './stream';
-import MiOS from '../../mios';
+import MiOS from '../../../mios';
/**
* Messaging stream connection
diff --git a/src/client/app/common/scripts/streaming/othello-game.ts b/src/client/app/common/scripts/streaming/othello-game.ts
index b85af8f72b..9e36f647bb 100644
--- a/src/client/app/common/scripts/streaming/othello-game.ts
+++ b/src/client/app/common/scripts/streaming/othello-game.ts
@@ -1,5 +1,5 @@
import Stream from './stream';
-import MiOS from '../../mios';
+import MiOS from '../../../mios';
export class OthelloGameStream extends Stream {
constructor(os: MiOS, me, game) {
diff --git a/src/client/app/common/scripts/streaming/othello.ts b/src/client/app/common/scripts/streaming/othello.ts
index f5d47431cd..8f4f217e39 100644
--- a/src/client/app/common/scripts/streaming/othello.ts
+++ b/src/client/app/common/scripts/streaming/othello.ts
@@ -1,6 +1,6 @@
import StreamManager from './stream-manager';
import Stream from './stream';
-import MiOS from '../../mios';
+import MiOS from '../../../mios';
export class OthelloStream extends Stream {
constructor(os: MiOS, me) {
diff --git a/src/client/app/common/scripts/streaming/server.ts b/src/client/app/common/scripts/streaming/server.ts
index 3d35ef4d9d..2ea4239288 100644
--- a/src/client/app/common/scripts/streaming/server.ts
+++ b/src/client/app/common/scripts/streaming/server.ts
@@ -1,6 +1,6 @@
import Stream from './stream';
import StreamManager from './stream-manager';
-import MiOS from '../../mios';
+import MiOS from '../../../mios';
/**
* Server stream connection
diff --git a/src/client/app/common/scripts/streaming/stream.ts b/src/client/app/common/scripts/streaming/stream.ts
index 3912186ad3..fefa8e5ced 100644
--- a/src/client/app/common/scripts/streaming/stream.ts
+++ b/src/client/app/common/scripts/streaming/stream.ts
@@ -2,7 +2,7 @@ import { EventEmitter } from 'eventemitter3';
import * as uuid from 'uuid';
import * as ReconnectingWebsocket from 'reconnecting-websocket';
import { wsUrl } from '../../../config';
-import MiOS from '../../mios';
+import MiOS from '../../../mios';
/**
* Misskey stream connection
diff --git a/src/client/app/common/scripts/streaming/user-list.ts b/src/client/app/common/scripts/streaming/user-list.ts
new file mode 100644
index 0000000000..30a52b98dd
--- /dev/null
+++ b/src/client/app/common/scripts/streaming/user-list.ts
@@ -0,0 +1,17 @@
+import Stream from './stream';
+import MiOS from '../../mios';
+
+export class UserListStream extends Stream {
+ constructor(os: MiOS, me, listId) {
+ super(os, 'user-list', {
+ i: me.token,
+ listId
+ });
+
+ (this as any).on('_connected_', () => {
+ this.send({
+ i: me.token
+ });
+ });
+ }
+}
diff --git a/src/client/app/common/views/components/autocomplete.vue b/src/client/app/common/views/components/autocomplete.vue
index 5c8f61a2a2..84173d20b5 100644
--- a/src/client/app/common/views/components/autocomplete.vue
+++ b/src/client/app/common/views/components/autocomplete.vue
@@ -234,7 +234,7 @@ export default Vue.extend({
margin-top calc(1em + 8px)
overflow hidden
background #fff
- border solid 1px rgba(0, 0, 0, 0.1)
+ border solid 1px rgba(#000, 0.1)
border-radius 4px
transition top 0.1s ease, left 0.1s ease
@@ -253,7 +253,7 @@ export default Vue.extend({
white-space nowrap
overflow hidden
font-size 0.9em
- color rgba(0, 0, 0, 0.8)
+ color rgba(#000, 0.8)
cursor default
&, *
@@ -285,10 +285,10 @@ export default Vue.extend({
.name
margin 0 8px 0 0
- color rgba(0, 0, 0, 0.8)
+ color rgba(#000, 0.8)
.username
- color rgba(0, 0, 0, 0.3)
+ color rgba(#000, 0.3)
> .emojis > li
@@ -298,10 +298,10 @@ export default Vue.extend({
width 24px
.name
- color rgba(0, 0, 0, 0.8)
+ color rgba(#000, 0.8)
.alias
margin 0 0 0 8px
- color rgba(0, 0, 0, 0.3)
+ color rgba(#000, 0.3)
</style>
diff --git a/src/client/app/common/views/components/avatar.vue b/src/client/app/common/views/components/avatar.vue
new file mode 100644
index 0000000000..a4648c272e
--- /dev/null
+++ b/src/client/app/common/views/components/avatar.vue
@@ -0,0 +1,42 @@
+<template>
+ <router-link class="mk-avatar" :to="user | userPage" :title="user | acct" :target="target" :style="style" v-if="disablePreview"></router-link>
+ <router-link class="mk-avatar" :to="user | userPage" :title="user | acct" :target="target" :style="style" v-else v-user-preview="user.id"></router-link>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+export default Vue.extend({
+ props: {
+ user: {
+ type: Object,
+ required: true
+ },
+ target: {
+ required: false,
+ default: null
+ },
+ disablePreview: {
+ required: false,
+ default: false
+ }
+ },
+ computed: {
+ style(): any {
+ return {
+ backgroundColor: this.user.avatarColor ? `rgb(${ this.user.avatarColor.join(',') })` : null,
+ backgroundImage: `url(${ this.user.avatarUrl }?thumbnail)`,
+ borderRadius: (this as any).clientSettings.circleIcons ? '100%' : null
+ };
+ }
+ }
+});
+</script>
+
+<style lang="stylus" scoped>
+.mk-avatar
+ display inline-block
+ vertical-align bottom
+ background-size cover
+ background-position center center
+ transition border-radius 1s ease
+</style>
diff --git a/src/client/app/common/views/components/google.vue b/src/client/app/common/views/components/google.vue
new file mode 100644
index 0000000000..92817d3c1f
--- /dev/null
+++ b/src/client/app/common/views/components/google.vue
@@ -0,0 +1,67 @@
+<template>
+<div class="mk-google">
+ <input type="search" v-model="query" :placeholder="q">
+ <button @click="search">検索</button>
+</div>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+export default Vue.extend({
+ props: ['q'],
+ data() {
+ return {
+ query: null
+ };
+ },
+ mounted() {
+ this.query = this.q;
+ },
+ methods: {
+ search() {
+ window.open(`https://www.google.com/?#q=${this.query}`, '_blank');
+ }
+ }
+});
+</script>
+
+<style lang="stylus" scoped>
+root(isDark)
+ display flex
+ margin 8px 0
+
+ > input
+ flex-shrink 1
+ padding 10px
+ width 100%
+ height 40px
+ font-family sans-serif
+ font-size 16px
+ color isDark ? #dee4e8 : #55595c
+ background isDark ? #191b22 : #fff
+ border solid 1px isDark ? #495156 : #dadada
+ border-radius 4px 0 0 4px
+
+ &:hover
+ border-color isDark ? #777c86 : #b0b0b0
+
+ > button
+ flex-shrink 0
+ padding 0 16px
+ border solid 1px isDark ? #495156 : #dadada
+ border-left none
+ border-radius 0 4px 4px 0
+
+ &:hover
+ background-color isDark ? #2e3440 : #eee
+
+ &:active
+ box-shadow 0 2px 4px rgba(#000, 0.15) inset
+
+.mk-google[data-darkmode]
+ root(true)
+
+.mk-google:not([data-darkmode])
+ root(false)
+
+</style>
diff --git a/src/client/app/common/views/components/index.ts b/src/client/app/common/views/components/index.ts
index 6bfe43a800..69fed00c74 100644
--- a/src/client/app/common/views/components/index.ts
+++ b/src/client/app/common/views/components/index.ts
@@ -3,6 +3,7 @@ import Vue from 'vue';
import signin from './signin.vue';
import signup from './signup.vue';
import forkit from './forkit.vue';
+import avatar from './avatar.vue';
import nav from './nav.vue';
import noteHtml from './note-html';
import poll from './poll.vue';
@@ -28,6 +29,7 @@ import welcomeTimeline from './welcome-timeline.vue';
Vue.component('mk-signin', signin);
Vue.component('mk-signup', signup);
Vue.component('mk-forkit', forkit);
+Vue.component('mk-avatar', avatar);
Vue.component('mk-nav', nav);
Vue.component('mk-note-html', noteHtml);
Vue.component('mk-poll', poll);
diff --git a/src/client/app/common/views/components/media-list.vue b/src/client/app/common/views/components/media-list.vue
index 64172ad0b4..ff9d5e1022 100644
--- a/src/client/app/common/views/components/media-list.vue
+++ b/src/client/app/common/views/components/media-list.vue
@@ -2,7 +2,7 @@
<div class="mk-media-list" :data-count="mediaList.length">
<template v-for="media in mediaList">
<mk-media-video :video="media" :key="media.id" v-if="media.type.startsWith('video')" :inline-playable="mediaList.length === 1"/>
- <mk-media-image :image="media" :key="media.id" v-else />
+ <mk-media-image :image="media" :key="media.id" v-else :raw="raw"/>
</template>
</div>
</template>
@@ -11,7 +11,14 @@
import Vue from 'vue';
export default Vue.extend({
- props: ['mediaList'],
+ props: {
+ mediaList: {
+ required: true
+ },
+ raw: {
+ default: false
+ }
+ }
});
</script>
@@ -23,7 +30,7 @@ export default Vue.extend({
@media (max-width 500px)
height 192px
-
+
&[data-count="1"]
grid-template-rows 1fr
&[data-count="2"]
@@ -40,7 +47,7 @@ export default Vue.extend({
&[data-count="4"]
grid-template-columns 1fr 1fr
grid-template-rows 1fr 1fr
-
+
:nth-child(1)
grid-column 1 / 2
grid-row 1 / 2
@@ -53,5 +60,5 @@ export default Vue.extend({
:nth-child(4)
grid-column 2 / 3
grid-row 2 / 3
-
+
</style>
diff --git a/src/client/app/common/views/components/messaging-room.message.vue b/src/client/app/common/views/components/messaging-room.message.vue
index afd700e777..ba0ab3209f 100644
--- a/src/client/app/common/views/components/messaging-room.message.vue
+++ b/src/client/app/common/views/components/messaging-room.message.vue
@@ -1,8 +1,6 @@
<template>
<div class="message" :data-is-me="isMe">
- <router-link class="avatar-anchor" :to="message.user | userPage" :title="message.user | acct" target="_blank">
- <img class="avatar" :src="`${message.user.avatarUrl}?thumbnail&size=80`" alt=""/>
- </router-link>
+ <mk-avatar class="avatar" :user="message.user" target="_blank"/>
<div class="content">
<div class="balloon" :data-no-text="message.text == null">
<p class="read" v-if="isMe && message.isRead">%i18n:@is-read%</p>
@@ -67,20 +65,14 @@ export default Vue.extend({
padding 10px 12px 10px 12px
background-color transparent
- > .avatar-anchor
+ > .avatar
display block
position absolute
top 10px
-
- > .avatar
- display block
- min-width 54px
- min-height 54px
- max-width 54px
- max-height 54px
- margin 0
- border-radius 8px
- transition all 0.1s ease
+ width 54px
+ height 54px
+ border-radius 8px
+ transition all 0.1s ease
> .content
@@ -134,7 +126,7 @@ export default Vue.extend({
bottom -4px
left -12px
margin 0
- color rgba(0, 0, 0, 0.5)
+ color rgba(#000, 0.5)
font-size 11px
> .content
@@ -146,7 +138,7 @@ export default Vue.extend({
overflow hidden
overflow-wrap break-word
font-size 1em
- color rgba(0, 0, 0, 0.5)
+ color rgba(#000, 0.5)
> .text
display block
@@ -155,7 +147,7 @@ export default Vue.extend({
overflow hidden
overflow-wrap break-word
font-size 1em
- color rgba(0, 0, 0, 0.8)
+ color rgba(#000, 0.8)
& + .file
> a
@@ -195,13 +187,13 @@ export default Vue.extend({
display block
margin 2px 0 0 0
font-size 10px
- color rgba(0, 0, 0, 0.4)
+ color rgba(#000, 0.4)
> [data-fa]
margin-left 4px
&:not([data-is-me])
- > .avatar-anchor
+ > .avatar
left 12px
> .content
@@ -225,7 +217,7 @@ export default Vue.extend({
text-align left
&[data-is-me]
- > .avatar-anchor
+ > .avatar
right 12px
> .content
diff --git a/src/client/app/common/views/components/messaging-room.vue b/src/client/app/common/views/components/messaging-room.vue
index 38202d7581..a45114e6bb 100644
--- a/src/client/app/common/views/components/messaging-room.vue
+++ b/src/client/app/common/views/components/messaging-room.vue
@@ -256,7 +256,7 @@ export default Vue.extend({
padding 16px 8px 8px 8px
text-align center
font-size 0.8em
- color rgba(0, 0, 0, 0.4)
+ color rgba(#000, 0.4)
[data-fa]
margin-right 4px
@@ -267,7 +267,7 @@ export default Vue.extend({
padding 16px 8px 8px 8px
text-align center
font-size 0.8em
- color rgba(0, 0, 0, 0.4)
+ color rgba(#000, 0.4)
[data-fa]
margin-right 4px
@@ -278,7 +278,7 @@ export default Vue.extend({
padding 16px
text-align center
font-size 0.8em
- color rgba(0, 0, 0, 0.4)
+ color rgba(#000, 0.4)
[data-fa]
margin-right 4px
@@ -289,14 +289,14 @@ export default Vue.extend({
padding 0 12px
line-height 24px
color #fff
- background rgba(0, 0, 0, 0.3)
+ background rgba(#000, 0.3)
border-radius 12px
&:hover
- background rgba(0, 0, 0, 0.4)
+ background rgba(#000, 0.4)
&:active
- background rgba(0, 0, 0, 0.5)
+ background rgba(#000, 0.5)
&.fetching
cursor wait
@@ -322,7 +322,7 @@ export default Vue.extend({
left 0
right 0
margin 0 auto
- background rgba(0, 0, 0, 0.1)
+ background rgba(#000, 0.1)
> span
display inline-block
@@ -330,7 +330,7 @@ export default Vue.extend({
padding 0 16px
//font-weight bold
line-height 32px
- color rgba(0, 0, 0, 0.3)
+ color rgba(#000, 0.3)
background #fff
> footer
diff --git a/src/client/app/common/views/components/messaging.vue b/src/client/app/common/views/components/messaging.vue
index f74d9643eb..11f9c366d4 100644
--- a/src/client/app/common/views/components/messaging.vue
+++ b/src/client/app/common/views/components/messaging.vue
@@ -13,7 +13,7 @@
@click="navigate(user)"
tabindex="-1"
>
- <img class="avatar" :src="`${user.avatarUrl}?thumbnail&size=32`" alt=""/>
+ <mk-avatar class="avatar" :user="user"/>
<span class="name">{{ user | userName }}</span>
<span class="username">@{{ user | acct }}</span>
</li>
@@ -31,7 +31,7 @@
:key="message.id"
>
<div>
- <img class="avatar" :src="`${isMe(message) ? message.recipient.avatarUrl : message.user.avatarUrl}?thumbnail&size=64`" alt=""/>
+ <mk-avatar class="avatar" :user="isMe(message) ? message.recipient : message.user"/>
<header>
<span class="name">{{ isMe(message) ? message.recipient : message.user | userName }}</span>
<span class="username">@{{ isMe(message) ? message.recipient : message.user | acct }}</span>
@@ -169,7 +169,7 @@ export default Vue.extend({
<style lang="stylus" scoped>
@import '~const.styl'
-.mk-messaging
+root(isDark)
&[data-compact]
font-size 0.8em
@@ -205,11 +205,11 @@ export default Vue.extend({
z-index 1
width 100%
background #fff
- box-shadow 0 0px 2px rgba(0, 0, 0, 0.2)
+ box-shadow 0 0px 2px rgba(#000, 0.2)
> .form
padding 8px
- background #f7f7f7
+ background isDark ? #282c37 : #f7f7f7
> label
display block
@@ -241,13 +241,14 @@ export default Vue.extend({
line-height 38px
color #000
outline none
- border solid 1px #eee
+ background isDark ? #191b22 : #fff
+ border solid 1px isDark ? #495156 : #eee
border-radius 5px
box-shadow none
transition color 0.5s ease, border 0.5s ease
&:hover
- border solid 1px #ddd
+ border solid 1px isDark ? #b0b0b0 : #ddd
transition border 0.2s ease
&:focus
@@ -278,7 +279,7 @@ export default Vue.extend({
vertical-align top
white-space nowrap
overflow hidden
- color rgba(0, 0, 0, 0.8)
+ color rgba(#000, 0.8)
text-decoration none
transition none
cursor pointer
@@ -317,32 +318,32 @@ export default Vue.extend({
margin 0 8px 0 0
/*font-weight bold*/
font-weight normal
- color rgba(0, 0, 0, 0.8)
+ color rgba(#000, 0.8)
.username
font-weight normal
- color rgba(0, 0, 0, 0.3)
+ color rgba(#000, 0.3)
> .history
> a
display block
text-decoration none
- background #fff
- border-bottom solid 1px #eee
+ background isDark ? #282c37 : #fff
+ border-bottom solid 1px isDark ? #1c2023 : #eee
*
pointer-events none
user-select none
&:hover
- background #fafafa
+ background isDark ? #1e2129 : #fafafa
> .avatar
filter saturate(200%)
&:active
- background #eee
+ background isDark ? #14161b : #eee
&[data-is-read]
&[data-is-me]
@@ -382,17 +383,17 @@ export default Vue.extend({
overflow hidden
text-overflow ellipsis
font-size 1em
- color rgba(0, 0, 0, 0.9)
+ color isDark ? #fff : rgba(#000, 0.9)
font-weight bold
transition all 0.1s ease
> .username
margin 0 8px
- color rgba(0, 0, 0, 0.5)
+ color isDark ? #606984 : rgba(#000, 0.5)
> .mk-time
margin 0 0 0 auto
- color rgba(0, 0, 0, 0.5)
+ color isDark ? #606984 : rgba(#000, 0.5)
font-size 80%
> .avatar
@@ -412,10 +413,10 @@ export default Vue.extend({
overflow hidden
overflow-wrap break-word
font-size 1.1em
- color rgba(0, 0, 0, 0.8)
+ color isDark ? #fff : rgba(#000, 0.8)
.me
- color rgba(0, 0, 0, 0.4)
+ color isDark ? rgba(#fff, 0.7) : rgba(#000, 0.4)
> .image
display block
@@ -460,4 +461,10 @@ export default Vue.extend({
> .avatar
margin 0 12px 0 0
+.mk-messaging[data-darkmode]
+ root(true)
+
+.mk-messaging:not([data-darkmode])
+ root(false)
+
</style>
diff --git a/src/client/app/common/views/components/nav.vue b/src/client/app/common/views/components/nav.vue
index 2295957928..cd1f99288a 100644
--- a/src/client/app/common/views/components/nav.vue
+++ b/src/client/app/common/views/components/nav.vue
@@ -2,16 +2,10 @@
<span class="mk-nav">
<a :href="aboutUrl">%i18n:@about%</a>
<i>・</i>
- <a :href="statsUrl">%i18n:@stats%</a>
- <i>・</i>
- <a :href="statusUrl">%i18n:@status%</a>
- <i>・</i>
- <a href="http://zawazawa.jp/misskey/">%i18n:@wiki%</a>
- <i>・</i>
- <a href="https://github.com/syuilo/misskey/blob/master/DONORS.md">%i18n:@donors%</a>
- <i>・</i>
<a href="https://github.com/syuilo/misskey">%i18n:@repository%</a>
<i>・</i>
+ <a href="https://github.com/syuilo/misskey/issues/new" target="_blank">%i18n:@feedback%</a>
+ <i>・</i>
<a :href="devUrl">%i18n:@develop%</a>
<i>・</i>
<a href="https://twitter.com/misskey_xyz" target="_blank">Follow us on %fa:B twitter%</a>
diff --git a/src/client/app/common/views/components/note-html.ts b/src/client/app/common/views/components/note-html.ts
index 24e750a671..f86b50659e 100644
--- a/src/client/app/common/views/components/note-html.ts
+++ b/src/client/app/common/views/components/note-html.ts
@@ -4,6 +4,7 @@ import parse from '../../../../../text/parse';
import getAcct from '../../../../../acct/render';
import { url } from '../../../config';
import MkUrl from './url.vue';
+import MkGoogle from './google.vue';
const flatten = list => list.reduce(
(a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), []
@@ -97,7 +98,9 @@ export default Vue.component('mk-note-html', {
}, token.content);
case 'code':
- return createElement('pre', [
+ return createElement('pre', {
+ class: 'code'
+ }, [
createElement('code', {
domProps: {
innerHTML: token.html
@@ -132,10 +135,24 @@ export default Vue.component('mk-note-html', {
}, text2.replace(/\n/g, ' '));
}
+ case 'title':
+ return createElement('div', {
+ attrs: {
+ class: 'title'
+ }
+ }, token.title);
+
case 'emoji':
const emoji = emojilib.lib[token.emoji];
return createElement('span', emoji ? emoji.char : token.content);
+ case 'search':
+ return createElement(MkGoogle, {
+ props: {
+ q: token.query
+ }
+ });
+
default:
console.log('unknown ast type:', token.type);
}
@@ -144,7 +161,7 @@ export default Vue.component('mk-note-html', {
const _els = [];
els.forEach((el, i) => {
if (el.tag == 'br') {
- if (els[i - 1].tag != 'div') {
+ if (!['div', 'pre'].includes(els[i - 1].tag)) {
_els.push(el);
}
} else {
diff --git a/src/client/app/common/views/components/note-menu.vue b/src/client/app/common/views/components/note-menu.vue
index 877d2c16bb..88dc22aaf4 100644
--- a/src/client/app/common/views/components/note-menu.vue
+++ b/src/client/app/common/views/components/note-menu.vue
@@ -2,6 +2,7 @@
<div class="mk-note-menu">
<div class="backdrop" ref="backdrop" @click="close"></div>
<div class="popover" :class="{ compact }" ref="popover">
+ <button @click="favorite">%i18n:@favorite%</button>
<button v-if="note.userId == os.i.id" @click="pin">%i18n:@pin%</button>
<a v-if="note.uri" :href="note.uri" target="_blank">%i18n:@remote%</a>
</div>
@@ -58,6 +59,14 @@ export default Vue.extend({
});
},
+ favorite() {
+ (this as any).api('notes/favorites/create', {
+ noteId: this.note.id
+ }).then(() => {
+ this.$destroy();
+ });
+ },
+
close() {
(this.$refs.backdrop as any).style.pointerEvents = 'none';
anime({
@@ -96,7 +105,7 @@ $border-color = rgba(27, 31, 35, 0.15)
z-index 10000
width 100%
height 100%
- background rgba(0, 0, 0, 0.1)
+ background rgba(#000, 0.1)
opacity 0
> .popover
@@ -142,6 +151,7 @@ $border-color = rgba(27, 31, 35, 0.15)
> a
display block
padding 8px 16px
+ width 100%
&:hover
color $theme-color-foreground
diff --git a/src/client/app/common/views/components/othello.vue b/src/client/app/common/views/components/othello.vue
index 8f7d9dfd6a..a0971c45b4 100644
--- a/src/client/app/common/views/components/othello.vue
+++ b/src/client/app/common/views/components/othello.vue
@@ -31,7 +31,7 @@
<section v-if="invitations.length > 0">
<h2>対局の招待があります!:</h2>
<div class="invitation" v-for="i in invitations" tabindex="-1" @click="accept(i)">
- <img :src="`${i.parent.avatarUrl}?thumbnail&size=32`" alt="">
+ <mk-avatar class="avatar" :user="i.parent"/>
<span class="name"><b>{{ i.parent.name }}</b></span>
<span class="username">@{{ i.parent.username }}</span>
<mk-time :time="i.createdAt"/>
@@ -40,8 +40,8 @@
<section v-if="myGames.length > 0">
<h2>自分の対局</h2>
<a class="game" v-for="g in myGames" tabindex="-1" @click.prevent="go(g)" :href="`/othello/${g.id}`">
- <img :src="`${g.user1.avatarUrl}?thumbnail&size=32`" alt="">
- <img :src="`${g.user2.avatarUrl}?thumbnail&size=32`" alt="">
+ <mk-avatar class="avatar" :user="g.user1"/>
+ <mk-avatar class="avatar" :user="g.user2"/>
<span><b>{{ g.user1.name }}</b> vs <b>{{ g.user2.name }}</b></span>
<span class="state">{{ g.isEnded ? '終了' : '進行中' }}</span>
</a>
@@ -49,8 +49,8 @@
<section v-if="games.length > 0">
<h2>みんなの対局</h2>
<a class="game" v-for="g in games" tabindex="-1" @click.prevent="go(g)" :href="`/othello/${g.id}`">
- <img :src="`${g.user1.avatarUrl}?thumbnail&size=32`" alt="">
- <img :src="`${g.user2.avatarUrl}?thumbnail&size=32`" alt="">
+ <mk-avatar class="avatar" :user="g.user1"/>
+ <mk-avatar class="avatar" :user="g.user2"/>
<span><b>{{ g.user1.name }}</b> vs <b>{{ g.user2.name }}</b></span>
<span class="state">{{ g.isEnded ? '終了' : '進行中' }}</span>
</a>
@@ -271,8 +271,9 @@ export default Vue.extend({
&:active
background #eee
- > img
- vertical-align bottom
+ > .avatar
+ width 32px
+ height 32px
border-radius 100%
> span
@@ -301,8 +302,9 @@ export default Vue.extend({
&:active
background #eee
- > img
- vertical-align bottom
+ > .avatar
+ width 32px
+ height 32px
border-radius 100%
> span
diff --git a/src/client/app/common/views/components/poll-editor.vue b/src/client/app/common/views/components/poll-editor.vue
index 189172679b..95bcba996e 100644
--- a/src/client/app/common/views/components/poll-editor.vue
+++ b/src/client/app/common/views/components/poll-editor.vue
@@ -69,7 +69,7 @@ export default Vue.extend({
<style lang="stylus" scoped>
@import '~const.styl'
-.mk-poll-editor
+root(isDark)
padding 8px
> .caution
@@ -102,6 +102,8 @@ export default Vue.extend({
padding 6px 8px
width 300px
font-size 14px
+ color isDark ? #fff : #000
+ background isDark ? #191b22 : #fff
border solid 1px rgba($theme-color, 0.1)
border-radius 4px
@@ -139,4 +141,10 @@ export default Vue.extend({
&:active
color darken($theme-color, 30%)
+.mk-poll-editor[data-darkmode]
+ root(true)
+
+.mk-poll-editor:not([data-darkmode])
+ root(false)
+
</style>
diff --git a/src/client/app/common/views/components/poll.vue b/src/client/app/common/views/components/poll.vue
index 1834d4ddc2..46e41cbcdb 100644
--- a/src/client/app/common/views/components/poll.vue
+++ b/src/client/app/common/views/components/poll.vue
@@ -68,7 +68,7 @@ export default Vue.extend({
<style lang="stylus" scoped>
@import '~const.styl'
-.mk-poll
+root(isDark)
> ul
display block
@@ -81,16 +81,17 @@ export default Vue.extend({
margin 4px 0
padding 4px 8px
width 100%
- border solid 1px #eee
+ color isDark ? #fff : #000
+ border solid 1px isDark ? #5e636f : #eee
border-radius 4px
overflow hidden
cursor pointer
&:hover
- background rgba(0, 0, 0, 0.05)
+ background rgba(#000, 0.05)
&:active
- background rgba(0, 0, 0, 0.1)
+ background rgba(#000, 0.1)
> .backdrop
position absolute
@@ -108,6 +109,8 @@ export default Vue.extend({
margin-left 4px
> p
+ color isDark ? #a3aebf : #000
+
a
color inherit
@@ -121,4 +124,10 @@ export default Vue.extend({
&:active
background transparent
+.mk-poll[data-darkmode]
+ root(true)
+
+.mk-poll:not([data-darkmode])
+ root(false)
+
</style>
diff --git a/src/client/app/common/views/components/reaction-picker.vue b/src/client/app/common/views/components/reaction-picker.vue
index 267eeb3a14..e2c8a6ed3f 100644
--- a/src/client/app/common/views/components/reaction-picker.vue
+++ b/src/client/app/common/views/components/reaction-picker.vue
@@ -110,7 +110,7 @@ export default Vue.extend({
$border-color = rgba(27, 31, 35, 0.15)
-.mk-reaction-picker
+root(isDark)
position initial
> .backdrop
@@ -120,13 +120,14 @@ $border-color = rgba(27, 31, 35, 0.15)
z-index 10000
width 100%
height 100%
- background rgba(0, 0, 0, 0.1)
+ background isDark ? rgba(#000, 0.4) : rgba(#000, 0.1)
opacity 0
> .popover
+ $bgcolor = isDark ? #2c303c : #fff
position absolute
z-index 10001
- background #fff
+ background $bgcolor
border 1px solid $border-color
border-radius 4px
box-shadow 0 3px 12px rgba(27, 31, 35, 0.15)
@@ -159,15 +160,15 @@ $border-color = rgba(27, 31, 35, 0.15)
border-top solid $balloon-size transparent
border-left solid $balloon-size transparent
border-right solid $balloon-size transparent
- border-bottom solid $balloon-size #fff
+ border-bottom solid $balloon-size $bgcolor
> p
display block
margin 0
padding 8px 10px
font-size 14px
- color #586069
- border-bottom solid 1px #e1e4e8
+ color isDark ? #d6dce2 : #586069
+ border-bottom solid 1px isDark ? #1c2023 : #e1e4e8
> div
padding 4px
@@ -182,10 +183,16 @@ $border-color = rgba(27, 31, 35, 0.15)
border-radius 2px
&:hover
- background #eee
+ background isDark ? #252731 : #eee
&:active
background $theme-color
box-shadow inset 0 0.15em 0.3em rgba(27, 31, 35, 0.15)
+.mk-reaction-picker[data-darkmode]
+ root(true)
+
+.mk-reaction-picker:not([data-darkmode])
+ root(false)
+
</style>
diff --git a/src/client/app/common/views/components/reactions-viewer.vue b/src/client/app/common/views/components/reactions-viewer.vue
index 1afcf525d2..97cb6be17c 100644
--- a/src/client/app/common/views/components/reactions-viewer.vue
+++ b/src/client/app/common/views/components/reactions-viewer.vue
@@ -1,15 +1,15 @@
<template>
<div class="mk-reactions-viewer">
<template v-if="reactions">
- <span v-if="reactions.like"><mk-reaction-icon reaction='like'/><span>{{ reactions.like }}</span></span>
- <span v-if="reactions.love"><mk-reaction-icon reaction='love'/><span>{{ reactions.love }}</span></span>
- <span v-if="reactions.laugh"><mk-reaction-icon reaction='laugh'/><span>{{ reactions.laugh }}</span></span>
- <span v-if="reactions.hmm"><mk-reaction-icon reaction='hmm'/><span>{{ reactions.hmm }}</span></span>
- <span v-if="reactions.surprise"><mk-reaction-icon reaction='surprise'/><span>{{ reactions.surprise }}</span></span>
- <span v-if="reactions.congrats"><mk-reaction-icon reaction='congrats'/><span>{{ reactions.congrats }}</span></span>
- <span v-if="reactions.angry"><mk-reaction-icon reaction='angry'/><span>{{ reactions.angry }}</span></span>
- <span v-if="reactions.confused"><mk-reaction-icon reaction='confused'/><span>{{ reactions.confused }}</span></span>
- <span v-if="reactions.pudding"><mk-reaction-icon reaction='pudding'/><span>{{ reactions.pudding }}</span></span>
+ <span v-if="reactions.like"><mk-reaction-icon reaction="like"/><span>{{ reactions.like }}</span></span>
+ <span v-if="reactions.love"><mk-reaction-icon reaction="love"/><span>{{ reactions.love }}</span></span>
+ <span v-if="reactions.laugh"><mk-reaction-icon reaction="laugh"/><span>{{ reactions.laugh }}</span></span>
+ <span v-if="reactions.hmm"><mk-reaction-icon reaction="hmm"/><span>{{ reactions.hmm }}</span></span>
+ <span v-if="reactions.surprise"><mk-reaction-icon reaction="surprise"/><span>{{ reactions.surprise }}</span></span>
+ <span v-if="reactions.congrats"><mk-reaction-icon reaction="congrats"/><span>{{ reactions.congrats }}</span></span>
+ <span v-if="reactions.angry"><mk-reaction-icon reaction="angry"/><span>{{ reactions.angry }}</span></span>
+ <span v-if="reactions.confused"><mk-reaction-icon reaction="confused"/><span>{{ reactions.confused }}</span></span>
+ <span v-if="reactions.pudding"><mk-reaction-icon reaction="pudding"/><span>{{ reactions.pudding }}</span></span>
</template>
</div>
</template>
@@ -27,9 +27,10 @@ export default Vue.extend({
</script>
<style lang="stylus" scoped>
-.mk-reactions-viewer
- border-top dashed 1px #eee
- border-bottom dashed 1px #eee
+root(isDark)
+ $borderColor = isDark ? #5e6673 : #eee
+ border-top dashed 1px $borderColor
+ border-bottom dashed 1px $borderColor
margin 4px 0
&:empty
@@ -44,6 +45,12 @@ export default Vue.extend({
> span
margin-left 4px
font-size 1.2em
- color #444
+ color isDark ? #d1d5dc : #444
+
+.mk-reactions-viewer[data-darkmode]
+ root(true)
+
+.mk-reactions-viewer:not([data-darkmode])
+ root(false)
</style>
diff --git a/src/client/app/common/views/components/signin.vue b/src/client/app/common/views/components/signin.vue
index 25f90a2f13..7fb9fc3fd4 100644
--- a/src/client/app/common/views/components/signin.vue
+++ b/src/client/app/common/views/components/signin.vue
@@ -91,7 +91,7 @@ export default Vue.extend({
width 100%
line-height 44px
font-size 1em
- color rgba(0, 0, 0, 0.7)
+ color rgba(#000, 0.7)
background #fff
outline none
border solid 1px #eee
@@ -117,7 +117,7 @@ export default Vue.extend({
margin -6px 0 0 0
width 100%
font-size 1.2em
- color rgba(0, 0, 0, 0.5)
+ color rgba(#000, 0.5)
outline none
border none
border-radius 0
diff --git a/src/client/app/common/views/components/signup.vue b/src/client/app/common/views/components/signup.vue
index 33a559ff8f..516979acd0 100644
--- a/src/client/app/common/views/components/signup.vue
+++ b/src/client/app/common/views/components/signup.vue
@@ -234,13 +234,13 @@ export default Vue.extend({
color #333 !important
background #fff !important
outline none
- border solid 1px rgba(0, 0, 0, 0.1)
+ border solid 1px rgba(#000, 0.1)
border-radius 4px
box-shadow 0 0 0 114514px #fff inset
transition all .3s ease
&:hover
- border-color rgba(0, 0, 0, 0.2)
+ border-color rgba(#000, 0.2)
transition all .1s ease
&:focus
diff --git a/src/client/app/common/views/components/stream-indicator.vue b/src/client/app/common/views/components/stream-indicator.vue
index 93758102de..d573db32e6 100644
--- a/src/client/app/common/views/components/stream-indicator.vue
+++ b/src/client/app/common/views/components/stream-indicator.vue
@@ -73,7 +73,7 @@ export default Vue.extend({
padding 6px 12px
font-size 0.9em
color #fff
- background rgba(0, 0, 0, 0.8)
+ background rgba(#000, 0.8)
border-radius 4px
> p
diff --git a/src/client/app/common/views/components/switch.vue b/src/client/app/common/views/components/switch.vue
index 19a4adc3de..32caab638a 100644
--- a/src/client/app/common/views/components/switch.vue
+++ b/src/client/app/common/views/components/switch.vue
@@ -87,7 +87,7 @@ export default Vue.extend({
<style lang="stylus" scoped>
@import '~const.styl'
-.mk-switch
+root(isDark)
display flex
margin 12px 0
cursor pointer
@@ -121,11 +121,12 @@ export default Vue.extend({
&:hover
> .label
> span
- color #2e3338
+ color isDark ? #fff : #2e3338
> .button
- background #ced2da
- border-color #ced2da
+ $color = isDark ? #15181d : #ced2da
+ background $color
+ border-color $color
> input
position absolute
@@ -147,14 +148,16 @@ export default Vue.extend({
border-radius 14px
> .button
+ $color = isDark ? #1c1f25 : #dcdfe6
+
display inline-block
margin 0
width 40px
min-width 40px
height 20px
min-height 20px
- background #dcdfe6
- border 1px solid #dcdfe6
+ background $color
+ border 1px solid $color
outline none
border-radius 10px
transition inherit
@@ -179,12 +182,18 @@ export default Vue.extend({
> span
display block
line-height 20px
- color #4a535a
+ color isDark ? #c4ccd2 : #4a535a
transition inherit
> p
margin 0
//font-size 90%
- color #9daab3
+ color isDark ? #78858e : #9daab3
+
+.mk-switch[data-darkmode]
+ root(true)
+
+.mk-switch:not([data-darkmode])
+ root(false)
</style>
diff --git a/src/client/app/common/views/components/twitter-setting.vue b/src/client/app/common/views/components/twitter-setting.vue
index 6ca1037aba..ab07e6d09a 100644
--- a/src/client/app/common/views/components/twitter-setting.vue
+++ b/src/client/app/common/views/components/twitter-setting.vue
@@ -50,8 +50,6 @@ export default Vue.extend({
<style lang="stylus" scoped>
.mk-twitter-setting
- color #4a535a
-
.account
border solid 1px #e1e8ed
border-radius 4px
diff --git a/src/client/app/common/views/components/url-preview.vue b/src/client/app/common/views/components/url-preview.vue
index fd25480f61..3bae6e5078 100644
--- a/src/client/app/common/views/components/url-preview.vue
+++ b/src/client/app/common/views/components/url-preview.vue
@@ -2,8 +2,8 @@
<iframe v-if="youtubeId" type="text/html" height="250"
:src="`https://www.youtube.com/embed/${youtubeId}?origin=${misskeyUrl}`"
frameborder="0"/>
-<div v-else>
- <a class="mk-url-preview" :href="url" target="_blank" :title="url" v-if="!fetching">
+<div v-else class="mk-url-preview">
+ <a :href="url" target="_blank" :title="url" v-if="!fetching">
<div class="thumbnail" v-if="thumbnail" :style="`background-image: url(${thumbnail})`"></div>
<article>
<header>
@@ -45,7 +45,7 @@ export default Vue.extend({
} else if (url.hostname == 'youtu.be') {
this.youtubeId = url.pathname;
} else {
- fetch('/url?url=' + this.url).then(res => {
+ fetch('/url?url=' + encodeURIComponent(this.url)).then(res => {
res.json().then(info => {
this.title = info.title;
this.description = info.description;
@@ -65,78 +65,85 @@ export default Vue.extend({
iframe
width 100%
-.mk-url-preview
- display block
- font-size 16px
- border solid 1px #eee
- border-radius 4px
- overflow hidden
+root(isDark)
+ > a
+ display block
+ font-size 16px
+ border solid 1px isDark ? #191b1f : #eee
+ border-radius 4px
+ overflow hidden
- &:hover
- text-decoration none
- border-color #ddd
+ &:hover
+ text-decoration none
+ border-color isDark ? #4f5561 : #ddd
- > article > header > h1
- text-decoration underline
+ > article > header > h1
+ text-decoration underline
- > .thumbnail
- position absolute
- width 100px
- height 100%
- background-position center
- background-size cover
+ > .thumbnail
+ position absolute
+ width 100px
+ height 100%
+ background-position center
+ background-size cover
+
+ & + article
+ left 100px
+ width calc(100% - 100px)
- & + article
- left 100px
- width calc(100% - 100px)
+ > article
+ padding 16px
- > article
- padding 16px
+ > header
+ margin-bottom 8px
- > header
- margin-bottom 8px
+ > h1
+ margin 0
+ font-size 1em
+ color isDark ? #d6dae0 : #555
- > h1
+ > p
margin 0
- font-size 1em
- color #555
+ color isDark ? #a4aab3 : #777
+ font-size 0.8em
- > p
- margin 0
- color #777
- font-size 0.8em
+ > footer
+ margin-top 8px
+ height 16px
- > footer
- margin-top 8px
- height 16px
+ > img
+ display inline-block
+ width 16px
+ height 16px
+ margin-right 4px
+ vertical-align top
- > img
- display inline-block
- width 16px
- height 16px
- margin-right 4px
- vertical-align top
+ > p
+ display inline-block
+ margin 0
+ color isDark ? #b0b4bf : #666
+ font-size 0.8em
+ line-height 16px
+ vertical-align top
- > p
- display inline-block
- margin 0
- color #666
- font-size 0.8em
- line-height 16px
- vertical-align top
+ @media (max-width 500px)
+ font-size 8px
+ border none
- @media (max-width 500px)
- font-size 8px
- border none
+ > .thumbnail
+ width 70px
- > .thumbnail
- width 70px
+ & + article
+ left 70px
+ width calc(100% - 70px)
- & + article
- left 70px
- width calc(100% - 70px)
+ > article
+ padding 8px
- > article
- padding 8px
+.mk-url-preview[data-darkmode]
+ root(true)
+
+.mk-url-preview:not([data-darkmode])
+ root(false)
</style>
diff --git a/src/client/app/common/views/components/visibility-chooser.vue b/src/client/app/common/views/components/visibility-chooser.vue
new file mode 100644
index 0000000000..50f0877ae9
--- /dev/null
+++ b/src/client/app/common/views/components/visibility-chooser.vue
@@ -0,0 +1,223 @@
+<template>
+<div class="mk-visibility-chooser">
+ <div class="backdrop" ref="backdrop" @click="close"></div>
+ <div class="popover" :class="{ compact }" ref="popover">
+ <div @click="choose('public')" :class="{ active: v == 'public' }">
+ <div>%fa:globe%</div>
+ <div>
+ <span>公開</span>
+ </div>
+ </div>
+ <div @click="choose('home')" :class="{ active: v == 'home' }">
+ <div>%fa:home%</div>
+ <div>
+ <span>ホーム</span>
+ <span>ホームタイムラインにのみ公開</span>
+ </div>
+ </div>
+ <div @click="choose('followers')" :class="{ active: v == 'followers' }">
+ <div>%fa:unlock%</div>
+ <div>
+ <span>フォロワー</span>
+ <span>自分のフォロワーにのみ公開</span>
+ </div>
+ </div>
+ <div @click="choose('specified')" :class="{ active: v == 'specified' }">
+ <div>%fa:envelope%</div>
+ <div>
+ <span>ダイレクト</span>
+ <span>指定したユーザーにのみ公開</span>
+ </div>
+ </div>
+ <div @click="choose('private')" :class="{ active: v == 'private' }">
+ <div>%fa:lock%</div>
+ <div>
+ <span>非公開</span>
+ </div>
+ </div>
+ </div>
+</div>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+import * as anime from 'animejs';
+
+export default Vue.extend({
+ props: ['source', 'compact', 'v'],
+ mounted() {
+ this.$nextTick(() => {
+ const popover = this.$refs.popover as any;
+
+ const rect = this.source.getBoundingClientRect();
+ const width = popover.offsetWidth;
+ const height = popover.offsetHeight;
+
+ let left;
+ let top;
+
+ if (this.compact) {
+ const x = rect.left + window.pageXOffset + (this.source.offsetWidth / 2);
+ const y = rect.top + window.pageYOffset + (this.source.offsetHeight / 2);
+ left = (x - (width / 2));
+ top = (y - (height / 2));
+ } else {
+ const x = rect.left + window.pageXOffset + (this.source.offsetWidth / 2);
+ const y = rect.top + window.pageYOffset + this.source.offsetHeight;
+ left = (x - (width / 2));
+ top = y;
+ }
+
+ if (left + width > window.innerWidth) {
+ left = window.innerWidth - width;
+ }
+
+ popover.style.left = left + 'px';
+ popover.style.top = top + 'px';
+
+ anime({
+ targets: this.$refs.backdrop,
+ opacity: 1,
+ duration: 100,
+ easing: 'linear'
+ });
+
+ anime({
+ targets: this.$refs.popover,
+ opacity: 1,
+ scale: [0.5, 1],
+ duration: 500
+ });
+ });
+ },
+ methods: {
+ choose(visibility) {
+ this.$emit('chosen', visibility);
+ this.$destroy();
+ },
+ close() {
+ (this.$refs.backdrop as any).style.pointerEvents = 'none';
+ anime({
+ targets: this.$refs.backdrop,
+ opacity: 0,
+ duration: 200,
+ easing: 'linear'
+ });
+
+ (this.$refs.popover as any).style.pointerEvents = 'none';
+ anime({
+ targets: this.$refs.popover,
+ opacity: 0,
+ scale: 0.5,
+ duration: 200,
+ easing: 'easeInBack',
+ complete: () => this.$destroy()
+ });
+ }
+ }
+});
+</script>
+
+<style lang="stylus" scoped>
+@import '~const.styl'
+
+$border-color = rgba(27, 31, 35, 0.15)
+
+root(isDark)
+ position initial
+
+ > .backdrop
+ position fixed
+ top 0
+ left 0
+ z-index 10000
+ width 100%
+ height 100%
+ background isDark ? rgba(#000, 0.4) : rgba(#000, 0.1)
+ opacity 0
+
+ > .popover
+ $bgcolor = isDark ? #2c303c : #fff
+ position absolute
+ z-index 10001
+ width 240px
+ padding 8px 0
+ background $bgcolor
+ border 1px solid $border-color
+ border-radius 4px
+ box-shadow 0 3px 12px rgba(27, 31, 35, 0.15)
+ transform scale(0.5)
+ opacity 0
+
+ $balloon-size = 10px
+
+ &:not(.compact)
+ margin-top $balloon-size
+ transform-origin center -($balloon-size)
+
+ &:before
+ content ""
+ display block
+ position absolute
+ top -($balloon-size * 2)
+ left s('calc(50% - %s)', $balloon-size)
+ border-top solid $balloon-size transparent
+ border-left solid $balloon-size transparent
+ border-right solid $balloon-size transparent
+ border-bottom solid $balloon-size $border-color
+
+ &:after
+ content ""
+ display block
+ position absolute
+ top -($balloon-size * 2) + 1.5px
+ left s('calc(50% - %s)', $balloon-size)
+ border-top solid $balloon-size transparent
+ border-left solid $balloon-size transparent
+ border-right solid $balloon-size transparent
+ border-bottom solid $balloon-size $bgcolor
+
+ > div
+ display flex
+ padding 8px 14px
+ font-size 12px
+ color isDark ? #fff : #666
+ cursor pointer
+
+ &:hover
+ background isDark ? #252731 : #eee
+
+ &:active
+ background isDark ? #21242b : #ddd
+
+ &.active
+ color $theme-color-foreground
+ background $theme-color
+
+ > *
+ user-select none
+ pointer-events none
+
+ > *:first-child
+ display flex
+ justify-content center
+ align-items center
+ margin-right 10px
+
+ > *:last-child
+ flex 1 1 auto
+
+ > span:first-child
+ display block
+ font-weight bold
+
+ > span:last-child:not(:first-child)
+ opacity 0.6
+
+.mk-visibility-chooser[data-darkmode]
+ root(true)
+
+.mk-visibility-chooser:not([data-darkmode])
+ root(false)
+
+</style>
diff --git a/src/client/app/common/views/components/welcome-timeline.vue b/src/client/app/common/views/components/welcome-timeline.vue
index a80bc04f7f..6fadb030c3 100644
--- a/src/client/app/common/views/components/welcome-timeline.vue
+++ b/src/client/app/common/views/components/welcome-timeline.vue
@@ -1,9 +1,7 @@
<template>
<div class="mk-welcome-timeline">
<div v-for="note in notes">
- <router-link class="avatar-anchor" :to="note.user | userPage" v-user-preview="note.user.id">
- <img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=96`" alt="avatar"/>
- </router-link>
+ <mk-avatar class="avatar" :user="note.user" target="_blank"/>
<div class="body">
<header>
<router-link class="name" :to="note.user | userPage" v-user-preview="note.user.id">{{ note.user | userName }}</router-link>
@@ -62,25 +60,22 @@ export default Vue.extend({
overflow-wrap break-word
font-size .9em
color #4C4C4C
- border-bottom 1px solid rgba(0, 0, 0, 0.05)
+ border-bottom 1px solid rgba(#000, 0.05)
&:after
content ""
display block
clear both
- > .avatar-anchor
+ > .avatar
display block
float left
position -webkit-sticky
position sticky
top 16px
-
- > img
- display block
- width 42px
- height 42px
- border-radius 6px
+ width 42px
+ height 42px
+ border-radius 6px
> .body
float right
diff --git a/src/client/app/common/views/widgets/access-log.vue b/src/client/app/common/views/widgets/access-log.vue
index 637ba328c6..8652e35645 100644
--- a/src/client/app/common/views/widgets/access-log.vue
+++ b/src/client/app/common/views/widgets/access-log.vue
@@ -61,6 +61,7 @@ export default define({
} else {
this.props.design++;
}
+ this.save();
}
}
});
@@ -78,7 +79,7 @@ export default define({
color #555
&:nth-child(odd)
- background rgba(0, 0, 0, 0.025)
+ background rgba(#000, 0.025)
> b
margin-right 4px
diff --git a/src/client/app/common/views/widgets/broadcast.vue b/src/client/app/common/views/widgets/broadcast.vue
index 96d1d0ef3a..75b1d60524 100644
--- a/src/client/app/common/views/widgets/broadcast.vue
+++ b/src/client/app/common/views/widgets/broadcast.vue
@@ -68,6 +68,7 @@ export default define({
} else {
this.props.design++;
}
+ this.save();
}
}
});
diff --git a/src/client/app/common/views/widgets/calendar.vue b/src/client/app/common/views/widgets/calendar.vue
index 03f69a7597..41e9253784 100644
--- a/src/client/app/common/views/widgets/calendar.vue
+++ b/src/client/app/common/views/widgets/calendar.vue
@@ -73,6 +73,7 @@ export default define({
} else {
this.props.design++;
}
+ this.save();
},
tick() {
const now = new Date();
@@ -109,11 +110,11 @@ export default define({
<style lang="stylus" scoped>
@import '~const.styl'
-.mkw-calendar
+root(isDark)
padding 16px 0
- color #777
- background #fff
- border solid 1px rgba(0, 0, 0, 0.075)
+ color isDark ? #c5ced6 :#777
+ background isDark ? #282C37 : #fff
+ border solid 1px rgba(#000, 0.075)
border-radius 6px
&[data-special='on-new-years-day']
@@ -126,7 +127,7 @@ export default define({
&[data-mobile]
border none
border-radius 8px
- box-shadow 0 0 0 1px rgba(0, 0, 0, 0.2)
+ box-shadow 0 0 0 1px rgba(#000, 0.2)
&:after
content ""
@@ -171,7 +172,7 @@ export default define({
margin 0 0 2px 0
font-size 12px
line-height 18px
- color #888
+ color isDark ? #7a8692 : #888
> b
margin-left 2px
@@ -179,7 +180,7 @@ export default define({
> .meter
width 100%
overflow hidden
- background #eee
+ background isDark ? #1c1f25 : #eee
border-radius 8px
> .val
@@ -198,4 +199,10 @@ export default define({
> .meter > .val
background #41ddde
+.mkw-calendar[data-darkmode]
+ root(true)
+
+.mkw-calendar:not([data-darkmode])
+ root(false)
+
</style>
diff --git a/src/client/app/common/views/widgets/donation.vue b/src/client/app/common/views/widgets/donation.vue
index 6b5a6697ed..e35462611d 100644
--- a/src/client/app/common/views/widgets/donation.vue
+++ b/src/client/app/common/views/widgets/donation.vue
@@ -19,9 +19,9 @@ export default define({
</script>
<style lang="stylus" scoped>
-.mkw-donation
- background #fff
- border solid 1px #ead8bb
+root(isDark)
+ background isDark ? #282c37 : #fff
+ border solid 1px isDark ? #c3831c : #ead8bb
border-radius 6px
> article
@@ -30,7 +30,7 @@ export default define({
> h1
margin 0 0 5px 0
font-size 1em
- color #888
+ color isDark ? #b2bac1 : #888
> [data-fa]
margin-right 0.25em
@@ -40,13 +40,13 @@ export default define({
z-index 1
margin 0
font-size 0.8em
- color #999
+ color isDark ? #a1a6ab : #999
&[data-mobile]
border none
background #ead8bb
border-radius 8px
- box-shadow 0 0 0 1px rgba(0, 0, 0, 0.2)
+ box-shadow 0 0 0 1px rgba(#000, 0.2)
> article
> h1
@@ -55,4 +55,10 @@ export default define({
> p
color #777d71
+.mkw-donation[data-darkmode]
+ root(true)
+
+.mkw-donation:not([data-darkmode])
+ root(false)
+
</style>
diff --git a/src/client/app/common/views/widgets/nav.vue b/src/client/app/common/views/widgets/nav.vue
index 7bd5a7832f..0cbf7c158e 100644
--- a/src/client/app/common/views/widgets/nav.vue
+++ b/src/client/app/common/views/widgets/nav.vue
@@ -1,7 +1,7 @@
<template>
<div class="mkw-nav">
<mk-widget-container>
- <div :class="$style.body">
+ <div class="mkw-nav--body">
<mk-nav/>
</div>
</mk-widget-container>
@@ -15,17 +15,24 @@ export default define({
});
</script>
-<style lang="stylus" module>
-.body
- padding 16px
- font-size 12px
- color #aaa
- background #fff
+<style lang="stylus" scoped>
+root(isDark)
+ .mkw-nav--body
+ padding 16px
+ font-size 12px
+ color isDark ? #9aa4b3 : #aaa
+ background isDark ? #282c37 : #fff
- a
- color #999
+ a
+ color isDark ? #9aa4b3 : #999
- i
- color #ccc
+ i
+ color isDark ? #9aa4b3 : #ccc
+
+.mkw-nav[data-darkmode]
+ root(true)
+
+.mkw-nav:not([data-darkmode])
+ root(false)
</style>
diff --git a/src/client/app/common/views/widgets/photo-stream.vue b/src/client/app/common/views/widgets/photo-stream.vue
index c51d932bd1..ae5924bb10 100644
--- a/src/client/app/common/views/widgets/photo-stream.vue
+++ b/src/client/app/common/views/widgets/photo-stream.vue
@@ -59,6 +59,8 @@ export default define({
} else {
this.props.design++;
}
+
+ this.save();
}
}
});
diff --git a/src/client/app/common/views/widgets/rss.vue b/src/client/app/common/views/widgets/rss.vue
index 4d74b2f7a4..b5339add0b 100644
--- a/src/client/app/common/views/widgets/rss.vue
+++ b/src/client/app/common/views/widgets/rss.vue
@@ -4,9 +4,11 @@
<template slot="header">%fa:rss-square%RSS</template>
<button slot="func" title="設定" @click="setting">%fa:cog%</button>
- <p :class="$style.fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p>
- <div :class="$style.feed" v-else>
- <a v-for="item in items" :href="item.link" target="_blank">{{ item.title }}</a>
+ <div class="mkw-rss--body">
+ <p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p>
+ <div class="feed" v-else>
+ <a v-for="item in items" :href="item.link" target="_blank">{{ item.title }}</a>
+ </div>
</div>
</mk-widget-container>
</div>
@@ -38,6 +40,7 @@ export default define({
methods: {
func() {
this.props.compact = !this.props.compact;
+ this.save();
},
fetch() {
fetch(`https://api.rss2json.com/v1/api.json?rss_url=${this.url}`, {
@@ -56,38 +59,46 @@ export default define({
});
</script>
-<style lang="stylus" module>
-.feed
- padding 12px 16px
- font-size 0.9em
+<style lang="stylus" scoped>
+root(isDark)
+ .mkw-rss--body
+ .feed
+ padding 12px 16px
+ font-size 0.9em
- > a
- display block
- padding 4px 0
- color #666
- border-bottom dashed 1px #eee
+ > a
+ display block
+ padding 4px 0
+ color isDark ? #9aa4b3 : #666
+ border-bottom dashed 1px isDark ? #1c2023 : #eee
- &:last-child
- border-bottom none
+ &:last-child
+ border-bottom none
-.fetching
- margin 0
- padding 16px
- text-align center
- color #aaa
+ .fetching
+ margin 0
+ padding 16px
+ text-align center
+ color #aaa
- > [data-fa]
- margin-right 4px
+ > [data-fa]
+ margin-right 4px
-&[data-mobile]
- .feed
- padding 0
- font-size 1em
+ &[data-mobile]
+ .feed
+ padding 0
+ font-size 1em
- > a
- padding 8px 16px
+ > a
+ padding 8px 16px
- &:nth-child(even)
- background rgba(0, 0, 0, 0.05)
+ &:nth-child(even)
+ background rgba(#000, 0.05)
+
+.mkw-rss[data-darkmode]
+ root(true)
+
+.mkw-rss:not([data-darkmode])
+ root(false)
</style>
diff --git a/src/client/app/common/views/widgets/server.cpu-memory.vue b/src/client/app/common/views/widgets/server.cpu-memory.vue
index d75a142568..fbd36b255a 100644
--- a/src/client/app/common/views/widgets/server.cpu-memory.vue
+++ b/src/client/app/common/views/widgets/server.cpu-memory.vue
@@ -100,7 +100,7 @@ export default Vue.extend({
</script>
<style lang="stylus" scoped>
-.cpu-memory
+root(isDark)
> svg
display block
padding 10px
@@ -115,7 +115,7 @@ export default Vue.extend({
> text
font-size 5px
- fill rgba(0, 0, 0, 0.55)
+ fill isDark ? rgba(#fff, 0.55) : rgba(#000, 0.55)
> tspan
opacity 0.5
@@ -124,4 +124,11 @@ export default Vue.extend({
content ""
display block
clear both
+
+.cpu-memory[data-darkmode]
+ root(true)
+
+.cpu-memory:not([data-darkmode])
+ root(false)
+
</style>
diff --git a/src/client/app/common/views/widgets/server.cpu.vue b/src/client/app/common/views/widgets/server.cpu.vue
index 596c856da8..b9748bdf7c 100644
--- a/src/client/app/common/views/widgets/server.cpu.vue
+++ b/src/client/app/common/views/widgets/server.cpu.vue
@@ -38,7 +38,7 @@ export default Vue.extend({
</script>
<style lang="stylus" scoped>
-.cpu
+root(isDark)
> .pie
padding 10px
height 100px
@@ -52,7 +52,7 @@ export default Vue.extend({
> p
margin 0
font-size 12px
- color #505050
+ color isDark ? #a8b4bd : #505050
&:first-child
font-weight bold
@@ -65,4 +65,10 @@ export default Vue.extend({
display block
clear both
+.cpu[data-darkmode]
+ root(true)
+
+.cpu:not([data-darkmode])
+ root(false)
+
</style>
diff --git a/src/client/app/common/views/widgets/server.disk.vue b/src/client/app/common/views/widgets/server.disk.vue
index 2af1982a96..5c7e9678de 100644
--- a/src/client/app/common/views/widgets/server.disk.vue
+++ b/src/client/app/common/views/widgets/server.disk.vue
@@ -46,7 +46,7 @@ export default Vue.extend({
</script>
<style lang="stylus" scoped>
-.disk
+root(isDark)
> .pie
padding 10px
height 100px
@@ -60,7 +60,7 @@ export default Vue.extend({
> p
margin 0
font-size 12px
- color #505050
+ color isDark ? #a8b4bd : #505050
&:first-child
font-weight bold
@@ -73,4 +73,10 @@ export default Vue.extend({
display block
clear both
+.disk[data-darkmode]
+ root(true)
+
+.disk:not([data-darkmode])
+ root(false)
+
</style>
diff --git a/src/client/app/common/views/widgets/server.memory.vue b/src/client/app/common/views/widgets/server.memory.vue
index 834a62671d..9212f2271f 100644
--- a/src/client/app/common/views/widgets/server.memory.vue
+++ b/src/client/app/common/views/widgets/server.memory.vue
@@ -46,7 +46,7 @@ export default Vue.extend({
</script>
<style lang="stylus" scoped>
-.memory
+root(isDark)
> .pie
padding 10px
height 100px
@@ -60,7 +60,7 @@ export default Vue.extend({
> p
margin 0
font-size 12px
- color #505050
+ color isDark ? #a8b4bd : #505050
&:first-child
font-weight bold
@@ -73,4 +73,10 @@ export default Vue.extend({
display block
clear both
+.memory[data-darkmode]
+ root(true)
+
+.memory:not([data-darkmode])
+ root(false)
+
</style>
diff --git a/src/client/app/common/views/widgets/server.pie.vue b/src/client/app/common/views/widgets/server.pie.vue
index ce2cff1d00..d557c52ea5 100644
--- a/src/client/app/common/views/widgets/server.pie.vue
+++ b/src/client/app/common/views/widgets/server.pie.vue
@@ -45,7 +45,7 @@ export default Vue.extend({
</script>
<style lang="stylus" scoped>
-svg
+root(isDark)
display block
height 100%
@@ -56,6 +56,12 @@ svg
> text
font-size 0.15px
- fill rgba(0, 0, 0, 0.6)
+ fill isDark ? rgba(#fff, 0.6) : rgba(#000, 0.6)
+
+svg[data-darkmode]
+ root(true)
+
+svg:not([data-darkmode])
+ root(false)
</style>
diff --git a/src/client/app/common/views/widgets/server.vue b/src/client/app/common/views/widgets/server.vue
index 2fbc07adf0..2fdd60499b 100644
--- a/src/client/app/common/views/widgets/server.vue
+++ b/src/client/app/common/views/widgets/server.vue
@@ -68,6 +68,7 @@ export default define({
} else {
this.props.view++;
}
+ this.save();
},
func() {
if (this.props.design == 2) {
@@ -75,6 +76,7 @@ export default define({
} else {
this.props.design++;
}
+ this.save();
}
}
});
diff --git a/src/client/app/common/views/widgets/slideshow.vue b/src/client/app/common/views/widgets/slideshow.vue
index ad32299f37..459b24a32f 100644
--- a/src/client/app/common/views/widgets/slideshow.vue
+++ b/src/client/app/common/views/widgets/slideshow.vue
@@ -64,6 +64,7 @@ export default define({
} else {
this.props.size++;
}
+ this.save();
this.applySize();
},
@@ -111,6 +112,7 @@ export default define({
choose() {
(this as any).apis.chooseDriveFolder().then(folder => {
this.props.folder = folder ? folder.id : null;
+ this.save();
this.fetch();
});
}
@@ -122,13 +124,13 @@ export default define({
.mkw-slideshow
overflow hidden
background #fff
- border solid 1px rgba(0, 0, 0, 0.075)
+ border solid 1px rgba(#000, 0.075)
border-radius 6px
&[data-mobile]
border none
border-radius 8px
- box-shadow 0 0 0 1px rgba(0, 0, 0, 0.2)
+ box-shadow 0 0 0 1px rgba(#000, 0.2)
> div
width 100%
diff --git a/src/client/app/common/views/widgets/tips.vue b/src/client/app/common/views/widgets/tips.vue
index bdecc068e1..08e665f92f 100644
--- a/src/client/app/common/views/widgets/tips.vue
+++ b/src/client/app/common/views/widgets/tips.vue
@@ -17,7 +17,7 @@ const tips = [
'ドライブでファイルをドラッグしてフォルダ移動できます',
'ドライブでフォルダをドラッグしてフォルダ移動できます',
'ホームは設定からカスタマイズできます',
- 'MisskeyはMIT Licenseです',
+ 'MisskeyはAGPLv3です',
'タイムマシンウィジェットを利用すると、簡単に過去のタイムラインに遡れます',
'投稿の ... をクリックして、投稿をユーザーページにピン留めできます',
'ドライブの容量は(デフォルトで)1GBです',