0.2.1 - Open beta
This commit is contained in:
parent
878389f717
commit
51d8ba1680
|
@ -12,10 +12,15 @@
|
|||
</filterable-select>
|
||||
<div v-show="!data.kinks.length" class="alert alert-warning">{{l('characterSearch.kinkNotice')}}</div>
|
||||
</div>
|
||||
<div v-else-if="results">
|
||||
<h5>{{l('characterSearch.results')}}</h5>
|
||||
<div v-for="character in results">
|
||||
<user :character="character"></user>
|
||||
<div v-else-if="results" class="results">
|
||||
<h4>{{l('characterSearch.results')}}</h4>
|
||||
<div v-for="character in results" :key="character.name" :class="'status-' + character.status">
|
||||
<template v-if="character.status === 'looking'" v-once>
|
||||
<img :src="characterImage(character.name)" v-if="showAvatars"/>
|
||||
<user :character="character" :showStatus="true"></user>
|
||||
<bbcode :text="character.statusText"></bbcode>
|
||||
</template>
|
||||
<user v-else :character="character" :showStatus="true" v-once></user>
|
||||
</div>
|
||||
</div>
|
||||
</modal>
|
||||
|
@ -27,6 +32,8 @@
|
|||
import CustomDialog from '../components/custom_dialog';
|
||||
import FilterableSelect from '../components/FilterableSelect.vue';
|
||||
import Modal from '../components/Modal.vue';
|
||||
import {BBCodeView} from './bbcode';
|
||||
import {characterImage} from './common';
|
||||
import core from './core';
|
||||
import {Character, Connection} from './interfaces';
|
||||
import l from './localize';
|
||||
|
@ -41,8 +48,16 @@
|
|||
|
||||
type Kink = {id: number, name: string, description: string};
|
||||
|
||||
function sort(x: Character, y: Character): number {
|
||||
if(x.status === 'looking' && y.status !== 'looking') return -1;
|
||||
if(x.status !== 'looking' && y.status === 'looking') return 1;
|
||||
if(x.name < y.name) return -1;
|
||||
if(x.name > y.name) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Component({
|
||||
components: {modal: Modal, user: UserView, 'filterable-select': FilterableSelect}
|
||||
components: {modal: Modal, user: UserView, 'filterable-select': FilterableSelect, bbcode: BBCodeView}
|
||||
})
|
||||
export default class CharacterSearch extends CustomDialog {
|
||||
//tslint:disable:no-null-keyword
|
||||
|
@ -50,6 +65,7 @@
|
|||
kinksFilter = '';
|
||||
error = '';
|
||||
results: Character[] | null = null;
|
||||
characterImage = characterImage;
|
||||
options: {
|
||||
kinks: Kink[]
|
||||
genders: string[]
|
||||
|
@ -82,7 +98,6 @@
|
|||
roles: options.listitems.filter((x) => x.name === 'subdom').map((x) => x.value),
|
||||
positions: options.listitems.filter((x) => x.name === 'position').map((x) => x.value)
|
||||
};
|
||||
this.$nextTick(() => (<Modal>this.$children[0]).fixDropdowns());
|
||||
}
|
||||
|
||||
mounted(): void {
|
||||
|
@ -98,7 +113,8 @@
|
|||
this.error = l('characterSearch.error.tooManyResults');
|
||||
}
|
||||
});
|
||||
core.connection.onMessage('FKS', (data) => this.results = data.characters.map((x: string) => core.characters.get(x)));
|
||||
core.connection.onMessage('FKS', (data) => this.results = data.characters.map((x: string) => core.characters.get(x)).sort(sort));
|
||||
(<Modal>this.$children[0]).fixDropdowns();
|
||||
}
|
||||
|
||||
filterKink(filter: RegExp, kink: Kink): boolean {
|
||||
|
@ -107,6 +123,10 @@
|
|||
return filter.test(kink.name);
|
||||
}
|
||||
|
||||
get showAvatars(): boolean {
|
||||
return core.state.settings.showAvatars;
|
||||
}
|
||||
|
||||
submit(): void {
|
||||
if(this.results !== null) {
|
||||
this.results = null;
|
||||
|
@ -122,8 +142,25 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.character-search .dropdown {
|
||||
margin-bottom: 10px;
|
||||
<style lang="less">
|
||||
.character-search {
|
||||
.dropdown {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.results {
|
||||
.user-view {
|
||||
display: block;
|
||||
}
|
||||
& > .status-looking {
|
||||
margin-bottom: 5px;
|
||||
min-height: 50px;
|
||||
}
|
||||
img {
|
||||
float: left;
|
||||
margin-right: 5px;
|
||||
width: 50px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -84,7 +84,12 @@
|
|||
|
||||
connect(): void {
|
||||
this.connecting = true;
|
||||
core.connection.connect(this.selectedCharacter);
|
||||
try {
|
||||
core.connection.connect(this.selectedCharacter);
|
||||
} catch(e) {
|
||||
if(e.request !== undefined) this.error = l('login.connectError'); //catch axios network errors
|
||||
else throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -169,6 +169,12 @@
|
|||
core.connection.send('STA', {status: 'idle', statusmsg: ownCharacter.statusText});
|
||||
}, core.state.settings.idleTimer * 60000);
|
||||
};
|
||||
core.connection.onEvent('closed', () => {
|
||||
if(idleTimer !== undefined) {
|
||||
window.clearTimeout(idleTimer);
|
||||
idleTimer = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
logOut(): void {
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div style="z-index:5; position:absolute; left:0; right:32px; max-height:60%; overflow:auto;"
|
||||
<div style="z-index:5;position:absolute;left:0;right:0;max-height:60%;overflow:auto"
|
||||
:style="'display:' + (descriptionExpanded ? 'block' : 'none')" class="bg-solid-text">
|
||||
<bbcode :text="conversation.channel.description"></bbcode>
|
||||
</div>
|
||||
|
@ -53,21 +53,18 @@
|
|||
</div>
|
||||
<div class="border-top messages" :class="'messages-' + conversation.mode" style="flex:1;overflow:auto;margin-top:2px"
|
||||
ref="messages" @scroll="onMessagesScroll">
|
||||
<template v-if="!isConsoleTab">
|
||||
<message-view v-for="message in conversation.messages" :message="message" :channel="conversation.channel"
|
||||
:classes="message == conversation.lastRead ? 'last-read' : ''" :key="message.id">
|
||||
<template v-for="message in conversation.messages">
|
||||
<message-view :message="message" :channel="conversation.channel" :key="message.id"
|
||||
:classes="message == conversation.lastRead ? 'last-read' : ''">
|
||||
</message-view>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div v-for="message in conversation.messages" :key="message.id">
|
||||
<message-view :message="message"></message-view>
|
||||
<span v-if="message.sfc && message.sfc.action == 'report'">
|
||||
<a :href="'https://www.f-list.net/fchat/getLog.php?log=' + message.sfc.logid">{{l('events.report.viewLog')}}</a>
|
||||
<span v-show="!message.sfc.confirmed">
|
||||
| <a href="#" @click.prevent="acceptReport(message.sfc)">{{l('events.report.confirm')}}</a>
|
||||
</span>
|
||||
<span v-if="message.sfc && message.sfc.action == 'report'" :key="message.id">
|
||||
<a :href="'https://www.f-list.net/fchat/getLog.php?log=' + message.sfc.logid"
|
||||
v-if="message.sfc.logid">{{l('events.report.viewLog')}}</a>
|
||||
<span v-else>{{l('events.report.noLog')}}</span>
|
||||
<span v-show="!message.sfc.confirmed">
|
||||
| <a href="#" @click.prevent="acceptReport(message.sfc)">{{l('events.report.confirm')}}</a>
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
<div>
|
||||
|
@ -185,8 +182,8 @@
|
|||
}
|
||||
|
||||
onMessagesScroll(): void {
|
||||
const messageView = <HTMLElement>this.$refs['messages'];
|
||||
if(messageView.scrollTop < 50) this.conversation.loadMore();
|
||||
const messageView = <HTMLElement | undefined>this.$refs['messages'];
|
||||
if(messageView !== undefined && messageView.scrollTop < 50) this.conversation.loadMore();
|
||||
}
|
||||
|
||||
@Watch('conversation.errorText')
|
||||
|
|
|
@ -69,7 +69,7 @@
|
|||
character: Character | null = null;
|
||||
position = {left: '', top: ''};
|
||||
characterImage: string | null = null;
|
||||
touchTimer: number;
|
||||
touchTimer: number | undefined;
|
||||
channel: Channel | null = null;
|
||||
memo = '';
|
||||
memoId: number;
|
||||
|
@ -145,8 +145,7 @@
|
|||
}
|
||||
|
||||
handleEvent(e: MouseEvent | TouchEvent): void {
|
||||
if(e.type === 'touchend') return clearTimeout(this.touchTimer);
|
||||
const touch = e instanceof TouchEvent ? e.touches[0] : e;
|
||||
const touch = e instanceof TouchEvent ? e.changedTouches[0] : e;
|
||||
let node = <Node & {character?: Character, channel?: Channel}>touch.target;
|
||||
while(node !== document.body) {
|
||||
if(node.character !== undefined || node.parentNode === null) break;
|
||||
|
@ -158,13 +157,20 @@
|
|||
}
|
||||
switch(e.type) {
|
||||
case 'click':
|
||||
this.character = node.character;
|
||||
if(core.state.settings.clickOpensMessage) this.openConversation(true);
|
||||
else window.open(this.profileLink);
|
||||
this.showContextMenu = false;
|
||||
this.onClick(node.character);
|
||||
break;
|
||||
case 'touchstart':
|
||||
this.touchTimer = window.setTimeout(() => this.openMenu(touch, node.character!, node.channel), 500);
|
||||
this.touchTimer = window.setTimeout(() => {
|
||||
this.openMenu(touch, node.character!, node.channel);
|
||||
this.touchTimer = undefined;
|
||||
}, 500);
|
||||
break;
|
||||
case 'touchend':
|
||||
if(this.touchTimer !== undefined) {
|
||||
clearTimeout(this.touchTimer);
|
||||
this.touchTimer = undefined;
|
||||
this.onClick(node.character);
|
||||
}
|
||||
break;
|
||||
case 'contextmenu':
|
||||
this.openMenu(touch, node.character, node.channel);
|
||||
|
@ -172,6 +178,13 @@
|
|||
e.preventDefault();
|
||||
}
|
||||
|
||||
private onClick(character: Character): void {
|
||||
this.character = character;
|
||||
if(core.state.settings.clickOpensMessage) this.openConversation(true);
|
||||
else window.open(this.profileLink);
|
||||
this.showContextMenu = false;
|
||||
}
|
||||
|
||||
private openMenu(touch: MouseEvent | Touch, character: Character, channel: Channel | undefined): void {
|
||||
this.channel = channel !== undefined ? channel : null;
|
||||
this.character = character;
|
||||
|
|
|
@ -73,7 +73,7 @@ export function errorToString(e: any): string {
|
|||
//tslint:enable
|
||||
|
||||
export async function requestNotificationsPermission(): Promise<void> {
|
||||
if(<object | undefined>Notification !== undefined) await Notification.requestPermission();
|
||||
if((<Window & {Notification: Notification | undefined}>window).Notification !== undefined) await Notification.requestPermission();
|
||||
}
|
||||
|
||||
let messageId = 0;
|
||||
|
@ -84,6 +84,7 @@ export class Message implements Conversation.ChatMessage {
|
|||
|
||||
constructor(readonly type: Conversation.Message.Type, readonly sender: Character, readonly text: string,
|
||||
readonly time: Date = new Date()) {
|
||||
if(Conversation.Message.Type[type] === undefined) throw new Error('Unknown type'); /*tslint:disable-line*/ //TODO debug code
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import {Channel, Character, Connection, Conversation as Interfaces} from './inte
|
|||
import l from './localize';
|
||||
import {CommandContext, isCommand, parse as parseCommand} from './slash_commands';
|
||||
import MessageType = Interfaces.Message.Type;
|
||||
|
||||
function createMessage(this: void, type: MessageType, sender: Character, text: string, time?: Date): Message {
|
||||
if(type === MessageType.Message && text.match(/^\/me\b/) !== null) {
|
||||
type = MessageType.Action;
|
||||
|
@ -179,7 +180,7 @@ class PrivateConversation extends Conversation implements Interfaces.PrivateConv
|
|||
core.connection.send('PRI', {recipient: this.name, message: this.enteredText});
|
||||
const message = createMessage(MessageType.Message, core.characters.ownCharacter, this.enteredText);
|
||||
this.safeAddMessage(message);
|
||||
core.logs.logMessage(this, message);
|
||||
if(core.state.settings.logMessages) this.logPromise.then(() => core.logs.logMessage(this, message));
|
||||
this.enteredText = '';
|
||||
}
|
||||
|
||||
|
@ -205,7 +206,7 @@ class ChannelConversation extends Conversation implements Interfaces.ChannelConv
|
|||
this.chat.unshift(...this.both.filter((x) => x.type !== MessageType.Ad));
|
||||
this.ads.unshift(...this.both.filter((x) => x.type === MessageType.Ad));
|
||||
this.lastRead = this.messages[this.messages.length - 1];
|
||||
this.mode = this.channel.mode;
|
||||
this.messages = this.allMessages.slice(-this.maxMessages);
|
||||
});
|
||||
|
||||
constructor(readonly channel: Channel) {
|
||||
|
@ -218,6 +219,7 @@ class ChannelConversation extends Conversation implements Interfaces.ChannelConv
|
|||
this.mode = value;
|
||||
if(value !== 'both') this.isSendingAds = value === 'ads';
|
||||
});
|
||||
this.mode = this.channel.mode;
|
||||
}
|
||||
|
||||
get maxMessageLength(): number {
|
||||
|
@ -552,7 +554,8 @@ export default function(this: void): Interfaces.State {
|
|||
});
|
||||
connection.onMessage('HLO', (data, time) => addEventMessage(new EventMessage(data.message, time)));
|
||||
connection.onMessage('BRO', (data, time) => {
|
||||
const text = l('events.broadcast', `[user]${data.character}[/user]`, decodeHTML(data.message.substr(data.character.length + 23)));
|
||||
const text = data.character === undefined ? decodeHTML(data.message) :
|
||||
l('events.broadcast', `[user]${data.character}[/user]`, decodeHTML(data.message.substr(data.character.length + 23)));
|
||||
addEventMessage(new EventMessage(text, time));
|
||||
});
|
||||
connection.onMessage('CIU', (data, time) => {
|
||||
|
|
|
@ -12,6 +12,8 @@ const strings: {[key: string]: string | undefined} = {
|
|||
'action.updateAvailable': 'UPDATE AVAILABLE',
|
||||
'action.update': 'Restart now!',
|
||||
'action.cancel': 'Cancel',
|
||||
'consoleWarning.head': 'THIS IS THE DANGER ZONE.',
|
||||
'consoleWarning.body': `ANYTHING YOU WRITE OR PASTE IN HERE COULD BE USED TO STEAL YOUR PASSWORDS OR TAKE OVER YOUR ENTIRE COMPUTER. This is where happiness goes to die. If you aren't a developer or a special kind of daredevil, please get out of here!`,
|
||||
'help.fchat': 'FChat 3.0 Help and Changelog',
|
||||
'help.rules': 'F-List Rules',
|
||||
'help.faq': 'F-List FAQ',
|
||||
|
@ -183,6 +185,7 @@ Are you sure?`,
|
|||
'events.report.confirmed': '{0} is handling {1}\'s report.',
|
||||
'events.report.confirm': 'Confirm report',
|
||||
'events.report.viewLog': 'View log',
|
||||
'events.report.noLog': 'No log available',
|
||||
'events.status': '{0} is now {1}.',
|
||||
'events.status.message': '{0} is now {1}: {2}',
|
||||
'events.status.own': 'You are now {0}.',
|
||||
|
|
|
@ -106,5 +106,10 @@
|
|||
display: flex;
|
||||
text-align: left
|
||||
}
|
||||
|
||||
input[type=checkbox] {
|
||||
vertical-align: text-bottom;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -51,10 +51,11 @@
|
|||
import Vue from 'vue';
|
||||
import Component from 'vue-class-component';
|
||||
import Chat from '../chat/Chat.vue';
|
||||
import Connection from '../chat/connection';
|
||||
import core, {init as initCore} from '../chat/core';
|
||||
import l from '../chat/localize';
|
||||
import Socket from '../chat/WebSocket';
|
||||
import Modal from '../components/Modal.vue';
|
||||
import Connection from '../fchat/connection';
|
||||
import {GeneralSettings, getGeneralSettings, Logs, setGeneralSettings, SettingsStore} from './filesystem';
|
||||
import Notifications from './notifications';
|
||||
|
||||
|
@ -91,18 +92,23 @@
|
|||
this.loggingIn = true;
|
||||
try {
|
||||
const data = <{ticket?: string, error: string, characters: string[], default_character: string}>
|
||||
(await Axios.post('https://www.f-list.net/json/getApiTicket.php',
|
||||
qs.stringify({account: this.settings!.account, password: this.settings!.password, no_friends: true, no_bookmarks: true})
|
||||
)).data;
|
||||
(await Axios.post('https://www.f-list.net/json/getApiTicket.php', qs.stringify(
|
||||
{account: this.settings!.account, password: this.settings!.password, no_friends: true, no_bookmarks: true})
|
||||
)).data;
|
||||
if(data.error !== '') {
|
||||
this.error = data.error;
|
||||
return;
|
||||
}
|
||||
if(this.saveLogin)
|
||||
await setGeneralSettings(this.settings!);
|
||||
const connection = new Connection(this.settings!.host, this.settings!.account, this.getTicket.bind(this));
|
||||
connection.onEvent('connected', () => Raven.setUserContext({username: core.connection.character}));
|
||||
connection.onEvent('closed', () => Raven.setUserContext());
|
||||
Socket.host = this.settings!.host;
|
||||
const connection = new Connection(Socket, this.settings!.account, this.getTicket.bind(this));
|
||||
connection.onEvent('connected', () => {
|
||||
Raven.setUserContext({username: core.connection.character});
|
||||
});
|
||||
connection.onEvent('closed', () => {
|
||||
Raven.setUserContext();
|
||||
});
|
||||
initCore(connection, Logs, SettingsStore, Notifications);
|
||||
this.characters = data.characters.sort();
|
||||
this.defaultCharacter = data.default_character;
|
||||
|
|
|
@ -38,7 +38,7 @@ import {init as fsInit} from './filesystem';
|
|||
import Index from './Index.vue';
|
||||
|
||||
if(process.env.NODE_ENV === 'production') {
|
||||
Raven.config('https://af3e6032460e418cb794b1799e536f37@sentry.newtsin.space/2', {
|
||||
Raven.config('https://a9239b17b0a14f72ba85e8729b9d1612@sentry.f-list.net/2', {
|
||||
release: `android-${require('./package.json').version}`, //tslint:disable-line:no-require-imports no-unsafe-any
|
||||
dataCallback: (data: {culprit: string, exception: {values: {stacktrace: {frames: {filename: string}[]}}[]}}) => {
|
||||
data.culprit = `~${data.culprit.substr(data.culprit.lastIndexOf('/'))}`;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "fchat",
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.0",
|
||||
"author": "The F-List Team",
|
||||
"description": "F-List.net Chat Client",
|
||||
"main": "main.js",
|
||||
|
|
|
@ -67,7 +67,10 @@ module.exports = function(env) {
|
|||
}));
|
||||
if(dist) {
|
||||
config.devtool = 'source-map';
|
||||
config.plugins.push(new UglifyPlugin({sourceMap: true}));
|
||||
config.plugins.push(
|
||||
new UglifyPlugin({sourceMap: true}),
|
||||
new webpack.LoaderOptionsPlugin({minimize: true})
|
||||
);
|
||||
}
|
||||
return config;
|
||||
};
|
|
@ -88,10 +88,12 @@
|
|||
{label: l('action.open'), click: () => mainWindow!.show()},
|
||||
{
|
||||
label: l('action.quit'),
|
||||
role: 'quit',
|
||||
click: () => {
|
||||
isClosing = true;
|
||||
mainWindow!.close();
|
||||
mainWindow = undefined;
|
||||
electron.remote.app.quit();
|
||||
}
|
||||
}
|
||||
];
|
||||
|
@ -100,7 +102,7 @@
|
|||
let isClosing = false;
|
||||
let mainWindow: Electron.BrowserWindow | undefined = electron.remote.getCurrentWindow(); //TODO
|
||||
//tslint:disable-next-line:no-require-imports
|
||||
const tray = new electron.remote.Tray(path.join(__dirname, <string>require('./build/icon.png')));
|
||||
const tray = new electron.remote.Tray(path.join(__dirname, <string>require('./build/tray.png')));
|
||||
tray.setToolTip(l('title'));
|
||||
tray.on('click', (_) => mainWindow!.show());
|
||||
tray.setContextMenu(trayMenu);
|
||||
|
@ -199,13 +201,7 @@
|
|||
},
|
||||
{type: 'separator'},
|
||||
{role: 'minimize'},
|
||||
{
|
||||
label: l('action.quit'),
|
||||
click(): void {
|
||||
isClosing = true;
|
||||
mainWindow!.close();
|
||||
}
|
||||
}
|
||||
{role: 'quit'}
|
||||
];
|
||||
electron.remote.Menu.setApplicationMenu(electron.remote.Menu.buildFromTemplate(appMenu));
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "fchat",
|
||||
"version": "0.1.29",
|
||||
"version": "0.2.1",
|
||||
"author": "The F-List Team",
|
||||
"description": "F-List.net Chat Client",
|
||||
"main": "main.js",
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
|
@ -34,11 +34,13 @@ import 'bootstrap/js/modal.js';
|
|||
import * as electron from 'electron';
|
||||
import * as Raven from 'raven-js';
|
||||
import Vue from 'vue';
|
||||
import {getKey} from '../chat/common';
|
||||
import l from '../chat/localize';
|
||||
import VueRaven from '../chat/vue-raven';
|
||||
import Index from './Index.vue';
|
||||
|
||||
if(process.env.NODE_ENV === 'production') {
|
||||
Raven.config('https://af3e6032460e418cb794b1799e536f37@sentry.newtsin.space/2', {
|
||||
Raven.config('https://a9239b17b0a14f72ba85e8729b9d1612@sentry.f-list.net/2', {
|
||||
release: electron.remote.app.getVersion(),
|
||||
dataCallback(data: {culprit: string, exception: {values: {stacktrace: {frames: {filename: string}[]}}[]}}): void {
|
||||
data.culprit = `~${data.culprit.substr(data.culprit.lastIndexOf('/'))}`;
|
||||
|
@ -52,6 +54,15 @@ if(process.env.NODE_ENV === 'production') {
|
|||
(<Window & {onunhandledrejection(e: PromiseRejectionEvent): void}>window).onunhandledrejection = (e: PromiseRejectionEvent) => {
|
||||
Raven.captureException(<Error>e.reason);
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', (e: KeyboardEvent) => {
|
||||
if(e.ctrlKey && e.shiftKey && getKey(e) === 'I')
|
||||
electron.remote.getCurrentWebContents().toggleDevTools();
|
||||
});
|
||||
electron.remote.getCurrentWebContents().on('devtools-opened', () => {
|
||||
console.log(`%c${l('consoleWarning.head')}`, 'background: red; color: yellow; font-size: 30pt');
|
||||
console.log(`%c${l('consoleWarning.body')}`, 'font-size: 16pt; color:red');
|
||||
});
|
||||
}
|
||||
|
||||
//tslint:disable-next-line:no-unused-expression
|
||||
|
@ -59,9 +70,4 @@ new Index({
|
|||
el: '#app'
|
||||
});
|
||||
|
||||
electron.ipcRenderer.on('focus', (_: Event, message: boolean) => message ? window.focus() : window.blur());
|
||||
|
||||
document.addEventListener('keydown', (e: KeyboardEvent) => {
|
||||
if(e.which === 123)
|
||||
electron.remote.getCurrentWebContents().toggleDevTools();
|
||||
});
|
||||
electron.ipcRenderer.on('focus', (_: Event, message: boolean) => message ? window.focus() : window.blur());
|
|
@ -8,7 +8,9 @@ export function mkdir(dir: string): void {
|
|||
if(!(e instanceof Error)) throw e;
|
||||
switch((<Error & {code: string}>e).code) {
|
||||
case 'ENOENT':
|
||||
mkdir(path.dirname(dir));
|
||||
const dirname = path.dirname(dir);
|
||||
if(dirname === dir) throw e;
|
||||
mkdir(dirname);
|
||||
mkdir(dir);
|
||||
break;
|
||||
default:
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
},
|
||||
"publish": {
|
||||
"provider": "generic",
|
||||
"url": "https://toys.in.newtsin.space/chat-updater",
|
||||
"url": "https://client.f-list.net/",
|
||||
"channel": "latest"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -85,9 +85,8 @@ module.exports = function(env) {
|
|||
config.devtool = 'source-map';
|
||||
config.plugins.push(
|
||||
new UglifyPlugin({sourceMap: true}),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.NODE_ENV': JSON.stringify('production')
|
||||
})
|
||||
new webpack.DefinePlugin({'process.env.NODE_ENV': JSON.stringify('production')}),
|
||||
new webpack.LoaderOptionsPlugin({minimize: true})
|
||||
);
|
||||
} else {
|
||||
//config.devtool = 'cheap-module-eval-source-map';
|
||||
|
|
|
@ -23,8 +23,8 @@ function mapToScreen(state: SavedWindowState): SavedWindowState {
|
|||
x /= primaryDisplay.scaleFactor;
|
||||
y /= primaryDisplay.scaleFactor;
|
||||
}
|
||||
state.x = x > 0 ? x : undefined;
|
||||
state.y = y > 0 ? y : undefined;
|
||||
state.x = x !== 0 ? x : undefined;
|
||||
state.y = y !== 0 ? y : undefined;
|
||||
return state;
|
||||
}
|
||||
|
||||
|
|
|
@ -72,8 +72,8 @@ class State implements Interfaces.State {
|
|||
officialChannels: {readonly [key: string]: ListItem | undefined} = {};
|
||||
openRooms: {readonly [key: string]: ListItem | undefined} = {};
|
||||
joinedChannels: Channel[] = [];
|
||||
joinedMap: {[key: string]: Channel | undefined} = {};
|
||||
handlers: Interfaces.EventHandler[] = [];
|
||||
joinedKeys: {[key: string]: number | undefined} = {};
|
||||
|
||||
constructor(private connection: Connection) {
|
||||
}
|
||||
|
@ -86,18 +86,6 @@ class State implements Interfaces.State {
|
|||
this.connection.send('LCH', {channel});
|
||||
}
|
||||
|
||||
addChannel(channel: Channel): void {
|
||||
this.joinedKeys[channel.id] = this.joinedChannels.length;
|
||||
this.joinedChannels.push(channel);
|
||||
for(const handler of this.handlers) handler('join', channel);
|
||||
}
|
||||
|
||||
removeChannel(channel: Channel): void {
|
||||
this.joinedChannels.splice(this.joinedKeys[channel.id]!, 1);
|
||||
delete this.joinedKeys[channel.id];
|
||||
for(const handler of this.handlers) handler('leave', channel);
|
||||
}
|
||||
|
||||
getChannelItem(id: string): ListItem | undefined {
|
||||
id = id.toLowerCase();
|
||||
return (id.substr(0, 4) === 'adh-' ? this.openRooms : this.officialChannels)[id];
|
||||
|
@ -108,8 +96,7 @@ class State implements Interfaces.State {
|
|||
}
|
||||
|
||||
getChannel(id: string): Channel | undefined {
|
||||
const key = this.joinedKeys[id.toLowerCase()];
|
||||
return key !== undefined ? this.joinedChannels[key] : undefined;
|
||||
return this.joinedMap[id.toLowerCase()];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -120,7 +107,7 @@ export default function(this: void, connection: Connection, characters: Characte
|
|||
let getChannelTimer: NodeJS.Timer | undefined;
|
||||
connection.onEvent('connecting', () => {
|
||||
state.joinedChannels = [];
|
||||
state.joinedKeys = {};
|
||||
state.joinedMap = {};
|
||||
});
|
||||
connection.onEvent('connected', (isReconnect) => {
|
||||
if(isReconnect) queuedJoin(Object.keys(state.joinedChannels));
|
||||
|
@ -132,13 +119,16 @@ export default function(this: void, connection: Connection, characters: Characte
|
|||
if(getChannelTimer !== undefined) clearInterval(getChannelTimer);
|
||||
getChannelTimer = setInterval(getChannels, 60000);
|
||||
});
|
||||
connection.onEvent('closed', () => {
|
||||
if(getChannelTimer !== undefined) clearInterval(getChannelTimer);
|
||||
});
|
||||
|
||||
connection.onMessage('CHA', (data) => {
|
||||
const channels: {[key: string]: ListItem} = {};
|
||||
for(const channel of data.channels) {
|
||||
const id = channel.name.toLowerCase();
|
||||
const item = new ListItem(id, channel.name, channel.characters);
|
||||
if(state.joinedKeys[id] !== undefined) item.isJoined = true;
|
||||
if(state.joinedMap[id] !== undefined) item.isJoined = true;
|
||||
channels[id] = item;
|
||||
}
|
||||
state.officialChannels = channels;
|
||||
|
@ -148,7 +138,7 @@ export default function(this: void, connection: Connection, characters: Characte
|
|||
for(const channel of data.channels) {
|
||||
const id = channel.name.toLowerCase();
|
||||
const item = new ListItem(id, decodeHTML(channel.title), channel.characters);
|
||||
if(state.joinedKeys[id] !== undefined) item.isJoined = true;
|
||||
if(state.joinedMap[id] !== undefined) item.isJoined = true;
|
||||
channels[id] = item;
|
||||
}
|
||||
state.openRooms = channels;
|
||||
|
@ -156,7 +146,9 @@ export default function(this: void, connection: Connection, characters: Characte
|
|||
connection.onMessage('JCH', (data) => {
|
||||
const item = state.getChannelItem(data.channel);
|
||||
if(data.character.identity === connection.character) {
|
||||
state.addChannel(new Channel(data.channel.toLowerCase(), decodeHTML(data.title)));
|
||||
const id = data.channel.toLowerCase();
|
||||
const channel = state.joinedMap[id] = new Channel(id, decodeHTML(data.title));
|
||||
state.joinedChannels.push(channel);
|
||||
if(item !== undefined) item.isJoined = true;
|
||||
} else {
|
||||
const channel = state.getChannel(data.channel)!;
|
||||
|
@ -179,6 +171,7 @@ export default function(this: void, connection: Connection, characters: Characte
|
|||
channel.sortedMembers = sorted;
|
||||
const item = state.getChannelItem(data.channel);
|
||||
if(item !== undefined) item.memberCount = data.users.length;
|
||||
for(const handler of state.handlers) handler('join', channel);
|
||||
});
|
||||
connection.onMessage('CDS', (data) => state.getChannel(data.channel)!.description = decodeHTML(data.description));
|
||||
connection.onMessage('LCH', (data) => {
|
||||
|
@ -186,7 +179,9 @@ export default function(this: void, connection: Connection, characters: Characte
|
|||
if(channel === undefined) return;
|
||||
const item = state.getChannelItem(data.channel);
|
||||
if(data.character === connection.character) {
|
||||
state.removeChannel(channel);
|
||||
state.joinedChannels.splice(state.joinedChannels.indexOf(channel), 1);
|
||||
delete state.joinedMap[channel.id];
|
||||
for(const handler of state.handlers) handler('leave', channel);
|
||||
if(item !== undefined) item.isJoined = false;
|
||||
} else {
|
||||
channel.removeMember(data.character);
|
||||
|
@ -230,13 +225,13 @@ export default function(this: void, connection: Connection, characters: Characte
|
|||
});
|
||||
connection.onMessage('RMO', (data) => state.getChannel(data.channel)!.mode = data.mode);
|
||||
connection.onMessage('FLN', (data) => {
|
||||
for(const key in state.joinedKeys)
|
||||
state.getChannel(key)!.removeMember(data.character);
|
||||
for(const key in state.joinedMap)
|
||||
state.joinedMap[key]!.removeMember(data.character);
|
||||
});
|
||||
const globalHandler = (data: Connection.ServerCommands['AOP'] | Connection.ServerCommands['DOP']) => {
|
||||
//tslint:disable-next-line:forin
|
||||
for(const key in state.joinedKeys) {
|
||||
const channel = state.getChannel(key)!;
|
||||
for(const key in state.joinedMap) {
|
||||
const channel = state.joinedMap[key]!;
|
||||
const member = channel.members[data.character];
|
||||
if(member !== undefined) channel.reSortMember(member);
|
||||
}
|
||||
|
|
|
@ -77,7 +77,11 @@ export default class Connection implements Interfaces.Connection {
|
|||
data.ticket = this.ticket = await this.ticketProvider();
|
||||
res = <{error: string}>(await queryApi(endpoint, data)).data;
|
||||
}
|
||||
if(res.error !== '') throw new Error(res.error);
|
||||
if(res.error !== '') {
|
||||
const error = new Error(res.error);
|
||||
(<Error & {request: true}>error).request = true;
|
||||
throw error;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@ export namespace Connection {
|
|||
export type ServerCommands = {
|
||||
ADL: {ops: ReadonlyArray<string>},
|
||||
AOP: {character: string},
|
||||
BRO: {message: string, character: string},
|
||||
BRO: {message: string, character?: string},
|
||||
CBU: {operator: string, channel: string, character: string},
|
||||
CDS: {channel: string, description: string},
|
||||
CHA: {channels: ReadonlyArray<{name: string, mode: Channel.Mode, characters: number}>},
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
@import "../variables/default.less";
|
||||
|
||||
.message-own {
|
||||
background-color: @gray-lighter;
|
||||
.nav-tabs > li > a:hover {
|
||||
background-color: @gray-darker;
|
||||
}
|
||||
|
||||
.whiteText {
|
||||
text-shadow: 1px 1px @gray;
|
||||
.modal .nav-tabs > li.active > a {
|
||||
background-color: @gray-dark;
|
||||
}
|
||||
|
||||
.message-own {
|
||||
background-color: @gray-darker;
|
||||
}
|
||||
|
||||
// Apply variables to theme.
|
||||
|
@ -13,7 +17,7 @@
|
|||
|
||||
* {
|
||||
&::-webkit-scrollbar-track {
|
||||
box-shadow: inset 0 0 8px @gray;
|
||||
box-shadow: inset 0 0 8px @panel-default-border;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
|
@ -24,13 +28,13 @@
|
|||
&::-webkit-scrollbar-thumb {
|
||||
border-radius: 10px;
|
||||
box-shadow: inset 0 0 4px rgba(0, 0, 0, 0.8);
|
||||
background-color: @gray-lighter;
|
||||
background-color: @gray-dark;
|
||||
&:hover {
|
||||
background-color: @gray-light;
|
||||
background-color: @gray;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: @gray;
|
||||
background-color: @gray-light;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
* {
|
||||
&::-webkit-scrollbar-track {
|
||||
box-shadow: inset 0 0 8px @gray-light;
|
||||
box-shadow: inset 0 0 8px @gray;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,23 +2,22 @@
|
|||
@import "~bootstrap/less/variables.less";
|
||||
@import "../../flist_variables.less";
|
||||
|
||||
@gray-base: #080810;
|
||||
@gray-darker: lighten(@gray-base, 15%);
|
||||
@gray-dark: lighten(@gray-base, 25%);
|
||||
@gray-base: #000000;
|
||||
@gray-darker: lighten(@gray-base, 4%);
|
||||
@gray-dark: lighten(@gray-base, 20%);
|
||||
@gray: lighten(@gray-base, 55%);
|
||||
@gray-light: lighten(@gray-base, 76.7%);
|
||||
@gray-lighter: lighten(@gray-base, 93.5%);
|
||||
@gray-light: lighten(@gray-base, 85%);
|
||||
@gray-lighter: lighten(@gray-base, 95%);
|
||||
|
||||
// @body-bg: #262626;
|
||||
@body-bg: darken(@text-background-color-disabled, 3%);
|
||||
@body-bg: @gray-darker;
|
||||
@text-color: @gray-lighter;
|
||||
@text-color-disabled: @gray;
|
||||
@link-color: darken(@gray-lighter, 15%);
|
||||
|
||||
@brand-warning: #c26c00;
|
||||
@brand-danger: #930300;
|
||||
@brand-success: #009900;
|
||||
@brand-info: #0447af;
|
||||
@brand-warning: #a50;
|
||||
@brand-danger: #800;
|
||||
@brand-success: #080;
|
||||
@brand-info: #13b;
|
||||
@brand-primary: @brand-info;
|
||||
|
||||
@state-info-bg: darken(@brand-info, 15%);
|
||||
|
|
|
@ -2,7 +2,113 @@
|
|||
@import "~bootstrap/less/variables.less";
|
||||
@import "../../flist_variables.less";
|
||||
|
||||
@gray-base: #080810;
|
||||
@gray-darker: lighten(@gray-base, 15%);
|
||||
@gray-dark: lighten(@gray-base, 25%);
|
||||
@gray: lighten(@gray-base, 55%);
|
||||
@gray-light: lighten(@gray-base, 76.7%);
|
||||
@gray-lighter: lighten(@gray-base, 93.5%);
|
||||
|
||||
// Update variables here.
|
||||
// @body-bg: #00ff00;
|
||||
@hr-border: @text-color;
|
||||
// @body-bg: #262626;
|
||||
@body-bg: darken(@text-background-color-disabled, 3%);
|
||||
@text-color: @gray-lighter;
|
||||
@text-color-disabled: @gray;
|
||||
@link-color: darken(@gray-lighter, 15%);
|
||||
|
||||
@brand-warning: #c26c00;
|
||||
@brand-danger: #930300;
|
||||
@brand-success: #009900;
|
||||
@brand-info: #0447af;
|
||||
@brand-primary: @brand-info;
|
||||
|
||||
@state-info-bg: darken(@brand-info, 15%);
|
||||
@state-info-text: lighten(@brand-info, 30%);
|
||||
@state-success-bg: darken(@brand-success, 15%);
|
||||
@state-success-text: lighten(@brand-success, 30%);
|
||||
@state-warning-bg: darken(@brand-warning, 15%);
|
||||
@state-warning-text: lighten(@brand-warning, 30%);
|
||||
@state-danger-bg: darken(@brand-danger, 15%);
|
||||
@state-danger-text: lighten(@brand-danger, 30%);
|
||||
|
||||
@text-background-color: @gray-dark;
|
||||
@text-background-color-disabled: @gray-darker;
|
||||
@border-color: lighten(spin(@text-background-color, -10), 15%);
|
||||
@border-color-active: lighten(spin(@text-background-color, -10), 25%);
|
||||
@border-color-disabled: darken(spin(@text-background-color-disabled, -10), 8%);
|
||||
|
||||
@hover-bg: lighten(@gray-dark, 15%);
|
||||
|
||||
|
||||
@hr-border: @text-color;
|
||||
|
||||
@panel-bg: @text-background-color;
|
||||
@panel-default-heading-bg: @gray;
|
||||
@panel-default-border: @border-color;
|
||||
|
||||
@input-color: @gray-light;
|
||||
@input-bg: @text-background-color;
|
||||
@input-bg-disabled: @text-background-color-disabled;
|
||||
@input-border: @border-color;
|
||||
@input-border-focus: @gray;
|
||||
|
||||
@dropdown-bg: @text-background-color;
|
||||
@dropdown-color: @text-color;
|
||||
@dropdown-link-color: @link-color;
|
||||
@dropdown-link-hover-color: @gray-dark;
|
||||
@dropdown-link-hover-bg: @gray-light;
|
||||
|
||||
@navbar-default-bg: @text-background-color;
|
||||
@navbar-default-color: @text-color;
|
||||
@navbar-default-link-color: @link-color;
|
||||
@navbar-default-link-hover-color: @link-hover-color;
|
||||
|
||||
@nav-link-hover-bg: @gray-light;
|
||||
@nav-link-hover-color: @gray-dark;
|
||||
|
||||
@nav-tabs-border-color: @border-color;
|
||||
@nav-tabs-link-hover-border-color: @border-color;
|
||||
@nav-tabs-active-link-hover-bg: @body-bg;
|
||||
@nav-tabs-active-link-hover-color: @text-color;
|
||||
@nav-tabs-active-link-hover-border-color: @border-color;
|
||||
|
||||
@component-active-color: @gray-dark;
|
||||
@component-active-bg: @gray-light;
|
||||
|
||||
@list-group-bg: @gray-darker;
|
||||
@list-group-border: @gray-dark;
|
||||
@list-group-link-color: @text-color;
|
||||
@list-group-hover-bg: @gray-dark;
|
||||
|
||||
@btn-default-bg: @text-background-color;
|
||||
@btn-default-color: @text-color;
|
||||
@btn-default-border: @border-color;
|
||||
|
||||
@pagination-bg: @text-background-color;
|
||||
@pagination-color: @text-color;
|
||||
@pagination-border: @border-color;
|
||||
@pagination-disabled-bg: @text-background-color-disabled;
|
||||
@pagination-disabled-color: @text-color-disabled;
|
||||
@pagination-disabled-border: @border-color-disabled;
|
||||
@pagination-active-bg: @gray;
|
||||
@pagination-active-color: @gray-lighter;
|
||||
@pagination-active-border: @border-color-active;
|
||||
|
||||
@modal-content-bg: @text-background-color;
|
||||
@modal-footer-border-color: lighten(spin(@modal-content-bg, -10), 15%);
|
||||
@modal-header-border-color: @modal-footer-border-color;
|
||||
|
||||
@badge-color: @gray-darker;
|
||||
|
||||
@close-color: saturate(@text-color, 10%);
|
||||
@close-text-shadow: 0 1px 0 @text-color;
|
||||
|
||||
@well-bg: @text-background-color;
|
||||
@well-border: @border-color;
|
||||
|
||||
@blockquote-border-color: @border-color-active;
|
||||
|
||||
@collapse-border: desaturate(@well-border, 20%);
|
||||
@collapse-header-bg: desaturate(@well-bg, 20%);
|
||||
|
||||
@white-color: @text-color;
|
||||
@purple-color: @gray-light;
|
|
@ -4,4 +4,5 @@
|
|||
|
||||
// Update variables here.
|
||||
// @body-bg: #00ff00;
|
||||
@hr-border: @text-color;
|
||||
@hr-border: @text-color;
|
||||
@body-bg: #fafafa;
|
Loading…
Reference in New Issue