diff options
Diffstat (limited to 'src/client/app/common')
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です', |