793 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
			
		
		
	
	
			793 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
| <template>
 | |
|     <div @mouseover="onMouseOver" id="page" style="position:relative;padding:5px 10px 10px" :class="getThemeClass()" @auxclick.prevent @click.middle="unpinUrlPreview">
 | |
|         <div v-html="styling"></div>
 | |
|         <div v-if="!characters" style="display:flex; align-items:center; justify-content:center; height: 100%;">
 | |
|             <div class="card bg-light" style="width: 400px;">
 | |
|                 <div class="initializer" :class="{visible: !hasCompletedUpgrades, complete: hasCompletedUpgrades, shouldShow: shouldShowSpinner}">
 | |
|                     <div class="title">
 | |
|                         Getting ready, please wait...
 | |
|                         <small>You should only experience this delay once per software update</small>
 | |
|                     </div>
 | |
|                     <i class="fas fa-circle-notch fa-spin search-spinner"></i>
 | |
|                 </div>
 | |
| 
 | |
|                 <BBCodeTester v-show="false"></BBCodeTester>
 | |
| 
 | |
|                 <h3 class="card-header" style="margin-top:0;display:flex">
 | |
|                     {{l('title')}}
 | |
| 
 | |
|                     <a href="#" @click.prevent="showLogs()" class="btn" style="flex:1;text-align:right">
 | |
|                         <span class="fa fa-file-alt"></span> <span class="btn-text">{{l('logs.title')}}</span>
 | |
|                     </a>
 | |
|                 </h3>
 | |
|                 <div class="card-body">
 | |
|                     <div class="alert alert-danger" v-show="error">
 | |
|                         {{error}}
 | |
|                     </div>
 | |
|                     <div class="form-group">
 | |
|                         <label class="control-label" for="account">{{l('login.account')}}</label>
 | |
|                         <input class="form-control" id="account" v-model="settings.account" @keypress.enter="login()"
 | |
|                             :disabled="loggingIn"/>
 | |
|                     </div>
 | |
|                     <div class="form-group">
 | |
|                         <label class="control-label" for="password">{{l('login.password')}}</label>
 | |
|                         <input class="form-control" type="password" id="password" v-model="password" @keypress.enter="login()"
 | |
|                             :disabled="loggingIn"/>
 | |
|                     </div>
 | |
|                     <div class="form-group" v-show="showAdvanced">
 | |
|                         <label class="control-label" for="host">{{l('login.host')}}</label>
 | |
|                         <div class="input-group">
 | |
|                             <input class="form-control" id="host" v-model="settings.host" @keypress.enter="login()" :disabled="loggingIn"/>
 | |
|                             <div class="input-group-append">
 | |
|                                 <button class="btn btn-outline-secondary" @click="resetHost()"><span class="fas fa-undo-alt"></span>
 | |
|                                 </button>
 | |
|                             </div>
 | |
|                         </div>
 | |
|                     </div>
 | |
|                     <div class="form-group">
 | |
|                         <label for="advanced"><input type="checkbox" id="advanced" v-model="showAdvanced"/> {{l('login.advanced')}}</label>
 | |
|                     </div>
 | |
|                     <div class="form-group">
 | |
|                         <label for="save"><input type="checkbox" id="save" v-model="saveLogin"/> {{l('login.save')}}</label>
 | |
|                     </div>
 | |
|                     <div class="form-group" style="margin:0;text-align:right">
 | |
|                         <button class="btn btn-primary" @click="login" :disabled="loggingIn">
 | |
|                             {{l(loggingIn ? 'login.working' : 'login.submit')}}
 | |
|                         </button>
 | |
|                     </div>
 | |
|                 </div>
 | |
|             </div>
 | |
|         </div>
 | |
|         <chat v-else :ownCharacters="characters" :defaultCharacter="defaultCharacter" ref="chat"></chat>
 | |
|         <div ref="linkPreview" class="link-preview"></div>
 | |
|         <modal :action="l('importer.importing')" ref="importModal" :buttons="false">
 | |
|             <span style="white-space:pre-wrap">{{l('importer.importingNote')}}</span>
 | |
|             <div class="progress" style="margin-top:5px">
 | |
|                 <div class="progress-bar" :style="{width: importProgress * 100 + '%'}"></div>
 | |
|             </div>
 | |
|         </modal>
 | |
|         <modal :buttons="false" ref="profileViewer" dialogClass="profile-viewer" >
 | |
|             <character-page :authenticated="true" :oldApi="true" :name="profileName" :image-preview="true" ref="characterPage"></character-page>
 | |
|             <template slot="title">
 | |
|                 {{profileName}}
 | |
|                 <a class="btn" @click="openProfileInBrowser"><i class="fa fa-external-link-alt"/></a>
 | |
|                 <a class="btn" @click="openConversation"><i class="fa fa-comment"></i></a>
 | |
|                 <a class="btn" @click="reloadCharacter"><i class="fa fa-sync" /></a>
 | |
| 
 | |
|                 <i class="fas fa-circle-notch fa-spin profileRefreshSpinner" v-show="isRefreshingProfile()"></i>
 | |
| 
 | |
|                 <bbcode :text="profileStatus" v-show="!!profileStatus" class="status-text"></bbcode>
 | |
| 
 | |
|                 <div class="profile-title-right">
 | |
|                   <button class="btn" @click="prevProfile" :disabled="!prevProfileAvailable()"><i class="fas fa-arrow-left"></i></button>
 | |
|                   <button class="btn" @click="nextProfile" :disabled="!nextProfileAvailable()"><i class="fas fa-arrow-right"></i></button>
 | |
|                 </div>
 | |
|             </template>
 | |
|         </modal>
 | |
|         <modal :action="l('fixLogs.action')" ref="fixLogsModal" @submit="fixLogs" buttonClass="btn-danger">
 | |
|             <span style="white-space:pre-wrap">{{l('fixLogs.text')}}</span>
 | |
|             <div class="form-group">
 | |
|                 <label class="control-label">{{l('fixLogs.character')}}</label>
 | |
|                 <select id="import" class="form-control" v-model="fixCharacter">
 | |
|                     <option v-for="character in fixCharacters" :value="character">{{character}}</option>
 | |
|                 </select>
 | |
|             </div>
 | |
|         </modal>
 | |
|         <modal :buttons="false" ref="wordDefinitionViewer" dialogClass="word-definition-viewer">
 | |
|             <word-definition :expression="wordDefinitionLookup" ref="wordDefinitionLookup"></word-definition>
 | |
|             <template slot="title">
 | |
|                 {{wordDefinitionLookup}}
 | |
|                 <a class="btn wordDefBtn dictionary" @click="openDefinitionWithDictionary"><i>D</i></a>
 | |
|                 <a class="btn wordDefBtn thesaurus" @click="openDefinitionWithThesaurus"><i>T</i></a>
 | |
|                 <a class="btn wordDefBtn urbandictionary" @click="openDefinitionWithUrbanDictionary"><i>UD</i></a>
 | |
|                 <a class="btn wordDefBtn wikipedia" @click="openDefinitionWithWikipedia"><i>W</i></a>
 | |
| 
 | |
|                 <a class="btn" @click="openWordDefinitionInBrowser"><i class="fa fa-external-link-alt"/></a>
 | |
|             </template>
 | |
|         </modal>
 | |
| 
 | |
|         <logs ref="logsDialog"></logs>
 | |
|     </div>
 | |
| </template>
 | |
| 
 | |
| <script lang="ts">
 | |
|     import { Component, Hook, Watch } from '@f-list/vue-ts';
 | |
|     import Axios from 'axios';
 | |
|     import * as electron from 'electron';
 | |
|     import * as remote from '@electron/remote';
 | |
|     import log from 'electron-log'; //tslint:disable-line:match-default-export-name
 | |
|     import * as fs from 'fs';
 | |
|     import * as path from 'path';
 | |
|     import * as qs from 'querystring';
 | |
|     import Raven from 'raven-js';
 | |
|     // import {promisify} from 'util';
 | |
|     import Vue from 'vue';
 | |
|     import Chat from '../chat/Chat.vue';
 | |
|     import {getKey, Settings} from '../chat/common';
 | |
|     import core /*, { init as initCore }*/ from '../chat/core';
 | |
|     import l from '../chat/localize';
 | |
|     import Logs from '../chat/Logs.vue';
 | |
|     import Socket from '../chat/WebSocket';
 | |
|     import Modal from '../components/Modal.vue';
 | |
|     import {SimpleCharacter} from '../interfaces';
 | |
|     import {Keys} from '../keys';
 | |
|     // import { BetterSqliteStore } from '../learn/store/better-sqlite3';
 | |
|     // import { Sqlite3Store } from '../learn/store/sqlite3';
 | |
|     import CharacterPage from '../site/character_page/character_page.vue';
 | |
|     import WordDefinition from '../learn/dictionary/WordDefinition.vue';
 | |
|     import ProfileAnalysis from '../learn/recommend/ProfileAnalysis.vue';
 | |
|     import {defaultHost, GeneralSettings, nativeRequire} from './common';
 | |
|     import { fixLogs /*SettingsStore, Logs as FSLogs*/ } from './filesystem';
 | |
|     import * as SlimcatImporter from './importer';
 | |
|     import _ from 'lodash';
 | |
|     import { EventBus } from '../chat/preview/event-bus';
 | |
| 
 | |
|     import BBCodeTester from '../bbcode/Tester.vue';
 | |
|     import { BBCodeView } from '../bbcode/view';
 | |
| 
 | |
|     // import ImagePreview from '../chat/preview/ImagePreview.vue';
 | |
|     // import Bluebird from 'bluebird';
 | |
|     // import Connection from '../fchat/connection';
 | |
|     // import Notifications from './notifications';
 | |
| 
 | |
|     const webContents = remote.getCurrentWebContents();
 | |
|     const parent = remote.getCurrentWindow().webContents;
 | |
| 
 | |
|     // Allow requests to imgur.com
 | |
|     const session = remote.session;
 | |
| 
 | |
|     /* tslint:disable:no-unsafe-any no-any no-unnecessary-type-assertion */
 | |
|     session!.defaultSession!.webRequest!.onBeforeSendHeaders(
 | |
|         {
 | |
|             urls: [
 | |
|                 'https://(api|i).imgur.com/.*',
 | |
|                 'http://(api|i).imgur.com/.*'
 | |
|             ]
 | |
|         },
 | |
|         (details: any, callback: any) => {
 | |
|             details.requestHeaders['Origin'] = null;
 | |
|             details.headers['Origin'] = null;
 | |
| 
 | |
|             callback({requestHeaders: details.requestHeaders});
 | |
|         }
 | |
|     );
 | |
| 
 | |
|     log.info('init.chat.keytar.load.start');
 | |
| 
 | |
|     /* tslint:disable: no-any no-unsafe-any */ //because this is hacky
 | |
|     //
 | |
| 
 | |
|     const keyStore = nativeRequire<
 | |
|       {
 | |
|         getPassword(service: string, account: string): Promise<string>
 | |
|         setPassword(service: string, account: string, password: string): Promise<void>
 | |
|         deletePassword(service: string, account: string): Promise<void>
 | |
|         findCredentials(service: string): Promise<{ account: string, password: string }>
 | |
|         findPassword(service: string): Promise<string>
 | |
|         [key: string]: (...args: any[]) => Promise<any>
 | |
|       }
 | |
|     >('keytar/build/Release/keytar.node');
 | |
| 
 | |
|     // const keyStore = import('keytar');
 | |
|     //
 | |
|     // for(const key in keyStore) keyStore[key] = promisify(<(...args: any[]) => any>keyStore[key].bind(keyStore, 'fchat'));
 | |
|     //tslint:enable
 | |
| 
 | |
|     log.info('init.chat.keytar.load.done');
 | |
| 
 | |
|     @Component({
 | |
|         components: {
 | |
|           chat: Chat,
 | |
|           modal: Modal,
 | |
|           characterPage: CharacterPage,
 | |
|           logs: Logs,
 | |
|           'word-definition': WordDefinition,
 | |
|           BBCodeTester: BBCodeTester,
 | |
|           bbcode: BBCodeView(core.bbCodeParser),
 | |
|           'profile-analysis': ProfileAnalysis
 | |
|         }
 | |
|     })
 | |
|     export default class Index extends Vue {
 | |
|         showAdvanced = false;
 | |
|         saveLogin = false;
 | |
|         loggingIn = false;
 | |
|         password = '';
 | |
|         character?: string;
 | |
|         characters?: SimpleCharacter[];
 | |
|         error = '';
 | |
|         defaultCharacter?: number;
 | |
|         l = l;
 | |
|         settings!: GeneralSettings;
 | |
|         hasCompletedUpgrades!: boolean;
 | |
|         importProgress = 0;
 | |
|         profileName = '';
 | |
|         profileStatus = '';
 | |
|         adName = '';
 | |
|         fixCharacters: ReadonlyArray<string> = [];
 | |
|         fixCharacter = '';
 | |
|         wordDefinitionLookup = '';
 | |
| 
 | |
|         shouldShowSpinner = false;
 | |
| 
 | |
|         profileNameHistory: string[] = [];
 | |
|         profilePointer = 0;
 | |
| 
 | |
| 
 | |
|         async startAndUpgradeCache(): Promise<void> {
 | |
|             log.debug('init.chat.cache.start');
 | |
| 
 | |
|             const timer = setTimeout(
 | |
|               () => {
 | |
|                 this.shouldShowSpinner = true;
 | |
|               },
 | |
|               250
 | |
|             );
 | |
| 
 | |
|             // tslint:disable-next-line no-floating-promises
 | |
|             await core.cache.start(this.settings, this.hasCompletedUpgrades);
 | |
| 
 | |
|             log.debug('init.chat.cache.done');
 | |
| 
 | |
|             clearTimeout(timer);
 | |
| 
 | |
|             parent.send('rising-upgrade-complete');
 | |
|             electron.ipcRenderer.send('rising-upgrade-complete');
 | |
| 
 | |
|             this.hasCompletedUpgrades = true;
 | |
|         }
 | |
| 
 | |
| 
 | |
|         @Watch('profileName')
 | |
|         onProfileNameChange(newName: string): void {
 | |
|           if (this.profileNameHistory[this.profilePointer] !== newName) {
 | |
|             this.profileNameHistory = _.takeRight(
 | |
|               _.filter(
 | |
|                 _.take(this.profileNameHistory, this.profilePointer + 1),
 | |
|                 (n) => (n !== newName)
 | |
|               ),
 | |
|               30
 | |
|             );
 | |
| 
 | |
|             this.profileNameHistory.push(newName);
 | |
| 
 | |
|             this.profilePointer = this.profileNameHistory.length - 1;
 | |
|           }
 | |
|         }
 | |
| 
 | |
| 
 | |
|         @Hook('mounted')
 | |
|         onMounted(): void {
 | |
|             log.debug('init.chat.mounted');
 | |
| 
 | |
|             EventBus.$on(
 | |
|               'word-definition',
 | |
|               (data: any) => {
 | |
|                 this.wordDefinitionLookup = data.lookupWord;
 | |
| 
 | |
|                 if (!!data.lookupWord) {
 | |
|                   (<Modal>this.$refs.wordDefinitionViewer).show();
 | |
|                 }
 | |
|               }
 | |
|             );
 | |
|         }
 | |
| 
 | |
| 
 | |
|         @Hook('created')
 | |
|         async created(): Promise<void> {
 | |
|             await this.startAndUpgradeCache();
 | |
| 
 | |
|             if(this.settings.account.length > 0) this.saveLogin = true;
 | |
| 
 | |
|             keyStore.getPassword('f-list.net', this.settings.account)
 | |
|                 .then((value: string) => this.password = value, (err: Error) => this.error = err.message);
 | |
| 
 | |
|             log.debug('init.chat.keystore.get.done');
 | |
| 
 | |
|             Vue.set(core.state, 'generalSettings', this.settings);
 | |
| 
 | |
|             electron.ipcRenderer.on('settings', (_e: Event, settings: GeneralSettings) => {
 | |
|               log.debug('settings.update.index');
 | |
|               core.state.generalSettings = this.settings = settings;
 | |
|             });
 | |
| 
 | |
|             electron.ipcRenderer.on('open-profile', (_e: Event, name: string) => {
 | |
|                 const profileViewer = <Modal>this.$refs['profileViewer'];
 | |
| 
 | |
|                 this.openProfile(name);
 | |
| 
 | |
|                 profileViewer.show();
 | |
|             });
 | |
| 
 | |
|             electron.ipcRenderer.on('reopen-profile', (_e: Event) => {
 | |
|               if (
 | |
|                 (this.profileNameHistory.length > 0)
 | |
|                 && (this.profilePointer < this.profileNameHistory.length)
 | |
|                 && (this.profilePointer >= 0)
 | |
|               ) {
 | |
|                 const name = this.profileNameHistory[this.profilePointer];
 | |
|                 const profileViewer = <Modal>this.$refs['profileViewer'];
 | |
| 
 | |
|                 if ((this.profileName === name) && (profileViewer.isShown)) {
 | |
|                   profileViewer.hide();
 | |
|                   return;
 | |
|                 }
 | |
| 
 | |
|                 this.openProfile(name);
 | |
|                 profileViewer.show();
 | |
|               }
 | |
|             });
 | |
| 
 | |
|             electron.ipcRenderer.on('fix-logs', async() => {
 | |
|                 this.fixCharacters = await core.settingsStore.getAvailableCharacters();
 | |
|                 this.fixCharacter = this.fixCharacters[0];
 | |
|                 (<Modal>this.$refs['fixLogsModal']).show();
 | |
|             });
 | |
| 
 | |
|             electron.ipcRenderer.on('update-zoom', (_e, zoomLevel) => {
 | |
|                 webContents.setZoomLevel(zoomLevel);
 | |
|                 // log.info('INDEXVUE ZOOM UPDATE', zoomLevel);
 | |
|             });
 | |
| 
 | |
|             electron.ipcRenderer.on('active-tab', () => {
 | |
|                 core.cache.setTabActive(true);
 | |
|             });
 | |
| 
 | |
|             electron.ipcRenderer.on('inactive-tab', () => {
 | |
|                 core.cache.setTabActive(false);
 | |
|             });
 | |
| 
 | |
|             window.addEventListener('keydown', (e) => {
 | |
|                 const key = getKey(e);
 | |
| 
 | |
|                 if ((key === Keys.Tab) && (e.ctrlKey) && (!e.altKey)) {
 | |
|                     parent.send(`${e.shiftKey ? 'previous' : 'switch'}-tab`, this.character);
 | |
|                 }
 | |
| 
 | |
|                 if (((key === Keys.PageDown) || (key === Keys.PageUp)) && (e.ctrlKey) && (!e.altKey) && (!e.shiftKey)) {
 | |
|                   parent.send(`${key === Keys.PageUp ? 'previous' : 'switch'}-tab`, this.character);
 | |
|                 }
 | |
|             });
 | |
| 
 | |
|             log.debug('init.chat.listeners.done');
 | |
| 
 | |
|             /*if (process.env.NODE_ENV !== 'production') {
 | |
|                 const dt = require('@vue/devtools');
 | |
| 
 | |
|                 dt.connect();
 | |
|             }*/
 | |
|         }
 | |
| 
 | |
|         async login(): Promise<void> {
 | |
|             if(this.loggingIn) return;
 | |
|             this.loggingIn = true;
 | |
|             try {
 | |
|                 if(!this.saveLogin) await keyStore.deletePassword('f-list.net', this.settings.account);
 | |
| 
 | |
|                 core.siteSession.setCredentials(this.settings.account, this.password);
 | |
| 
 | |
|                 const data = <{ticket?: string, error: string, characters: {[key: string]: number}, default_character: number}>
 | |
|                     (await Axios.post('https://www.f-list.net/json/getApiTicket.php', qs.stringify({
 | |
|                         account: this.settings.account, password: this.password, no_friends: true, no_bookmarks: true,
 | |
|                         new_character_list: true
 | |
|                     }))).data;
 | |
|                 if(data.error !== '') {
 | |
|                     this.error = data.error;
 | |
|                     return;
 | |
|                 }
 | |
|                 if(this.saveLogin) {
 | |
|                     electron.ipcRenderer.send('save-login', this.settings.account, this.settings.host);
 | |
|                     await keyStore.setPassword('f-list.net', this.settings.account, this.password);
 | |
|                 }
 | |
|                 Socket.host = this.settings.host;
 | |
| 
 | |
|                 core.connection.onEvent('connecting', async() => {
 | |
|                     if(!electron.ipcRenderer.sendSync('connect', core.connection.character) && process.env.NODE_ENV === 'production') {
 | |
|                         alert(l('login.alreadyLoggedIn'));
 | |
|                         return core.connection.close();
 | |
|                     }
 | |
|                     parent.send('connect', webContents.id, core.connection.character);
 | |
|                     this.character = core.connection.character;
 | |
|                     if((await core.settingsStore.get('settings')) === undefined &&
 | |
|                         SlimcatImporter.canImportCharacter(core.connection.character)) {
 | |
|                         if(!confirm(l('importer.importGeneral'))) return core.settingsStore.set('settings', new Settings());
 | |
|                         (<Modal>this.$refs['importModal']).show(true);
 | |
|                         await SlimcatImporter.importCharacter(core.connection.character, (progress) => this.importProgress = progress);
 | |
|                         (<Modal>this.$refs['importModal']).hide();
 | |
|                     }
 | |
|                 });
 | |
|                 core.connection.onEvent('connected', () => {
 | |
|                     core.watch(() => core.conversations.hasNew, (newValue) => parent.send('has-new', webContents.id, newValue));
 | |
|                     Raven.setUserContext({username: core.connection.character});
 | |
|                 });
 | |
|                 core.connection.onEvent('closed', () => {
 | |
|                     if(this.character === undefined) return;
 | |
|                     electron.ipcRenderer.send('disconnect', this.character);
 | |
|                     this.character = undefined;
 | |
|                     parent.send('disconnect', webContents.id);
 | |
|                     Raven.setUserContext();
 | |
|                 });
 | |
|                 core.connection.setCredentials(this.settings.account, this.password);
 | |
|                 this.characters = Object.keys(data.characters).map((name) => ({name, id: data.characters[name], deleted: false}))
 | |
|                     .sort((x, y) => x.name.localeCompare(y.name));
 | |
|                 this.defaultCharacter = data.default_character;
 | |
|             } catch(e) {
 | |
|                 this.error = l('login.error');
 | |
|                 log.error('connect.error', e);
 | |
|                 if(process.env.NODE_ENV !== 'production') throw e;
 | |
|             } finally {
 | |
|                 this.loggingIn = false;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         fixLogs(): void {
 | |
|             if(!electron.ipcRenderer.sendSync('connect', this.fixCharacter)) return alert(l('login.alreadyLoggedIn'));
 | |
|             try {
 | |
|                 fixLogs(this.fixCharacter);
 | |
|                 alert(l('fixLogs.success'));
 | |
|             } catch(e) {
 | |
|                 alert(l('fixLogs.error'));
 | |
|                 throw e;
 | |
|             } finally {
 | |
|                 electron.ipcRenderer.send('disconnect', this.fixCharacter);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         resetHost(): void {
 | |
|             this.settings.host = defaultHost;
 | |
|         }
 | |
| 
 | |
|         onMouseOver(e: MouseEvent): void {
 | |
|             const preview = (<HTMLDivElement>this.$refs.linkPreview);
 | |
|             if((<HTMLElement>e.target).tagName === 'A') {
 | |
|                 const target = <HTMLAnchorElement>e.target;
 | |
|                 if(target.hostname !== '') {
 | |
|                     //tslint:disable-next-line:prefer-template
 | |
|                     preview.className = 'link-preview ' +
 | |
|                         (e.clientX < window.innerWidth / 2 && e.clientY > window.innerHeight - 150 ? ' right' : '');
 | |
|                     preview.textContent = target.href;
 | |
|                     preview.style.display = 'block';
 | |
|                     return;
 | |
|                 }
 | |
|             }
 | |
|             preview.textContent = '';
 | |
|             preview.style.display = 'none';
 | |
|         }
 | |
| 
 | |
|         async openProfileInBrowser(): Promise<void> {
 | |
|             await remote.shell.openExternal(`https://www.f-list.net/c/${this.profileName}`);
 | |
| 
 | |
|             // tslint:disable-next-line: no-any no-unsafe-any
 | |
|             (this.$refs.profileViewer as any).hide();
 | |
|         }
 | |
| 
 | |
|         openConversation(): void {
 | |
|             //this.
 | |
|             // this.profileName
 | |
|             const character = core.characters.get(this.profileName);
 | |
|             const conversation = core.conversations.getPrivate(character);
 | |
| 
 | |
|             conversation.show();
 | |
| 
 | |
|             // tslint:disable-next-line: no-any no-unsafe-any
 | |
|             (this.$refs.profileViewer as any).hide();
 | |
|         }
 | |
| 
 | |
| 
 | |
|         isRefreshingProfile(): boolean {
 | |
|           const cp = this.$refs.characterPage as CharacterPage;
 | |
| 
 | |
|           return cp && cp.refreshing;
 | |
|         }
 | |
| 
 | |
| 
 | |
|         reloadCharacter(): void {
 | |
|             // tslint:disable-next-line: no-any no-unsafe-any
 | |
|             (this.$refs.characterPage as any).reload();
 | |
|         }
 | |
| 
 | |
| 
 | |
|         getThemeClass(): Record<string, boolean> {
 | |
|           // console.log('getThemeClassIndex', core.state.generalSettings?.risingDisableWindowsHighContrast);
 | |
| 
 | |
|           try {
 | |
|             // Hack!
 | |
|             if (process.platform === 'win32') {
 | |
|               if (core.state.generalSettings?.risingDisableWindowsHighContrast) {
 | |
|                 document.querySelector('html')?.classList.add('disableWindowsHighContrast');
 | |
|               } else {
 | |
|                 document.querySelector('html')?.classList.remove('disableWindowsHighContrast');
 | |
|               }
 | |
|             }
 | |
| 
 | |
|             return {
 | |
|               [`theme-${this.settings.theme}`]: true,
 | |
|               colorblindMode: core.state.settings.risingColorblindMode,
 | |
|               disableWindowsHighContrast: core.state.generalSettings?.risingDisableWindowsHighContrast || false
 | |
|             };
 | |
|           } catch(err) {
 | |
|             return { [`theme-${this.settings.theme}`]: true };
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         nextProfile(): void {
 | |
|           if (!this.nextProfileAvailable()) {
 | |
|             return;
 | |
|           }
 | |
| 
 | |
|           this.profilePointer++;
 | |
| 
 | |
|           this.openProfile(this.profileNameHistory[this.profilePointer]);
 | |
|         }
 | |
| 
 | |
| 
 | |
|         nextProfileAvailable(): boolean {
 | |
|           return (this.profilePointer < this.profileNameHistory.length - 1);
 | |
|         }
 | |
| 
 | |
| 
 | |
|         prevProfile(): void {
 | |
|           if (!this.prevProfileAvailable()) {
 | |
|             return;
 | |
|           }
 | |
| 
 | |
|           this.profilePointer--;
 | |
| 
 | |
|           this.openProfile(this.profileNameHistory[this.profilePointer]);
 | |
|         }
 | |
| 
 | |
| 
 | |
|         prevProfileAvailable(): boolean {
 | |
|           return (this.profilePointer > 0);
 | |
|         }
 | |
| 
 | |
|         openProfile(name: string) {
 | |
|           this.profileName = name;
 | |
| 
 | |
|           const character = core.characters.get(name);
 | |
| 
 | |
|           this.profileStatus = character.statusText || '';
 | |
|         }
 | |
| 
 | |
|         get styling(): string {
 | |
|             try {
 | |
|                 return `<style id="themeStyle">${fs.readFileSync(path.join(__dirname, `themes/${this.settings.theme}.css`), 'utf8').toString()}</style>`;
 | |
|             } catch(e) {
 | |
|                 if((<Error & {code: string}>e).code === 'ENOENT' && this.settings.theme !== 'default') {
 | |
|                     this.settings.theme = 'default';
 | |
|                     return this.styling;
 | |
|                 }
 | |
|                 throw e;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         showLogs(): void {
 | |
|             (<Logs>this.$refs['logsDialog']).show();
 | |
|         }
 | |
| 
 | |
| 
 | |
|         async openDefinitionWithDictionary(): Promise<void> {
 | |
|           (this.$refs.wordDefinitionLookup as any).setMode('dictionary');
 | |
|         }
 | |
| 
 | |
| 
 | |
|         async openDefinitionWithThesaurus(): Promise<void> {
 | |
|           (this.$refs.wordDefinitionLookup as any).setMode('thesaurus');
 | |
|         }
 | |
| 
 | |
| 
 | |
|         async openDefinitionWithUrbanDictionary(): Promise<void> {
 | |
|           (this.$refs.wordDefinitionLookup as any).setMode('urbandictionary');
 | |
|         }
 | |
| 
 | |
| 
 | |
|         async openDefinitionWithWikipedia(): Promise<void> {
 | |
|           (this.$refs.wordDefinitionLookup as any).setMode('wikipedia');
 | |
|         }
 | |
| 
 | |
| 
 | |
|         async openWordDefinitionInBrowser(): Promise<void> {
 | |
|           await remote.shell.openExternal((this.$refs.wordDefinitionLookup as any).getWebUrl());
 | |
| 
 | |
|           // tslint:disable-next-line: no-any no-unsafe-any
 | |
|           (this.$refs.wordDefinitionViewer as any).hide();
 | |
|         }
 | |
| 
 | |
| 
 | |
|         unpinUrlPreview(e: Event): void {
 | |
|           const imagePreview = (this.$refs['chat'] as Chat)?.getChatView()?.getImagePreview();
 | |
| 
 | |
|           // const imagePreview = this.$refs['imagePreview'] as ImagePreview;
 | |
| 
 | |
|           if ((imagePreview) && (imagePreview.isVisible()) && (imagePreview.sticky)) {
 | |
|             e.stopPropagation();
 | |
|             e.preventDefault();
 | |
| 
 | |
|             EventBus.$emit('imagepreview-toggle-stickyness', {force: true});
 | |
|           }
 | |
|         }
 | |
| 
 | |
|     }
 | |
| </script>
 | |
| 
 | |
| <style lang="scss">
 | |
|     html, body, #page {
 | |
|         height: 100%;
 | |
|     }
 | |
| 
 | |
|     a[href^="#"]:not([draggable]) {
 | |
|         -webkit-user-drag: none;
 | |
|         -webkit-app-region: no-drag;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     .profileRefreshSpinner {
 | |
|         font-size: 12pt;
 | |
|         opacity: 0.5;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     .profile-viewer {
 | |
|       .modal-title {
 | |
|         width: 100%;
 | |
|         position: relative;
 | |
| 
 | |
|         .profile-title-right {
 | |
|           float: right;
 | |
|           top: -7px;
 | |
|           right: 0;
 | |
|           position: absolute;
 | |
|         }
 | |
| 
 | |
|         .status-text {
 | |
|           font-size: 12pt;
 | |
|           display: block;
 | |
|           max-height: 3em;
 | |
|           overflow: auto;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
| 
 | |
|     .initializer {
 | |
|         position: fixed;
 | |
|         top: 0;
 | |
|         left: 0;
 | |
|         right: 0;
 | |
|         bottom: 0;
 | |
|         display: flex;
 | |
|         align-items: center;
 | |
|         justify-content: center;
 | |
|         opacity: 0;
 | |
|         backdrop-filter: blur(3px) grayscale(35%);
 | |
| 
 | |
|         &.shouldShow {
 | |
|             transition: all 0.25s;
 | |
| 
 | |
|             &.visible {
 | |
|                 opacity: 1;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         &.complete {
 | |
|             pointer-events: none !important;
 | |
|         }
 | |
| 
 | |
|         i {
 | |
|             font-size: 130pt;
 | |
|             top: 50%;
 | |
|             right: 50%;
 | |
|             transform: translate(-50%, -50%);
 | |
|             width: fit-content;
 | |
|         }
 | |
| 
 | |
|         .title {
 | |
|             position: absolute;
 | |
|             top: 0;
 | |
|             background: rgba(147, 255, 215, 0.6);
 | |
|             width: 100%;
 | |
|             text-align: center;
 | |
|             padding-top: 20px;
 | |
|             padding-bottom: 20px;
 | |
|             font-weight: bold;
 | |
| 
 | |
|             small {
 | |
|                 display: block;
 | |
|                 opacity: 0.8;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
| 
 | |
|     .btn.wordDefBtn {
 | |
|         background-color: red;
 | |
|         padding: 0.2rem 0.2rem;
 | |
|         line-height: 90%;
 | |
|         margin-right: 0.2rem;
 | |
|         text-align: center;
 | |
| 
 | |
|         i {
 | |
|             font-style: normal !important;
 | |
|             color: white;
 | |
|             font-weight: bold
 | |
|         }
 | |
| 
 | |
|         &.thesaurus {
 | |
|             background-color: #F44725
 | |
|         }
 | |
| 
 | |
|         &.urbandictionary {
 | |
|             background-color: #d96a36;
 | |
| 
 | |
|             i {
 | |
|                 color: #fadf4b;
 | |
|                 text-transform: lowercase;
 | |
|                 font-family: monospace;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         &.dictionary {
 | |
|             background-color: #314ca7;
 | |
|         }
 | |
| 
 | |
|         &.wikipedia {
 | |
|             background-color: white;
 | |
| 
 | |
|             i {
 | |
|                 color: black;
 | |
|                 font-family: serif;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     .modal {
 | |
|       .word-definition-viewer {
 | |
|         max-width: 50rem !important;
 | |
|         width: 70% !important;
 | |
|         min-width: 22rem !important;
 | |
| 
 | |
|         .modal-content {
 | |
|           min-height: 75%;
 | |
|         }
 | |
| 
 | |
|         .definition-wrapper {
 | |
|           position: absolute;
 | |
|           left: 0;
 | |
|           right: 0;
 | |
|           top: 0;
 | |
|           bottom: 0;
 | |
|           margin-left: 20px;
 | |
|           margin-right: 20px;
 | |
| 
 | |
|           webview {
 | |
|             height: 100%;
 | |
|             padding-bottom: 10px;
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     .disableWindowsHighContrast, .disableWindowsHighContrast * {
 | |
|       forced-color-adjust: none;
 | |
|     }
 | |
| </style>
 |