Stable release!

This commit is contained in:
MayaWolf 2018-04-11 21:17:58 +02:00
parent 79d1ee4f48
commit 959cac855a
28 changed files with 163 additions and 95 deletions

View File

@ -21,7 +21,7 @@
<textarea ref="input" v-model="text" @input="onInput" v-show="!preview" :maxlength="maxlength"
:class="finalClasses" @keyup="onKeyUp" :disabled="disabled" @paste="onPaste" style="border-top-left-radius:0"
:placeholder="placeholder" @keypress="$emit('keypress', $event)" @keydown="onKeyDown"></textarea>
<div ref="sizer"></div>
<textarea ref="sizer"></textarea>
<div class="bbcode-preview" v-show="preview">
<div class="bbcode-preview-warnings">
<div class="alert alert-danger" v-show="previewWarnings.length">
@ -66,7 +66,7 @@
previewResult = '';
text = this.value !== undefined ? this.value : '';
element!: HTMLTextAreaElement;
sizer!: HTMLElement;
sizer!: HTMLTextAreaElement;
maxHeight!: number;
minHeight!: number;
showToolbar = false;
@ -99,7 +99,7 @@
this.undoStack.unshift(this.text);
}
}, 500);
this.sizer = <HTMLElement>this.$refs['sizer'];
this.sizer = <HTMLTextAreaElement>this.$refs['sizer'];
this.sizer.style.cssText = styles.cssText;
this.sizer.style.height = '0';
this.sizer.style.overflow = 'hidden';
@ -240,7 +240,7 @@
this.sizer.style.fontSize = this.element.style.fontSize;
this.sizer.style.lineHeight = this.element.style.lineHeight;
this.sizer.style.width = `${this.element.offsetWidth}px`;
this.sizer.textContent = this.element.value;
this.sizer.value = this.element.value;
this.element.style.height = `${Math.max(Math.min(this.sizer.scrollHeight, this.maxHeight), this.minHeight)}px`;
this.sizer.style.width = '0';
}

View File

@ -146,8 +146,10 @@
document.title = (hasNew ? '💬 ' : '') + l(core.connection.isOpen ? 'title.connected' : 'title', core.connection.character);
});
core.connection.onError((e) => {
this.error = errorToString(e);
this.connecting = false;
if((<Error & {request?: object}>e).request !== undefined) {//catch axios network errors
this.error = l('login.connectError', errorToString(e));
this.connecting = false;
} else throw e;
});
}
@ -158,12 +160,7 @@
connect(): void {
this.connecting = true;
core.connection.connect(this.selectedCharacter).catch((e: Error) => {
if((<Error & {request?: object}>e).request !== undefined) {//catch axios network errors
this.error = l('login.connectError', e.message);
this.connecting = false;
} else throw e;
});
core.connection.connect(this.selectedCharacter);
}
}
</script>

View File

@ -158,17 +158,17 @@
clearTimeout(idleTimer);
idleTimer = undefined;
}
window.setTimeout(() => {
if(idleStatus !== undefined) {
core.connection.send('STA', idleStatus);
idleStatus = undefined;
}
}, Math.max(lastUpdate + 5 /*core.connection.vars.sta_flood*/ * 1000 + 1000 - Date.now(), 0));
if(idleStatus !== undefined) {
const status = idleStatus;
window.setTimeout(() => core.connection.send('STA', status),
Math.max(lastUpdate + 5 /*core.connection.vars.sta_flood*/ * 1000 + 1000 - Date.now(), 0));
idleStatus = undefined;
}
});
window.addEventListener('blur', () => {
core.notifications.isInBackground = true;
if(idleTimer !== undefined) clearTimeout(idleTimer);
if(core.state.settings.idleTimer !== 0)
if(core.state.settings.idleTimer > 0)
idleTimer = window.setTimeout(() => {
lastUpdate = Date.now();
idleStatus = {status: ownCharacter.status, statusmsg: ownCharacter.statusText};

View File

@ -15,7 +15,12 @@
<div v-if="command.permission"><i>{{command.permission}}</i></div>
</div>
</div>
<input class="form-control" v-model="filter" :placeholder="l('filter')"/>
<div class="input-group" style="padding:10px 0;flex-shrink:0">
<div class="input-group-prepend">
<div class="input-group-text"><span class="fas fa-search"></span></div>
</div>
<input class="form-control" v-model="filter" :placeholder="l('filter')"/>
</div>
</modal>
</template>

View File

@ -48,7 +48,7 @@
</ul>
</div>
<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">
:style="'display:' + (descriptionExpanded ? 'block' : 'none')" class="bg-solid-text border-bottom">
<bbcode :text="conversation.channel.description"></bbcode>
</div>
</div>
@ -64,7 +64,7 @@
</div>
<input v-model="searchInput" @keydown.esc="showSearch = false; searchInput = ''" @keypress="lastSearchInput = Date.now()"
:placeholder="l('chat.search')" ref="searchField" class="form-control"/>
<a class="btn btn-sm btn-light" style="position:absolute;right:5px;top:50%;transform:translateY(-50%);line-height:0"
<a class="btn btn-sm btn-light" style="position:absolute;right:5px;top:50%;transform:translateY(-50%);line-height:0;z-index:10"
@click="showSearch = false"><i class="fas fa-times"></i></a>
</div>
<div class="border-top messages" :class="'messages-' + conversation.mode" style="flex:1;overflow:auto;margin-top:2px"
@ -258,6 +258,7 @@
async onKeyDown(e: KeyboardEvent): Promise<void> {
const editor = <Editor>this.$refs['textBox'];
if(getKey(e) === Keys.Tab) {
if(e.shiftKey || e.altKey || e.ctrlKey || e.metaKey) return;
e.preventDefault();
if(this.conversation.enteredText.length === 0 || this.isConsoleTab) return;
if(this.tabOptions === undefined) {

View File

@ -1,7 +1,12 @@
<template>
<modal :buttons="false" ref="dialog" id="logs-dialog" :action="l('logs.title')"
dialogClass="modal-lg w-100 modal-dialog-centered" @open="onOpen" @close="onClose">
<div class="form-group row" style="flex-shrink:0">
<modal :buttons="false" ref="dialog" @open="onOpen" @close="onClose" style="width:98%" dialogClass="logs-dialog">
<template slot="title">
{{l('logs.title')}}
<div class="logs-fab btn btn-secondary" slot="title" @click="showFilters = !showFilters">
<span class="fas" :class="'fa-chevron-' + (showFilters ? 'up' : 'down')"></span>
</div>
</template>
<div class="form-group row" style="flex-shrink:0" v-show="showFilters">
<label for="character" class="col-sm-2 col-form-label">{{l('logs.character')}}</label>
<div class="col-sm-10">
<select class="form-control" v-model="selectedCharacter" id="character" @change="loadCharacter">
@ -10,7 +15,7 @@
</select>
</div>
</div>
<div class="form-group row" style="flex-shrink:0">
<div class="form-group row" style="flex-shrink:0" v-show="showFilters">
<label class="col-sm-2 col-form-label">{{l('logs.conversation')}}</label>
<div class="col-sm-10">
<filterable-select v-model="selectedConversation" :options="conversations" :filterFunc="filterConversation"
@ -20,7 +25,7 @@
</filterable-select>
</div>
</div>
<div class="form-group row" style="flex-shrink:0">
<div class="form-group row" style="flex-shrink:0" v-show="showFilters">
<label for="date" class="col-sm-2 col-form-label">{{l('logs.date')}}</label>
<div class="col-sm-8 col-10">
<select class="form-control" v-model="selectedDate" id="date" @change="loadMessages">
@ -33,7 +38,7 @@
class="fa fa-download"></span></button>
</div>
</div>
<div class="messages-both" style="overflow: auto" ref="messages">
<div class="messages-both" style="overflow: auto" ref="messages" tabindex="-1">
<message-view v-for="message in filteredMessages" :message="message" :key="message.id"></message-view>
</div>
<div class="input-group" style="flex-shrink:0">
@ -85,6 +90,7 @@
keyDownListener?: (e: KeyboardEvent) => void;
characters: ReadonlyArray<string> = [];
selectedCharacter = core.connection.character;
showFilters = true;
get filteredMessages(): ReadonlyArray<Conversation.Message> {
if(this.filter.length === 0) return this.messages;
@ -180,7 +186,12 @@
</script>
<style>
#logs-dialog .modal-body {
.logs-dialog {
max-width: 98% !important;
width: 98% !important;
}
.logs-dialog .modal-body {
display: flex;
flex-direction: column;
}

View File

@ -2,7 +2,7 @@
<modal :action="l('chat.setStatus')" @submit="setStatus" @close="reset" dialogClass="w-100 modal-lg">
<div class="form-group" id="statusSelector">
<label class="control-label">{{l('chat.setStatus.status')}}</label>
<dropdown class="dropdown form-control" style="padding:0">
<dropdown>
<span slot="title"><span class="fa fa-fw" :class="getStatusIcon(status)"></span>{{l('status.' + status)}}</span>
<a href="#" class="dropdown-item" v-for="item in statuses" @click.prevent="status = item">
<span class="fa fa-fw" :class="getStatusIcon(item)"></span>{{l('status.' + item)}}

View File

@ -4,11 +4,11 @@
<div class="users" style="padding-left:10px" v-show="tab == 0">
<h4>{{l('users.friends')}}</h4>
<div v-for="character in friends" :key="character.name">
<user :character="character" :showStatus="true"></user>
<user :character="character" :showStatus="true" :bookmark="false"></user>
</div>
<h4>{{l('users.bookmarks')}}</h4>
<div v-for="character in bookmarks" :key="character.name">
<user :character="character" :showStatus="true"></user>
<user :character="character" :showStatus="true" :bookmark="false"></user>
</div>
</div>
<div v-if="channel" style="padding-left:5px;flex:1;display:flex;flex-direction:column" v-show="tab == 1">

View File

@ -1,5 +1,4 @@
import {WebSocketConnection} from '../fchat';
import l from './localize';
export default class Socket implements WebSocketConnection {
static host = 'wss://chat.f-list.net:9799';
@ -31,7 +30,7 @@ export default class Socket implements WebSocketConnection {
onError(handler: (error: Error) => void): void {
this.errorHandler = handler;
this.socket.addEventListener('error', () => handler(new Error(l('login.connectError'))));
this.socket.addEventListener('error', () => handler(new Error()));
}
send(message: string): void {

View File

@ -6,12 +6,13 @@ const codecs: {[key: string]: string} = {mpeg: 'mp3', wav: 'wav', ogg: 'ogg'};
export default class Notifications implements Interface {
isInBackground = false;
protected shouldNotify(conversation: Conversation): boolean {
return core.characters.ownCharacter.status !== 'dnd' && (this.isInBackground ||
conversation !== core.conversations.selectedConversation || core.state.settings.alwaysNotify);
}
notify(conversation: Conversation, title: string, body: string, icon: string, sound: string): void {
if(core.characters.ownCharacter.status === 'dnd') return;
if(!this.isInBackground && conversation === core.conversations.selectedConversation) {
if(core.state.settings.alwaysNotify) this.playSound(sound);
return;
}
if(!this.shouldNotify(conversation)) return;
this.playSound(sound);
if(core.state.settings.notifications) {
/*tslint:disable-next-line:no-object-literal-type-assertion*///false positive

View File

@ -32,7 +32,7 @@ export function getStatusIcon(status: Character.Status): string {
const UserView = Vue.extend({
functional: true,
render(this: void | Vue, createElement: CreateElement, context?: RenderContext): VNode {
const props = <{character: Character, channel?: Channel, showStatus?: true}>(
const props = <{character: Character, channel?: Channel, showStatus?: true, bookmark?: false}>(
context !== undefined ? context.props : (<Vue>this).$options.propsData);
const character = props.character;
let rankIcon;
@ -46,7 +46,8 @@ const UserView = Vue.extend({
if(props.showStatus !== undefined || character.status === 'crown')
children.unshift(createElement('span', {staticClass: `fa-fw ${getStatusIcon(character.status)}`}));
const gender = character.gender !== undefined ? character.gender.toLowerCase() : 'none';
const isBookmark = core.connection.isOpen && core.state.settings.colorBookmarks && (character.isFriend || character.isBookmarked);
const isBookmark = props.bookmark !== false && core.connection.isOpen && core.state.settings.colorBookmarks &&
(character.isFriend || character.isBookmarked);
return createElement('span', {
attrs: {class: `user-view gender-${gender}${isBookmark ? ' user-bookmark' : ''}`},
domProps: {character, channel: props.channel, bbcodeTag: 'user'}

View File

@ -1,11 +1,11 @@
<template>
<div class="dropdown">
<a class="btn btn-secondary dropdown-toggle" aria-haspopup="true" :aria-expanded="isOpen" @click="isOpen = true"
<button class="form-control custom-select" aria-haspopup="true" :aria-expanded="isOpen" @click="isOpen = true"
@blur="isOpen = false" style="width:100%;text-align:left;display:flex;align-items:center" role="button" tabindex="-1">
<div style="flex:1">
<slot name="title" style="flex:1"></slot>
</div>
</a>
</button>
<div class="dropdown-menu" :style="open ? 'display:block' : ''" @mousedown.stop.prevent @click="isOpen = false"
ref="menu">
<slot></slot>

View File

@ -60,7 +60,10 @@
const index = selected.indexOf(item);
if(index === -1) selected.push(item);
else selected.splice(index, 1);
} else this.selected = item;
} else {
this.keepOpen = false;
this.selected = item;
}
this.$emit('input', this.selected);
}

View File

@ -176,6 +176,7 @@
alert(l('login.alreadyLoggedIn'));
return core.connection.close();
}
parent.send('connect', webContents.id, core.connection.character);
this.character = connection.character;
if((await core.settingsStore.get('settings')) === undefined &&
SlimcatImporter.canImportCharacter(core.connection.character)) {
@ -187,7 +188,6 @@
});
connection.onEvent('connected', () => {
core.watch(() => core.conversations.hasNew, (newValue) => parent.send('has-new', webContents.id, newValue));
parent.send('connect', webContents.id, core.connection.character);
Raven.setUserContext({username: core.connection.character});
});
connection.onEvent('closed', () => {

View File

@ -225,6 +225,7 @@
electron.ipcRenderer.send('has-new', this.tabs.reduce((cur, t) => cur || t.hasNew, false));
delete this.tabMap[tab.view.webContents.id];
if(this.tabs.length === 0) {
browserWindow.setBrowserView(null!);
if(process.env.NODE_ENV === 'production') browserWindow.close();
} else if(this.activeTab === tab) this.show(this.tabs[0]);
destroyTab(tab);

View File

@ -1,6 +1,6 @@
{
"name": "fchat",
"version": "0.2.19",
"version": "3.0.0",
"author": "The F-List Team",
"description": "F-List.net Chat Client",
"main": "main.js",

View File

@ -214,9 +214,10 @@ export class Logs implements Logging {
const entry = this.getIndex(character)[key];
if(entry === undefined) return [];
const dates = [];
const offset = new Date().getTimezoneOffset() * 60000;
for(const item in entry.index)
dates.push(new Date(parseInt(item, 10) * dayMs + offset));
for(const item in entry.index) {
const date = new Date(parseInt(item, 10) * dayMs);
dates.push(new Date(date.getTime() + date.getTimezoneOffset() * 60000));
}
return dates;
}

View File

@ -8,7 +8,7 @@ const browserWindow = remote.getCurrentWindow();
export default class Notifications extends BaseNotifications {
notify(conversation: Conversation, title: string, body: string, icon: string, sound: string): void {
if(!this.isInBackground && conversation === core.conversations.selectedConversation && !core.state.settings.alwaysNotify) return;
if(!this.shouldNotify(conversation)) return;
this.playSound(sound);
browserWindow.flashFrame(true);
if(core.state.settings.notifications) {

View File

@ -36,17 +36,26 @@ export default class Connection implements Interfaces.Connection {
try {
this.ticket = await this.ticketProvider();
} catch(e) {
(<Error & {request: true}>e).request = true;
throw e;
return this.invokeErrorHandlers(<Error>e, true);
}
try {
await this.invokeHandlers('connecting', this.isReconnect);
} catch(e) {
await this.invokeHandlers('closed', false);
return this.invokeErrorHandlers(<Error>e);
}
await this.invokeHandlers('connecting', this.isReconnect);
if(this.cleanClose) {
this.cleanClose = false;
await this.invokeHandlers('closed', false);
return;
}
const socket = this.socket = new this.socketProvider();
socket.onOpen(() => {
try {
this.socket = new this.socketProvider();
} catch(e) {
await this.invokeHandlers('closed', false);
return this.invokeErrorHandlers(<Error>e, true);
}
this.socket.onOpen(() => {
this.send('IDN', {
account: this.account,
character: this.character,
@ -56,39 +65,21 @@ export default class Connection implements Interfaces.Connection {
ticket: this.ticket
});
});
socket.onMessage(async(msg: string) => {
this.socket.onMessage(async(msg: string) => {
const type = <keyof Interfaces.ServerCommands>msg.substr(0, 3);
const data = msg.length > 6 ? <object>JSON.parse(msg.substr(4)) : undefined;
return this.handleMessage(type, data);
});
socket.onClose(async() => {
this.socket.onClose(async() => {
if(!this.cleanClose) this.reconnect();
this.socket = undefined;
await this.invokeHandlers('closed', !this.cleanClose);
});
socket.onError((error: Error) => {
for(const handler of this.errorHandlers) handler(error);
});
return new Promise<void>((resolve, reject) => {
const handler = () => {
resolve();
this.offEvent('connected', handler);
};
this.onEvent('connected', handler);
this.onError(reject);
});
this.socket.onError((error: Error) => this.invokeErrorHandlers(error, true));
}
private reconnect(): void {
this.reconnectTimer = setTimeout(async() => {
try {
await this.connect(this.character);
} catch(e) {
for(const handler of this.errorHandlers) handler(<Error>e);
await this.invokeHandlers('closed', true);
this.reconnect();
}
}, this.reconnectDelay);
this.reconnectTimer = setTimeout(async() => this.connect(this.character), this.reconnectDelay);
this.reconnectDelay = this.reconnectDelay >= 30000 ? 60000 : this.reconnectDelay >= 10000 ? 30000 : 10000;
}
@ -167,8 +158,7 @@ export default class Connection implements Interfaces.Connection {
break;
case 'ERR':
if(fatalErrors.indexOf(data.number) !== -1) {
const error = new Error(data.message);
for(const handler of this.errorHandlers) handler(error);
this.invokeErrorHandlers(new Error(data.message), true);
if(dieErrors.indexOf(data.number) !== -1) {
this.close();
this.character = '';
@ -198,4 +188,9 @@ export default class Connection implements Interfaces.Connection {
if(handlers === undefined) return;
for(const handler of handlers) await handler(isReconnect);
}
private invokeErrorHandlers(error: Error, request: boolean = false): void {
if(request) (<Error & {request: true}>error).request = true;
for(const handler of this.errorHandlers) handler(error);
}
}

View File

@ -135,7 +135,7 @@ export namespace Connection {
readonly character: string
readonly vars: Vars
readonly isOpen: boolean
connect(character: string): Promise<void>
connect(character: string): void
close(): void
onMessage<K extends keyof ServerCommands>(type: K, handler: CommandHandler<K>): void
offMessage<K extends keyof ServerCommands>(type: K, handler: CommandHandler<K>): void

View File

@ -115,7 +115,6 @@
let settings = await getGeneralSettings();
if(settings === undefined) settings = new GeneralSettings();
if(settings.version !== appVersion) {
alert('Your beta version of F-Chat 3.0 has been updated. If you are experiencing any issues after this update, please perform a full reinstall of the application. If the issue persists, please report it.');
settings.version = appVersion;
await setGeneralSettings(settings);
}

View File

@ -8,8 +8,8 @@ android {
applicationId "net.f_list.fchat"
minSdkVersion 19
targetSdkVersion 27
versionCode 12
versionName "0.1.5"
versionCode 13
versionName "3.0.0"
}
buildTypes {
release {

View File

@ -77,8 +77,10 @@ export class Logs implements Logging {
async getLogDates(character: string, key: string): Promise<ReadonlyArray<Date>> {
const entry = (await this.getIndex(character))[key];
if(entry === undefined) return [];
const offset = new Date().getTimezoneOffset() * 60000;
return entry.dates.map((x) => new Date(x * dayMs + offset));
return entry.dates.map((x) => {
const date = new Date(x * dayMs);
return new Date(date.getTime() + date.getTimezoneOffset() * 60000);
});
}
async getConversations(character: string): Promise<ReadonlyArray<{key: string, name: string}>> {

View File

@ -16,8 +16,7 @@ document.addEventListener('notification-clicked', (e: Event) => {
export default class Notifications extends BaseNotifications {
notify(conversation: Conversation, title: string, body: string, icon: string, sound: string): void {
if(!this.isInBackground && conversation === core.conversations.selectedConversation && !core.state.settings.alwaysNotify) return;
if(core.characters.ownCharacter.status === 'dnd') return;
if(!this.shouldNotify(conversation)) return;
NativeNotification.notify(core.state.settings.notifications && this.isInBackground, title, body, icon,
core.state.settings.playSound ? sound : null, conversation.key); //tslint:disable-line:no-null-keyword
}

View File

@ -1,6 +1,6 @@
{
"name": "net.f_list.fchat",
"version": "0.2.18",
"version": "3.0.0",
"displayName": "F-Chat",
"author": "The F-List Team",
"description": "F-List.net Chat Client",

View File

@ -268,4 +268,16 @@ $genders: (
@media (max-width: breakpoint-max(xs)) {
display: none;
}
}
.logs-fab {
position: absolute;
top: 47px;
z-index: 10;
padding: 12px;
left: 50%;
margin-left: -20px;
border-radius: 100%;
line-height: 0;
box-shadow: 0 1px 4px #000;
}

View File

@ -1,4 +1,4 @@
import {EventMessage, Message} from '../chat/common';
import {EventMessage, Message, Settings as SettingsImpl} from '../chat/common';
import core from '../chat/core';
import {Conversation, Logs as Logging, Settings} from '../chat/interfaces';
@ -30,7 +30,7 @@ async function iterate<S, T>(request: IDBRequest, map: (stored: S) => T, count:
const dayMs = 86400000;
const charactersKey = 'fchat.characters';
let hasComposite = true;
let getComposite: (conv: number, day: number) => string | number[] = (conv, day) => [conv, day];
let getComposite: (conv: number, day: number) => string | number[] = (conv, day) => [conv, day];
const decode = (str: string) => (str.charCodeAt(0) << 16) + str.charCodeAt(1);
try {
IDBKeyRange.only([]);
@ -133,10 +133,11 @@ export class Logs implements Logging {
async getLogDates(character: string, key: string): Promise<ReadonlyArray<Date>> {
const id = (await this.loadIndex(character))[key]!.id;
const trans = this.loadedDb!.transaction(['logs']);
const offset = new Date().getTimezoneOffset() * 60000;
const bound = IDBKeyRange.bound(getComposite(id, 0), getComposite(id, 1000000));
return iterate(trans.objectStore('logs').index('conversation-day').openCursor(bound, 'nextunique'), (value: StoredMessage) =>
new Date((hasComposite ? <number>value.day : decode((<string>value.day).substr(2))) * dayMs + offset));
return iterate(trans.objectStore('logs').index('conversation-day').openCursor(bound, 'nextunique'), (value: StoredMessage) => {
const date = new Date((hasComposite ? <number>value.day : decode((<string>value.day).substr(2))) * dayMs);
return new Date(date.getTime() + date.getTimezoneOffset() * 60000);
});
}
async getAvailableCharacters(): Promise<ReadonlyArray<string>> {
@ -148,7 +149,47 @@ export class Logs implements Logging {
export class SettingsStore implements Settings.Store {
async get<K extends keyof Settings.Keys>(key: K): Promise<Settings.Keys[K] | undefined> {
const stored = window.localStorage.getItem(`${core.connection.character}.settings.${key}`);
return stored !== null ? JSON.parse(stored) as Settings.Keys[K] : undefined;
if(stored === null) {
if(key === 'pinned') {
const tabs20 = window.localStorage.getItem(`tabs_${core.connection.character.toLowerCase()}`);
if(tabs20 !== null)
try {
const tabs = JSON.parse(tabs20) as {type: string, id: string, title: string}[];
const pinned: Settings.Keys['pinned'] = {channels: [], private: []};
pinned.channels = tabs.filter((x) => x.type === 'channel').map((x) => x.id.toLowerCase());
pinned.private = tabs.filter((x) => x.type === 'user').map((x) => x.title);
return pinned;
} catch {
return undefined;
}
} else if(key === 'settings') {
const settings20 = window.localStorage.getItem(`chat_settings`);
if(settings20 !== null)
try {
const old = JSON.parse(settings20) as {
animatedIcons: boolean, autoIdle: boolean, autoIdleTime: number, delimit: boolean, disableIconTag: boolean,
enableLogging: boolean, highlightMentions: boolean, highlightWords: string[],
html5Audio: boolean, joinLeaveAlerts: boolean, leftClickOpensFlist: boolean
};
const settings = new SettingsImpl();
settings.animatedEicons = old.animatedIcons;
if(old.autoIdle) settings.idleTimer = old.autoIdleTime / 60000;
settings.messageSeparators = old.delimit;
if(old.disableIconTag) settings.disallowedTags.push('icon');
settings.logMessages = old.enableLogging;
settings.highlight = old.highlightMentions;
settings.highlightWords = old.highlightWords;
settings.playSound = old.html5Audio;
settings.joinMessages = old.joinLeaveAlerts;
settings.clickOpensMessage = !old.leftClickOpensFlist;
return settings;
} catch {
return undefined;
}
}
return undefined;
}
return JSON.parse(stored) as Settings.Keys[K];
}
async set<K extends keyof Settings.Keys>(key: K, value: Settings.Keys[K]): Promise<void> {

View File

@ -1,6 +1,6 @@
{
"name": "net.f_list.fchat",
"version": "0.2.19",
"version": "3.0.0",
"displayName": "F-Chat",
"author": "The F-List Team",
"description": "F-List.net Chat Client",