Merge pull request #1135 from h3poteto/iss-901

refs #901 Use ipc, shell and clipboard from preload.js
This commit is contained in:
AkiraFukushima 2019-11-20 23:44:38 +09:00 committed by GitHub
commit 21f9d90443
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 232 additions and 199 deletions

18
package-lock.json generated
View File

@ -6385,7 +6385,7 @@
"dependencies": { "dependencies": {
"resolve": { "resolve": {
"version": "1.1.7", "version": "1.1.7",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", "resolved": "http://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz",
"integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=",
"dev": true "dev": true
} }
@ -9448,18 +9448,18 @@
"integrity": "sha512-v+Af5W5z99ehhaLOfE9eTSXUwjzh2wFlQjz51dvkZ6ZIrET6OB/zAZPvsuwT6tm3t5x+M1r+Ed3U3xtPZYAyuQ==" "integrity": "sha512-v+Af5W5z99ehhaLOfE9eTSXUwjzh2wFlQjz51dvkZ6ZIrET6OB/zAZPvsuwT6tm3t5x+M1r+Ed3U3xtPZYAyuQ=="
}, },
"electron-mock-ipc": { "electron-mock-ipc": {
"version": "0.1.3", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/electron-mock-ipc/-/electron-mock-ipc-0.1.3.tgz", "resolved": "https://registry.npmjs.org/electron-mock-ipc/-/electron-mock-ipc-0.2.0.tgz",
"integrity": "sha512-+x5v+hmZZYUi8SMJNBtcq1o2p3aUJfT3aJCEKIoajSa/6rV9EdmFOd/rHLVEH85GSlWQCf+i1yV4r4bDnfEXHw==", "integrity": "sha512-oEW4kS0W5ioQi5XprKXkBbD1zRINZk6AkGXsZD/mhgXaqTZuA0Qtsw1QoMWxykqnURVRl9plpmbHIvkYedsvNQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"typescript": "^3.5.2" "typescript": "^3.5.2"
}, },
"dependencies": { "dependencies": {
"typescript": { "typescript": {
"version": "3.5.2", "version": "3.7.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.2.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.2.tgz",
"integrity": "sha512-7KxJovlYhTX5RaRbUdkAXN1KUZ8PwWlTzQdHV6xNqvuFOs7+WBo10TQUqT19Q/Jz2hk5v9TQDIhyLhhJY4p5AA==", "integrity": "sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ==",
"dev": true "dev": true
} }
} }
@ -16507,7 +16507,7 @@
}, },
"readable-stream": { "readable-stream": {
"version": "2.3.6", "version": "2.3.6",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"dev": true, "dev": true,
"requires": { "requires": {
@ -16522,7 +16522,7 @@
}, },
"string_decoder": { "string_decoder": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true, "dev": true,
"requires": { "requires": {

View File

@ -248,7 +248,7 @@
"electron-builder": "^21.2.0", "electron-builder": "^21.2.0",
"electron-debug": "^3.0.1", "electron-debug": "^3.0.1",
"electron-devtools-installer": "^2.2.4", "electron-devtools-installer": "^2.2.4",
"electron-mock-ipc": "^0.1.3", "electron-mock-ipc": "^0.2.0",
"electron-packager": "^14.0.5", "electron-packager": "^14.0.5",
"eslint": "^5.16.0", "eslint": "^5.16.0",
"eslint-config-prettier": "^4.1.0", "eslint-config-prettier": "^4.1.0",

View File

@ -1,7 +1,8 @@
import createIPCMock from 'electron-mock-ipc' import createIPCMock from 'electron-mock-ipc'
import { IpcRenderer, IpcMain } from 'electron'
const mocked = createIPCMock() const mocked = createIPCMock()
const ipcMain = mocked.ipcMain const ipcMain = mocked.ipcMain as IpcMain
const ipcRenderer = mocked.ipcRenderer const ipcRenderer = mocked.ipcRenderer as IpcRenderer
export { ipcMain, ipcRenderer } export { ipcMain, ipcRenderer }

View File

@ -1,6 +1,6 @@
import { createLocalVue } from '@vue/test-utils' import { createLocalVue } from '@vue/test-utils'
import Vuex from 'vuex' import Vuex from 'vuex'
import { ipcMain } from '~/spec/mock/electron' import { ipcMain, ipcRenderer } from '~/spec/mock/electron'
import App from '@/store/App' import App from '@/store/App'
import DisplayStyle from '~/src/constants/displayStyle' import DisplayStyle from '~/src/constants/displayStyle'
import { LightTheme, DarkTheme } from '~/src/constants/themeColor' import { LightTheme, DarkTheme } from '~/src/constants/themeColor'
@ -8,6 +8,7 @@ import Theme from '~/src/constants/theme'
import TimeFormat from '~/src/constants/timeFormat' import TimeFormat from '~/src/constants/timeFormat'
import Language from '~/src/constants/language' import Language from '~/src/constants/language'
import DefaultFonts from '@/utils/fonts' import DefaultFonts from '@/utils/fonts'
import { MyWindow } from '~/src/types/global'
const state = () => { const state = () => {
return { return {
@ -43,6 +44,7 @@ describe('App', () => {
let localVue let localVue
beforeEach(() => { beforeEach(() => {
;(<MyWindow>window).ipcRenderer = ipcRenderer
localVue = createLocalVue() localVue = createLocalVue()
localVue.use(Vuex) localVue.use(Vuex)
store = new Vuex.Store({ store = new Vuex.Store({
@ -58,11 +60,10 @@ describe('App', () => {
ipcMain.once('get-preferences', (event: any, _) => { ipcMain.once('get-preferences', (event: any, _) => {
event.sender.send('error-get-preferences', new Error()) event.sender.send('error-get-preferences', new Error())
}) })
await store.dispatch('App/loadPreferences') await store.dispatch('App/loadPreferences').catch(err => {
.catch((err) => { expect(err instanceof Error).toEqual(true)
expect(err instanceof Error).toEqual(true) expect(store.state.App.theme).toEqual(LightTheme)
expect(store.state.App.theme).toEqual(LightTheme) })
})
}) })
}) })
describe('success', () => { describe('success', () => {

View File

@ -255,7 +255,9 @@ async function createWindow() {
webPreferences: { webPreferences: {
// It is required to use ipcRenderer in renderer process. // It is required to use ipcRenderer in renderer process.
// But it is not secure, so if you want to disable this option, please use preload script. // But it is not secure, so if you want to disable this option, please use preload script.
nodeIntegration: true nodeIntegration: true,
contextIsolation: false,
preload: path.resolve(__dirname, './preload.js')
} }
} }
const config: Config = { const config: Config = {

5
src/main/preload.js Normal file
View File

@ -0,0 +1,5 @@
const electron = require('electron')
global.ipcRenderer = electron.ipcRenderer
global.shell = electron.shell
global.clipboard = electron.clipboard
global.process = process

View File

@ -1,114 +1,112 @@
<template> <template>
<div id="account_profile" <div
v-loading="loading" id="account_profile"
:element-loading-text="$t('message.loading')" v-loading="loading"
element-loading-spinner="el-icon-loading" :element-loading-text="$t('message.loading')"
element-loading-background="rgba(0, 0, 0, 0.8)" element-loading-spinner="el-icon-loading"
role="article" element-loading-background="rgba(0, 0, 0, 0.8)"
aria-label="account profile"> role="article"
<div class="header-background" v-bind:style="{ backgroundImage: 'url(' + account.header + ')' }"> aria-label="account profile"
<div class="header"> >
<div class="follow-follower" v-if="relationship !== null && relationship !== '' && !isOwnProfile"> <div class="header-background" v-bind:style="{ backgroundImage: 'url(' + account.header + ')' }">
<div class="follower-status"> <div class="header">
<el-tag class="status" size="small" v-if="relationship.followed_by">{{ $t('side_bar.account_profile.follows_you') }}</el-tag> <div class="follow-follower" v-if="relationship !== null && relationship !== '' && !isOwnProfile">
<el-tag class="status" size="medium" v-else>{{ $t('side_bar.account_profile.doesnt_follow_you') }}</el-tag> <div class="follower-status">
<el-tag class="status" size="small" v-if="relationship.followed_by">{{ $t('side_bar.account_profile.follows_you') }}</el-tag>
<el-tag class="status" size="medium" v-else>{{ $t('side_bar.account_profile.doesnt_follow_you') }}</el-tag>
</div>
</div> </div>
</div> <div class="user-info">
<div class="user-info"> <div class="more" v-if="relationship !== null && relationship !== '' && !isOwnProfile">
<div class="more" v-if="relationship !== null && relationship !== '' && !isOwnProfile"> <popper trigger="click" :options="{ placement: 'bottom' }" ref="popper">
<popper trigger="click" :options="{placement: 'bottom'}" ref="popper"> <div class="popper">
<div class="popper"> <ul class="menu-list">
<ul class="menu-list"> <li role="button" @click="openBrowser(account)">
<li role="button" @click="openBrowser(account)"> {{ $t('side_bar.account_profile.open_in_browser') }}
{{ $t('side_bar.account_profile.open_in_browser') }} </li>
</li> <li role="button" @click="addToList(account)">
<li role="button" @click="addToList(account)"> {{ $t('side_bar.account_profile.manage_list_memberships') }}
{{ $t('side_bar.account_profile.manage_list_memberships') }} </li>
</li> <li role="button" @click="unmute(account)" v-if="muting">
<li role="button" @click="unmute(account)" v-if="muting"> {{ $t('side_bar.account_profile.unmute') }}
{{ $t('side_bar.account_profile.unmute') }} </li>
</li> <li role="button" @click="confirmMute(account)" v-else>
<li role="button" @click="confirmMute(account)" v-else> {{ $t('side_bar.account_profile.mute') }}
{{ $t('side_bar.account_profile.mute') }} </li>
</li> <li role="button" @click="unblock(account)" v-if="blocking">
<li role="button" @click="unblock(account)" v-if="blocking"> {{ $t('side_bar.account_profile.unblock') }}
{{ $t('side_bar.account_profile.unblock') }} </li>
</li> <li role="button" @click="block(account)" v-else>
<li role="button" @click="block(account)" v-else> {{ $t('side_bar.account_profile.block') }}
{{ $t('side_bar.account_profile.block') }} </li>
</li> </ul>
</ul> </div>
</div>
<el-button slot="reference" type="text" :title="$t('side_bar.account_profile.detail')"> <el-button slot="reference" type="text" :title="$t('side_bar.account_profile.detail')">
<icon name="cog" scale="1.4"></icon> <icon name="cog" scale="1.4"></icon>
</el-button> </el-button>
</popper> </popper>
</div>
<div class="icon" role="presentation">
<FailoverImg :src="account.avatar" :alt="`Avatar of ${account.username}`" />
</div>
<div class="follow-status" v-if="relationship !== null && relationship !== '' && !isOwnProfile">
<div v-if="relationship.following" class="unfollow" @click="unfollow(account)" :title="$t('side_bar.account_profile.unfollow')">
<icon name="user-times" scale="1.5"></icon>
</div> </div>
<div v-else-if="relationship.requested" :title="$t('side_bar.account_profile.follow_requested')"> <div class="icon" role="presentation">
<icon name="hourglass" scale="1.5"></icon> <FailoverImg :src="account.avatar" :alt="`Avatar of ${account.username}`" />
</div> </div>
<div v-else class="follow" @click="follow(account)" :title="$t('side_bar.account_profile.follow')"> <div class="follow-status" v-if="relationship !== null && relationship !== '' && !isOwnProfile">
<icon name="user-plus" scale="1.5"></icon> <div v-if="relationship.following" class="unfollow" @click="unfollow(account)" :title="$t('side_bar.account_profile.unfollow')">
<icon name="user-times" scale="1.5"></icon>
</div>
<div v-else-if="relationship.requested" :title="$t('side_bar.account_profile.follow_requested')">
<icon name="hourglass" scale="1.5"></icon>
</div>
<div v-else class="follow" @click="follow(account)" :title="$t('side_bar.account_profile.follow')">
<icon name="user-plus" scale="1.5"></icon>
</div>
</div> </div>
</div> </div>
<div class="username">
<bdi v-html="username(account)"></bdi>
</div>
<div class="account">@{{ account.acct }}</div>
<div class="note" v-html="note(account)" @click.capture.prevent="noteClick"></div>
</div> </div>
<div class="username"> </div>
<bdi v-html="username(account)"></bdi> <div class="metadata">
</div> <dl v-for="(data, index) in account.fields" :key="index">
<div class="account"> <dt>
@{{ account.acct }} {{ data.name }}
</div> </dt>
<div class="note" v-html="note(account)" @click.capture.prevent="noteClick"></div> <dd v-html="data.value" @click.capture.prevent="metadataClick"></dd>
</dl>
</div>
<el-row class="basic-info">
<el-col :span="8" :class="activeTab === 1 ? 'info info-active' : 'info'" @click="changeTab">
<el-button type="text" class="tab" @click="changeTab(1)">
<div class="title">{{ $t('side_bar.account_profile.toots') }}</div>
<div class="count">{{ account.statuses_count }}</div>
</el-button>
</el-col>
<el-col :span="8" :class="activeTab === 2 ? 'info info-active' : 'info'">
<el-button type="text" class="tab" @click="changeTab(2)">
<div class="title">{{ $t('side_bar.account_profile.follows') }}</div>
<div class="count">{{ account.following_count }}</div>
</el-button>
</el-col>
<el-col :span="8" :class="activeTab === 3 ? 'info info-active' : 'info'">
<el-button type="text" class="tab" @click="changeTab(3)">
<div class="title">{{ $t('side_bar.account_profile.followers') }}</div>
<div class="count">{{ account.followers_count }}</div>
</el-button>
</el-col>
</el-row>
<div class="timeline">
<timeline :account="account" v-if="activeTab === 1"></timeline>
<follows :account="account" v-if="activeTab === 2"></follows>
<followers :account="account" v-if="activeTab === 3"></followers>
</div> </div>
</div> </div>
<div class="metadata">
<dl v-for="(data, index) in account.fields" :key="index">
<dt>
{{ data.name }}
</dt>
<dd v-html="data.value" @click.capture.prevent="metadataClick">
</dd>
</dl>
</div>
<el-row class="basic-info">
<el-col :span="8" :class="activeTab === 1 ? 'info info-active' : 'info'" @click="changeTab">
<el-button type="text" class="tab" @click="changeTab(1)">
<div class="title">{{ $t('side_bar.account_profile.toots') }}</div>
<div class="count">{{ account.statuses_count }}</div>
</el-button>
</el-col>
<el-col :span="8" :class="activeTab === 2 ? 'info info-active' : 'info'">
<el-button type="text" class="tab" @click="changeTab(2)">
<div class="title">{{ $t('side_bar.account_profile.follows') }}</div>
<div class="count">{{ account.following_count }}</div>
</el-button>
</el-col>
<el-col :span="8" :class="activeTab === 3 ? 'info info-active' : 'info'">
<el-button type="text" class="tab" @click="changeTab(3)">
<div class="title">{{ $t('side_bar.account_profile.followers') }}</div>
<div class="count">{{ account.followers_count }}</div>
</el-button>
</el-col>
</el-row>
<div class="timeline">
<timeline :account="account" v-if="activeTab === 1"></timeline>
<follows :account="account" v-if="activeTab === 2"></follows>
<followers :account="account" v-if="activeTab === 3"></followers>
</div>
</div>
</template> </template>
<script> <script>
import { mapState, mapGetters } from 'vuex' import { mapState, mapGetters } from 'vuex'
import { shell } from 'electron'
import { findLink } from '~/src/renderer/utils/tootParser' import { findLink } from '~/src/renderer/utils/tootParser'
import emojify from '~/src/renderer/utils/emojify' import emojify from '~/src/renderer/utils/emojify'
import Timeline from './AccountProfile/Timeline' import Timeline from './AccountProfile/Timeline'
@ -124,14 +122,14 @@ export default {
Followers, Followers,
FailoverImg FailoverImg
}, },
data () { data() {
return { return {
activeTab: 1 activeTab: 1
} }
}, },
computed: { computed: {
...mapState({ ...mapState({
theme: (state) => { theme: state => {
return { return {
'--theme-mask-color': state.App.theme.wrapper_mask_color, '--theme-mask-color': state.App.theme.wrapper_mask_color,
'--theme-border-color': state.App.theme.border_color, '--theme-border-color': state.App.theme.border_color,
@ -149,31 +147,31 @@ export default {
...mapGetters('TimelineSpace/Contents/SideBar/AccountProfile', ['isOwnProfile']) ...mapGetters('TimelineSpace/Contents/SideBar/AccountProfile', ['isOwnProfile'])
}, },
watch: { watch: {
account: function () { account: function() {
this.activeTab = 1 this.activeTab = 1
}, },
loading: function (newState, _oldState) { loading: function(newState, _oldState) {
this.$emit('change-loading', newState) this.$emit('change-loading', newState)
} }
}, },
methods: { methods: {
username (account) { username(account) {
if (account.display_name !== '') { if (account.display_name !== '') {
return emojify(account.display_name, account.emojis) return emojify(account.display_name, account.emojis)
} else { } else {
return account.username return account.username
} }
}, },
note (account) { note(account) {
return emojify(account.note, account.emojis) return emojify(account.note, account.emojis)
}, },
noteClick (e) { noteClick(e) {
const link = findLink(e.target, 'note') const link = findLink(e.target, 'note')
if (link !== null) { if (link !== null) {
shell.openExternal(link) window.shell.openExternal(link)
} }
}, },
follow (account) { follow(account) {
this.$store.commit('TimelineSpace/Contents/SideBar/AccountProfile/changeLoading', true) this.$store.commit('TimelineSpace/Contents/SideBar/AccountProfile/changeLoading', true)
try { try {
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/follow', account) this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/follow', account)
@ -186,7 +184,7 @@ export default {
this.$store.commit('TimelineSpace/Contents/SideBar/AccountProfile/changeLoading', false) this.$store.commit('TimelineSpace/Contents/SideBar/AccountProfile/changeLoading', false)
} }
}, },
unfollow (account) { unfollow(account) {
this.$store.commit('TimelineSpace/Contents/SideBar/AccountProfile/changeLoading', true) this.$store.commit('TimelineSpace/Contents/SideBar/AccountProfile/changeLoading', true)
try { try {
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/unfollow', account) this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/unfollow', account)
@ -199,39 +197,39 @@ export default {
this.$store.commit('TimelineSpace/Contents/SideBar/AccountProfile/changeLoading', false) this.$store.commit('TimelineSpace/Contents/SideBar/AccountProfile/changeLoading', false)
} }
}, },
changeTab (index) { changeTab(index) {
this.activeTab = index this.activeTab = index
}, },
openBrowser (account) { openBrowser(account) {
shell.openExternal(account.url) window.shell.openExternal(account.url)
this.$refs.popper.doClose() this.$refs.popper.doClose()
}, },
addToList (account) { addToList(account) {
this.$store.dispatch('TimelineSpace/Modals/ListMembership/setAccount', account) this.$store.dispatch('TimelineSpace/Modals/ListMembership/setAccount', account)
this.$store.dispatch('TimelineSpace/Modals/ListMembership/changeModal', true) this.$store.dispatch('TimelineSpace/Modals/ListMembership/changeModal', true)
this.$refs.popper.doClose() this.$refs.popper.doClose()
}, },
confirmMute (account) { confirmMute(account) {
this.$store.dispatch('TimelineSpace/Modals/MuteConfirm/changeAccount', account) this.$store.dispatch('TimelineSpace/Modals/MuteConfirm/changeAccount', account)
this.$store.dispatch('TimelineSpace/Modals/MuteConfirm/changeModal', true) this.$store.dispatch('TimelineSpace/Modals/MuteConfirm/changeModal', true)
this.$refs.popper.doClose() this.$refs.popper.doClose()
}, },
unmute (account) { unmute(account) {
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/unmute', account) this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/unmute', account)
this.$refs.popper.doClose() this.$refs.popper.doClose()
}, },
block (account) { block(account) {
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/block', account) this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/block', account)
this.$refs.popper.doClose() this.$refs.popper.doClose()
}, },
unblock (account) { unblock(account) {
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/unblock', account) this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/unblock', account)
this.$refs.popper.doClose() this.$refs.popper.doClose()
}, },
metadataClick (e) { metadataClick(e) {
const link = findLink(e.target, 'metadata') const link = findLink(e.target, 'metadata')
if (link !== null) { if (link !== null) {
return shell.openExternal(link) return window.shell.openExternal(link)
} }
} }
} }

View File

@ -130,7 +130,6 @@
<script> <script>
import { mapState, mapGetters } from 'vuex' import { mapState, mapGetters } from 'vuex'
import { clipboard } from 'electron'
import Visibility from '~/src/constants/visibility' import Visibility from '~/src/constants/visibility'
import Status from './NewToot/Status' import Status from './NewToot/Status'
import Poll from './NewToot/Poll' import Poll from './NewToot/Poll'
@ -295,12 +294,12 @@ export default {
this.updateImage(file) this.updateImage(file)
}, },
onPaste(e) { onPaste(e) {
const mimeTypes = clipboard.availableFormats().filter(type => type.startsWith('image')) const mimeTypes = window.clipboard.availableFormats().filter(type => type.startsWith('image'))
if (mimeTypes.length === 0) { if (mimeTypes.length === 0) {
return return
} }
e.preventDefault() e.preventDefault()
const image = clipboard.readImage() const image = window.clipboard.readImage()
let data let data
if (/^image\/jpe?g$/.test(mimeTypes[0])) { if (/^image\/jpe?g$/.test(mimeTypes[0])) {
data = image.toJPEG(100) data = image.toJPEG(100)

View File

@ -133,7 +133,6 @@
<script> <script>
import { mapState } from 'vuex' import { mapState } from 'vuex'
import { shell } from 'electron'
export default { export default {
name: 'side-menu', name: 'side-menu',
@ -178,7 +177,7 @@ export default {
this.$store.dispatch('TimelineSpace/Contents/SideBar/openAccountComponent') this.$store.dispatch('TimelineSpace/Contents/SideBar/openAccountComponent')
break break
case 'edit': case 'edit':
shell.openExternal(this.account.baseURL + '/settings/profile') window.shell.openExternal(this.account.baseURL + '/settings/profile')
break break
case 'settings': case 'settings':
const url = `/${this.id()}/settings` const url = `/${this.id()}/settings`

View File

@ -101,7 +101,6 @@
<script> <script>
import { mapState } from 'vuex' import { mapState } from 'vuex'
import moment from 'moment' import moment from 'moment'
import { shell } from 'electron'
import { findAccount, findLink, findTag } from '~/src/renderer/utils/tootParser' import { findAccount, findLink, findTag } from '~/src/renderer/utils/tootParser'
import emojify from '~/src/renderer/utils/emojify' import emojify from '~/src/renderer/utils/emojify'
import TimeFormat from '~/src/constants/timeFormat' import TimeFormat from '~/src/constants/timeFormat'
@ -209,7 +208,7 @@ export default {
openLink(e) { openLink(e) {
const link = findLink(e.target, 'favourite') const link = findLink(e.target, 'favourite')
if (link !== null) { if (link !== null) {
return shell.openExternal(link) return window.shell.openExternal(link)
} }
}, },
openUser(account) { openUser(account) {

View File

@ -103,7 +103,6 @@
<script> <script>
import { mapState } from 'vuex' import { mapState } from 'vuex'
import moment from 'moment' import moment from 'moment'
import { shell } from 'electron'
import { findAccount, findLink, findTag } from '~/src/renderer/utils/tootParser' import { findAccount, findLink, findTag } from '~/src/renderer/utils/tootParser'
import emojify from '~/src/renderer/utils/emojify' import emojify from '~/src/renderer/utils/emojify'
import TimeFormat from '~/src/constants/timeFormat' import TimeFormat from '~/src/constants/timeFormat'
@ -209,7 +208,7 @@ export default {
openLink(e) { openLink(e) {
const link = findLink(e.target, 'reblog') const link = findLink(e.target, 'reblog')
if (link !== null) { if (link !== null) {
return shell.openExternal(link) return window.shell.openExternal(link)
} }
}, },
openUser(account) { openUser(account) {

View File

@ -178,7 +178,6 @@
<script> <script>
import moment from 'moment' import moment from 'moment'
import { shell, clipboard } from 'electron'
import { mapState } from 'vuex' import { mapState } from 'vuex'
import { findAccount, findLink, findTag } from '~/src/renderer/utils/tootParser' import { findAccount, findLink, findTag } from '~/src/renderer/utils/tootParser'
import DisplayStyle from '~/src/constants/displayStyle' import DisplayStyle from '~/src/constants/displayStyle'
@ -391,7 +390,7 @@ export default {
openLink(e) { openLink(e) {
const link = findLink(e.target, 'toot') const link = findLink(e.target, 'toot')
if (link !== null) { if (link !== null) {
return shell.openExternal(link) return window.shell.openExternal(link)
} }
}, },
openReply() { openReply() {
@ -404,11 +403,11 @@ export default {
this.$refs.popper.doClose() this.$refs.popper.doClose()
}, },
openBrowser(message) { openBrowser(message) {
shell.openExternal(message.url) window.shell.openExternal(message.url)
this.$refs.popper.doClose() this.$refs.popper.doClose()
}, },
copyLink(message) { copyLink(message) {
clipboard.writeText(message.url, 'toot-link') window.clipboard.writeText(message.url, 'toot-link')
this.$refs.popper.doClose() this.$refs.popper.doClose()
}, },
reportUser() { reportUser() {

View File

@ -16,15 +16,6 @@ import router from '@/router'
import store from './store' import store from './store'
import i18next from '~/src/config/i18n' import i18next from '~/src/config/i18n'
declare function require(x: string): any
declare var process: {
env: {
NODE_ENV: string,
IS_WEB: boolean
}
}
Vue.use(ElementUI, { locale }) Vue.use(ElementUI, { locale })
Vue.use(shortkey) Vue.use(shortkey)
Vue.use(VueI18Next) Vue.use(VueI18Next)
@ -33,7 +24,6 @@ Vue.component('popper', Popper)
sync(store, router) sync(store, router)
if (!process.env.IS_WEB) Vue.use(require('vue-electron'))
Vue.config.productionTip = false Vue.config.productionTip = false
const i18n: VueI18Next = new VueI18Next(i18next) const i18n: VueI18Next = new VueI18Next(i18next)

View File

@ -1,6 +1,34 @@
import Vue from 'vue' import Vue from 'vue'
import Router from 'vue-router' import Router from 'vue-router'
import Login from '@/components/Login.vue'
import Authorize from '@/components/Authorize.vue'
import Preferences from '@/components/Preferences.vue'
import PreferencesGeneral from '@/components/Preferences/General.vue'
import PreferencesAppearance from '@/components/Preferences/Appearance.vue'
import PreferencesNotification from '@/components/Preferences/Notification.vue'
import PreferencesAccount from '@/components/Preferences/Account.vue'
import PreferencesLanguage from '@/components/Preferences/Language.vue'
import GlobalHeader from '@/components/GlobalHeader.vue'
import Settings from '@/components/Settings.vue'
import SettingsGeneral from '@/components/Settings/General.vue'
import SettingsTimeline from '@/components/Settings/Timeline.vue'
import TimelineSpace from '@/components/TimelineSpace.vue'
import TimelineSpaceContentsHome from '@/components/TimelineSpace/Contents/Home.vue'
import TimelineSpaceContentsNotifications from '@/components/TimelineSpace/Contents/Notifications.vue'
import TimelineSpaceContentsMentions from '@/components/TimelineSpace/Contents/Mentions.vue'
import TimelineSpaceContentsFavourites from '@/components/TimelineSpace/Contents/Favourites.vue'
import TimelineSpaceContentsLocal from '@/components/TimelineSpace/Contents/Local.vue'
import TimelineSpaceContentsPublic from '@/components/TimelineSpace/Contents/Public.vue'
import TimelineSpaceContentsHashtag from '@/components/TimelineSpace/Contents/Hashtag.vue'
import TimelineSpaceContentsHashtagList from '@/components/TimelineSpace/Contents/Hashtag/List.vue'
import TimelineSpaceContentsHashtagTag from '@/components/TimelineSpace/Contents/Hashtag/Tag.vue'
import TimelineSpaceContentsSearch from '@/components/TimelineSpace/Contents/Search.vue'
import TimelineSpaceContentsDirectMessages from '@/components/TimelineSpace/Contents/DirectMessages.vue'
import TimelineSpaceContentsListsIndex from '@/components/TimelineSpace/Contents/Lists/Index.vue'
import TimelineSpaceContentsListsEdit from '@/components/TimelineSpace/Contents/Lists/Edit.vue'
import TimelineSpaceContentsListsShow from '@/components/TimelineSpace/Contents/Lists/Show.vue'
Vue.use(Router) Vue.use(Router)
const router = new Router({ const router = new Router({
@ -8,38 +36,38 @@ const router = new Router({
{ {
path: '/login', path: '/login',
name: 'login', name: 'login',
component: require('@/components/Login').default component: Login
}, },
{ {
path: '/authorize', path: '/authorize',
name: 'authorize', name: 'authorize',
component: require('@/components/Authorize').default, component: Authorize,
props: route => ({ url: route.query.url }) props: route => ({ url: route.query.url })
}, },
{ {
path: '/preferences/', path: '/preferences/',
name: 'preferences', name: 'preferences',
component: require('@/components/Preferences').default, component: Preferences,
children: [ children: [
{ {
path: 'general', path: 'general',
name: 'general', name: 'general',
component: require('@/components/Preferences/General').default component: PreferencesGeneral
}, },
{ {
path: 'appearance', path: 'appearance',
name: 'appearance', name: 'appearance',
component: require('@/components/Preferences/Appearance').default component: PreferencesAppearance
}, },
{ {
path: 'notification', path: 'notification',
name: 'notification', name: 'notification',
component: require('@/components/Preferences/Notification').default component: PreferencesNotification
}, },
{ {
path: 'account', path: 'account',
name: 'account', name: 'account',
component: require('@/components/Preferences/Account').default component: PreferencesAccount
}, },
{ {
path: 'network', path: 'network',
@ -49,48 +77,48 @@ const router = new Router({
{ {
path: 'language', path: 'language',
name: 'language', name: 'language',
component: require('@/components/Preferences/Language').default component: PreferencesLanguage
} }
] ]
}, },
{ {
path: '/', path: '/',
name: 'global-header', name: 'global-header',
component: require('@/components/GlobalHeader').default, component: GlobalHeader,
children: [ children: [
{ {
path: ':id/settings/', path: ':id/settings/',
component: require('@/components/Settings').default, component: Settings,
children: [ children: [
{ {
path: 'general', path: 'general',
component: require('@/components/Settings/General').default component: SettingsGeneral
}, },
{ {
path: 'timeline', path: 'timeline',
component: require('@/components/Settings/Timeline').default component: SettingsTimeline
} }
] ]
}, },
{ {
path: ':id/', path: ':id/',
name: 'timeline-space', name: 'timeline-space',
component: require('@/components/TimelineSpace').default, component: TimelineSpace,
children: [ children: [
{ {
path: 'home', path: 'home',
name: 'home', name: 'home',
component: require('@/components/TimelineSpace/Contents/Home').default component: TimelineSpaceContentsHome
}, },
{ {
path: 'notifications', path: 'notifications',
name: 'notifications', name: 'notifications',
component: require('@/components/TimelineSpace/Contents/Notifications').default component: TimelineSpaceContentsNotifications
}, },
{ {
path: 'mentions', path: 'mentions',
name: 'mentions', name: 'mentions',
component: require('@/components/TimelineSpace/Contents/Mentions').default component: TimelineSpaceContentsMentions
}, },
{ {
path: 'follow-requests', path: 'follow-requests',
@ -100,31 +128,31 @@ const router = new Router({
{ {
path: 'favourites', path: 'favourites',
name: 'favourites', name: 'favourites',
component: require('@/components/TimelineSpace/Contents/Favourites').default component: TimelineSpaceContentsFavourites
}, },
{ {
path: 'local', path: 'local',
name: 'local', name: 'local',
component: require('@/components/TimelineSpace/Contents/Local').default component: TimelineSpaceContentsLocal
}, },
{ {
path: 'public', path: 'public',
name: 'public', name: 'public',
component: require('@/components/TimelineSpace/Contents/Public').default component: TimelineSpaceContentsPublic
}, },
{ {
path: 'hashtag/', path: 'hashtag/',
component: require('@/components/TimelineSpace/Contents/Hashtag').default, component: TimelineSpaceContentsHashtag,
children: [ children: [
{ {
path: '', path: '',
name: 'hashtag-list', name: 'hashtag-list',
component: require('@/components/TimelineSpace/Contents/Hashtag/List').default component: TimelineSpaceContentsHashtagList
}, },
{ {
path: ':tag', path: ':tag',
name: 'tag', name: 'tag',
component: require('@/components/TimelineSpace/Contents/Hashtag/Tag').default, component: TimelineSpaceContentsHashtagTag,
props: true props: true
} }
] ]
@ -132,28 +160,28 @@ const router = new Router({
{ {
path: 'search', path: 'search',
name: 'search', name: 'search',
component: require('@/components/TimelineSpace/Contents/Search').default component: TimelineSpaceContentsSearch
}, },
{ {
path: 'direct-messages', path: 'direct-messages',
name: 'direct-messages', name: 'direct-messages',
component: require('@/components/TimelineSpace/Contents/DirectMessages').default component: TimelineSpaceContentsDirectMessages
}, },
{ {
path: 'lists', path: 'lists',
name: 'lists', name: 'lists',
component: require('@/components/TimelineSpace/Contents/Lists/Index').default component: TimelineSpaceContentsListsIndex
}, },
{ {
path: 'lists/:list_id/edit', path: 'lists/:list_id/edit',
name: 'edit-list', name: 'edit-list',
component: require('@/components/TimelineSpace/Contents/Lists/Edit').default, component: TimelineSpaceContentsListsEdit,
props: true props: true
}, },
{ {
path: 'lists/:list_id', path: 'lists/:list_id',
name: 'list', name: 'list',
component: require('@/components/TimelineSpace/Contents/Lists/Show').default, component: TimelineSpaceContentsListsShow,
props: true props: true
} }
] ]

View File

@ -1,4 +1,3 @@
import { ipcRenderer } from 'electron'
import { MutationTree, ActionTree, Module } from 'vuex' import { MutationTree, ActionTree, Module } from 'vuex'
import router from '@/router' import router from '@/router'
import { LightTheme, DarkTheme, SolarizedLightTheme, SolarizedDarkTheme, KimbieDarkTheme, ThemeColorType } from '~/src/constants/themeColor' import { LightTheme, DarkTheme, SolarizedLightTheme, SolarizedDarkTheme, KimbieDarkTheme, ThemeColorType } from '~/src/constants/themeColor'
@ -12,6 +11,9 @@ import { Notify } from '~/src/types/notify'
import { BaseConfig } from '~/src/types/preference' import { BaseConfig } from '~/src/types/preference'
import { Appearance } from '~/src/types/appearance' import { Appearance } from '~/src/types/appearance'
import { ProxyConfig } from 'megalodon' import { ProxyConfig } from 'megalodon'
import { MyWindow } from '~/src/types/global'
const win = window as MyWindow
export type AppState = { export type AppState = {
theme: ThemeColorType theme: ThemeColorType
@ -107,22 +109,22 @@ const mutations: MutationTree<AppState> = {
const actions: ActionTree<AppState, RootState> = { const actions: ActionTree<AppState, RootState> = {
watchShortcutsEvents: () => { watchShortcutsEvents: () => {
ipcRenderer.on('open-preferences', () => { win.ipcRenderer.on('open-preferences', () => {
router.push('/preferences/general') router.push('/preferences/general')
}) })
}, },
removeShortcutsEvents: () => { removeShortcutsEvents: () => {
ipcRenderer.removeAllListeners('open-preferences') win.ipcRenderer.removeAllListeners('open-preferences')
}, },
loadPreferences: ({ commit, dispatch }) => { loadPreferences: ({ commit, dispatch }) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
ipcRenderer.send('get-preferences') win.ipcRenderer.send('get-preferences')
ipcRenderer.once('error-get-preferences', (_, err: Error) => { win.ipcRenderer.once('error-get-preferences', (_, err: Error) => {
ipcRenderer.removeAllListeners('response-get-preferences') win.ipcRenderer.removeAllListeners('response-get-preferences')
reject(err) reject(err)
}) })
ipcRenderer.once('response-get-preferences', (_, conf: BaseConfig) => { win.ipcRenderer.once('response-get-preferences', (_, conf: BaseConfig) => {
ipcRenderer.removeAllListeners('error-get-preferences') win.ipcRenderer.removeAllListeners('error-get-preferences')
dispatch('updateTheme', conf.appearance) dispatch('updateTheme', conf.appearance)
commit(MUTATION_TYPES.UPDATE_DISPLAY_NAME_STYLE, conf.appearance.displayNameStyle) commit(MUTATION_TYPES.UPDATE_DISPLAY_NAME_STYLE, conf.appearance.displayNameStyle)
commit(MUTATION_TYPES.UPDATE_FONT_SIZE, conf.appearance.fontSize) commit(MUTATION_TYPES.UPDATE_FONT_SIZE, conf.appearance.fontSize)
@ -166,11 +168,11 @@ const actions: ActionTree<AppState, RootState> = {
}, },
loadProxy: ({ commit }) => { loadProxy: ({ commit }) => {
return new Promise(resolve => { return new Promise(resolve => {
ipcRenderer.once('response-get-proxy-configuration', (_, proxy: ProxyConfig | false) => { win.ipcRenderer.once('response-get-proxy-configuration', (_, proxy: ProxyConfig | false) => {
commit(MUTATION_TYPES.UPDATE_PROXY_CONFIGURATION, proxy) commit(MUTATION_TYPES.UPDATE_PROXY_CONFIGURATION, proxy)
resolve(proxy) resolve(proxy)
}) })
ipcRenderer.send('get-proxy-configuration') win.ipcRenderer.send('get-proxy-configuration')
}) })
} }
} }

View File

@ -11,10 +11,13 @@ import TimelineSpace, { TimelineSpaceModuleState } from './TimelineSpace'
import Preferences, { PreferencesModuleState } from './Preferences' import Preferences, { PreferencesModuleState } from './Preferences'
import Settings, { SettingsModuleState } from './Settings' import Settings, { SettingsModuleState } from './Settings'
import organisms, { OrganismsModuleState } from './organisms' import organisms, { OrganismsModuleState } from './organisms'
import { MyWindow } from '~/src/types/global'
Vue.use(Vuex) Vue.use(Vuex)
export type RootState = { const win = window as MyWindow
export interface RootState {
App: AppState App: AppState
GlobalHeader: GlobalHeaderState GlobalHeader: GlobalHeaderState
Login: LoginState Login: LoginState
@ -27,8 +30,8 @@ export type RootState = {
} }
export default new Vuex.Store({ export default new Vuex.Store({
strict: process.env.NODE_ENV !== 'production', strict: win.process.env.NODE_ENV !== 'production',
plugins: process.env.NODE_ENV !== 'production' ? [createLogger({})] : [], plugins: win.process.env.NODE_ENV !== 'production' ? [createLogger({})] : [],
modules: { modules: {
App, App,
GlobalHeader, GlobalHeader,

8
src/types/global.ts Normal file
View File

@ -0,0 +1,8 @@
import { Shell, IpcRenderer, Clipboard } from 'electron'
export interface MyWindow extends Window {
shell: Shell
ipcRenderer: IpcRenderer
clipboard: Clipboard
process: NodeJS.Process
}