3.0.3
This commit is contained in:
parent
959cac855a
commit
128c638ad4
|
@ -102,6 +102,7 @@
|
|||
this.sizer = <HTMLTextAreaElement>this.$refs['sizer'];
|
||||
this.sizer.style.cssText = styles.cssText;
|
||||
this.sizer.style.height = '0';
|
||||
this.sizer.style.minHeight = '0';
|
||||
this.sizer.style.overflow = 'hidden';
|
||||
this.sizer.style.position = 'absolute';
|
||||
this.sizer.style.top = '0';
|
||||
|
|
|
@ -45,7 +45,7 @@ export let defaultButtons: ReadonlyArray<EditorButton> = [
|
|||
key: Keys.KeyS
|
||||
},
|
||||
{
|
||||
title: 'Color (Ctrl+D)\n\nStyles text with a color. Valid colors are: red, orange, yellow, green, cyan, blue, purple, pink, black, white, gray, primary, secondary, accent, and contrast.',
|
||||
title: 'Color (Ctrl+D)\n\nStyles text with a color. Valid colors are: red, orange, yellow, green, cyan, blue, purple, pink, black, brown, white and gray.',
|
||||
tag: 'color',
|
||||
startText: '[color=]',
|
||||
icon: 'fa-eye-dropper',
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<modal :buttons="false" :action="l('chat.channels')" @close="closed" dialog-class="w-100 channel-list">
|
||||
<modal :buttons="false" :action="l('chat.channels')" @open="opened" @close="closed" dialog-class="w-100 channel-list">
|
||||
<div style="display:flex;flex-direction:column">
|
||||
<tabs style="flex-shrink:0" :tabs="[l('channelList.public'), l('channelList.private')]" v-model="tab"></tabs>
|
||||
<div style="display: flex; flex-direction: column">
|
||||
|
@ -88,6 +88,10 @@
|
|||
this.hide();
|
||||
}
|
||||
|
||||
opened(): void {
|
||||
core.channels.requestChannelsIfNeeded(30000);
|
||||
}
|
||||
|
||||
closed(): void {
|
||||
this.createName = '';
|
||||
}
|
||||
|
|
|
@ -16,6 +16,10 @@
|
|||
@Prop({required: true})
|
||||
readonly text!: string;
|
||||
|
||||
mounted(): void {
|
||||
core.channels.requestChannelsIfNeeded(300000);
|
||||
}
|
||||
|
||||
joinChannel(): void {
|
||||
if(this.channel === undefined || !this.channel.isJoined)
|
||||
core.channels.join(this.id);
|
|
@ -48,33 +48,29 @@
|
|||
|
||||
type BBCodeNode = Node & {bbcodeTag?: string, bbcodeParam?: string, bbcodeHide?: boolean};
|
||||
|
||||
function copyNode(str: string, node: BBCodeNode, range: Range, flags: {endFound?: true}): string {
|
||||
if(node === range.endContainer) flags.endFound = true;
|
||||
function copyNode(str: string, node: BBCodeNode, end: Node, range: Range, flags: {endFound?: true}): string {
|
||||
if(node === end) flags.endFound = true;
|
||||
if(node.bbcodeTag !== undefined)
|
||||
str = `[${node.bbcodeTag}${node.bbcodeParam !== undefined ? `=${node.bbcodeParam}` : ''}]${str}[/${node.bbcodeTag}]`;
|
||||
if(node.nextSibling !== null && !flags.endFound) {
|
||||
if(node instanceof HTMLElement && getComputedStyle(node).display === 'block') str += '\r\n';
|
||||
str += scanNode(node.nextSibling!, range, flags);
|
||||
str += scanNode(node.nextSibling!, end, range, flags);
|
||||
}
|
||||
if(node.parentElement === null) return str;
|
||||
return copyNode(str, node.parentNode!, range, flags);
|
||||
return copyNode(str, node.parentNode!, end, range, flags);
|
||||
}
|
||||
|
||||
function scanNode(node: BBCodeNode, range: Range, flags: {endFound?: true}, hide?: boolean): string {
|
||||
function scanNode(node: BBCodeNode, end: Node, range: Range, flags: {endFound?: true}, hide?: boolean): string {
|
||||
let str = '';
|
||||
hide = hide || node.bbcodeHide;
|
||||
if(node === range.endContainer) {
|
||||
if(node instanceof HTMLElement && node.children.length === 1 && node.firstElementChild instanceof HTMLImageElement)
|
||||
str += scanNode(node.firstElementChild, range, flags, hide);
|
||||
flags.endFound = true;
|
||||
}
|
||||
if(node === end) flags.endFound = true;
|
||||
if(node.bbcodeTag !== undefined) str += `[${node.bbcodeTag}${node.bbcodeParam !== undefined ? `=${node.bbcodeParam}` : ''}]`;
|
||||
if(node instanceof Text) str += node === range.endContainer ? node.nodeValue!.substr(0, range.endOffset) : node.nodeValue;
|
||||
else if(node instanceof HTMLImageElement) str += node.alt;
|
||||
if(node.firstChild !== null && !flags.endFound) str += scanNode(node.firstChild, range, flags, hide);
|
||||
if(node.firstChild !== null && !flags.endFound) str += scanNode(node.firstChild, end, range, flags, hide);
|
||||
if(node.bbcodeTag !== undefined) str += `[/${node.bbcodeTag}]`;
|
||||
if(node instanceof HTMLElement && getComputedStyle(node).display === 'block') str += '\r\n';
|
||||
if(node.nextSibling !== null && !flags.endFound) str += scanNode(node.nextSibling, range, flags, hide);
|
||||
if(node.nextSibling !== null && !flags.endFound) str += scanNode(node.nextSibling, end, range, flags, hide);
|
||||
return hide ? '' : str;
|
||||
}
|
||||
|
||||
|
@ -105,12 +101,15 @@
|
|||
const selection = document.getSelection();
|
||||
if(selection.isCollapsed) return;
|
||||
const range = selection.getRangeAt(0);
|
||||
const start = range.startContainer;
|
||||
let startValue = start.nodeValue !== null ?
|
||||
start.nodeValue.substring(range.startOffset, start === range.endContainer ? range.endOffset : undefined) : '';
|
||||
if(start instanceof HTMLElement && start.children.length === 1 && start.firstElementChild instanceof HTMLImageElement)
|
||||
startValue += scanNode(start.firstElementChild, range, {});
|
||||
e.clipboardData.setData('text/plain', copyNode(startValue, start, range, {}));
|
||||
let start = range.startContainer, end = range.endContainer;
|
||||
let startValue: string;
|
||||
if(start instanceof HTMLElement) {
|
||||
start = start.childNodes[range.startOffset];
|
||||
startValue = start instanceof HTMLImageElement ? start.alt : scanNode(start.firstChild!, end, range, {});
|
||||
} else
|
||||
startValue = start.nodeValue!.substring(range.startOffset, start === range.endContainer ? range.endOffset : undefined);
|
||||
if(end instanceof HTMLElement) end = end.childNodes[range.endOffset - 1];
|
||||
e.clipboardData.setData('text/plain', copyNode(startValue, start, end, range, {}));
|
||||
e.preventDefault();
|
||||
}) as EventListener);
|
||||
window.addEventListener('keydown', (e) => {
|
||||
|
|
|
@ -62,10 +62,10 @@
|
|||
<div class="input-group-prepend">
|
||||
<div class="input-group-text"><span class="fas fa-search"></span></div>
|
||||
</div>
|
||||
<input v-model="searchInput" @keydown.esc="showSearch = false; searchInput = ''" @keypress="lastSearchInput = Date.now()"
|
||||
<input v-model="searchInput" @keydown.esc="hideSearch" @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;z-index:10"
|
||||
@click="showSearch = false"><i class="fas fa-times"></i></a>
|
||||
@click="hideSearch"><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"
|
||||
ref="messages" @scroll="onMessagesScroll">
|
||||
|
@ -203,6 +203,11 @@
|
|||
clearInterval(this.searchTimer);
|
||||
}
|
||||
|
||||
hideSearch(): void {
|
||||
this.showSearch = false;
|
||||
this.searchInput = '';
|
||||
}
|
||||
|
||||
get conversation(): Conversation {
|
||||
return core.conversations.selectedConversation;
|
||||
}
|
||||
|
|
|
@ -8,32 +8,41 @@
|
|||
</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">
|
||||
<div :class="canZip ? 'col-sm-8 col-10 col-xl-9' : 'col-sm-10'">
|
||||
<select class="form-control" v-model="selectedCharacter" id="character" @change="loadCharacter">
|
||||
<option value="">{{l('logs.selectCharacter')}}</option>
|
||||
<option v-for="character in characters">{{character}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-2 col-xl-1" v-if="canZip">
|
||||
<button @click="downloadCharacter" class="btn btn-secondary form-control" :disabled="!selectedCharacter"><span
|
||||
class="fa fa-download"></span></button>
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<div :class="canZip ? 'col-sm-8 col-10 col-xl-9' : 'col-sm-10'">
|
||||
<filterable-select v-model="selectedConversation" :options="conversations" :filterFunc="filterConversation"
|
||||
:placeholder="l('filter')">
|
||||
<template slot-scope="s">
|
||||
{{s.option && ((s.option.key[0] == '#' ? '#' : '') + s.option.name) || l('logs.selectConversation')}}</template>
|
||||
{{s.option && ((s.option.key[0] == '#' ? '#' : '') + s.option.name) || l('logs.selectConversation')}}
|
||||
</template>
|
||||
</filterable-select>
|
||||
</div>
|
||||
<div class="col-2 col-xl-1" v-if="canZip">
|
||||
<button @click="downloadConversation" class="btn btn-secondary form-control" :disabled="!selectedConversation"><span
|
||||
class="fa fa-download"></span></button>
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<div class="col-sm-8 col-10 col-xl-9">
|
||||
<select class="form-control" v-model="selectedDate" id="date" @change="loadMessages">
|
||||
<option :value="null">{{l('logs.selectDate')}}</option>
|
||||
<option v-for="date in dates" :value="date.getTime()">{{formatDate(date)}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<div class="col-2 col-xl-1">
|
||||
<button @click="downloadDay" class="btn btn-secondary form-control" :disabled="!selectedDate"><span
|
||||
class="fa fa-download"></span></button>
|
||||
</div>
|
||||
|
@ -58,18 +67,19 @@
|
|||
import FilterableSelect from '../components/FilterableSelect.vue';
|
||||
import Modal from '../components/Modal.vue';
|
||||
import {Keys} from '../keys';
|
||||
import {getKey, messageToString} from './common';
|
||||
import {formatTime, getKey, messageToString} from './common';
|
||||
import core from './core';
|
||||
import {Conversation, Logs as LogInterface} from './interfaces';
|
||||
import l from './localize';
|
||||
import MessageView from './message_view';
|
||||
import Zip from './zip';
|
||||
|
||||
function formatDate(this: void, date: Date): string {
|
||||
return format(date, 'YYYY-MM-DD');
|
||||
}
|
||||
|
||||
function formatTime(this: void, date: Date): string {
|
||||
return format(date, 'YYYY-MM-DD HH:mm');
|
||||
function getLogs(messages: ReadonlyArray<Conversation.Message>): string {
|
||||
return messages.reduce((acc, x) => acc + messageToString(x, (date) => formatTime(date, true)), '');
|
||||
}
|
||||
|
||||
@Component({
|
||||
|
@ -91,6 +101,7 @@
|
|||
characters: ReadonlyArray<string> = [];
|
||||
selectedCharacter = core.connection.character;
|
||||
showFilters = true;
|
||||
canZip = core.logs.canZip;
|
||||
|
||||
get filteredMessages(): ReadonlyArray<Conversation.Message> {
|
||||
if(this.filter.length === 0) return this.messages;
|
||||
|
@ -131,22 +142,47 @@
|
|||
await this.loadMessages();
|
||||
}
|
||||
|
||||
download(file: string, logs: ReadonlyArray<Conversation.Message>): void {
|
||||
download(file: string, logs: string): void {
|
||||
const a = document.createElement('a');
|
||||
a.target = '_blank';
|
||||
a.href = `data:${encodeURIComponent(file)},${encodeURIComponent(logs.map((x) => messageToString(x, formatTime)).join(''))}`;
|
||||
a.href = logs;
|
||||
a.setAttribute('download', file);
|
||||
a.style.display = 'none';
|
||||
document.body.appendChild(a);
|
||||
setTimeout(() => {
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(logs);
|
||||
});
|
||||
}
|
||||
|
||||
downloadDay(): void {
|
||||
if(this.selectedConversation === null || this.selectedDate === null || this.messages.length === 0) return;
|
||||
this.download(`${this.selectedConversation.name}-${formatDate(new Date(this.selectedDate))}.txt`, this.messages);
|
||||
const name = `${this.selectedConversation.name}-${formatDate(new Date(this.selectedDate))}.txt`;
|
||||
this.download(name, `data:${encodeURIComponent(name)},${encodeURIComponent(getLogs(this.messages))}`);
|
||||
}
|
||||
|
||||
async downloadConversation(): Promise<void> {
|
||||
if(this.selectedConversation === null) return;
|
||||
const zip = new Zip();
|
||||
for(const date of this.dates) {
|
||||
const messages = await core.logs.getLogs(this.selectedCharacter, this.selectedConversation.key, date);
|
||||
zip.addFile(`${formatDate(date)}.txt`, getLogs(messages));
|
||||
}
|
||||
this.download(`${this.selectedConversation.name}.zip`, URL.createObjectURL(zip.build()));
|
||||
}
|
||||
|
||||
async downloadCharacter(): Promise<void> {
|
||||
if(this.selectedCharacter === '' || !confirm(l('logs.confirmExport', this.selectedCharacter))) return;
|
||||
const zip = new Zip();
|
||||
for(const conv of this.conversations) {
|
||||
zip.addFile(`${conv.name}/`, '');
|
||||
const dates = await core.logs.getLogDates(this.selectedCharacter, conv.key);
|
||||
for(const date of dates) {
|
||||
const messages = await core.logs.getLogs(this.selectedCharacter, conv.key, date);
|
||||
zip.addFile(`${conv.name}/${formatDate(date)}.txt`, getLogs(messages));
|
||||
}
|
||||
}
|
||||
this.download(`${this.selectedCharacter}.zip`, URL.createObjectURL(zip.build()));
|
||||
}
|
||||
|
||||
async onOpen(): Promise<void> {
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
import Component from 'vue-class-component';
|
||||
import CustomDialog from '../components/custom_dialog';
|
||||
import Modal from '../components/Modal.vue';
|
||||
import ChannelView from './ChannelView.vue';
|
||||
import ChannelView from './ChannelTagView.vue';
|
||||
import core from './core';
|
||||
import {Character, Conversation} from './interfaces';
|
||||
import l from './localize';
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
<template>
|
||||
<modal :action="l('settings.action')" @submit="submit" @close="init()" id="settings" dialogClass="w-100">
|
||||
<tabs style="flex-shrink:0;margin-bottom:10px" :tabs="tabs" v-model="selectedTab"></tabs>
|
||||
<div v-show="selectedTab == 'general'">
|
||||
<tabs style="flex-shrink:0;margin-bottom:10px" v-model="selectedTab"
|
||||
:tabs="[l('settings.tabs.general'), l('settings.tabs.notifications'), l('settings.tabs.import')]"></tabs>
|
||||
<div v-show="selectedTab == 0">
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="disallowedTags">{{l('settings.disallowedTags')}}</label>
|
||||
<input id="disallowedTags" class="form-control" v-model="disallowedTags"/>
|
||||
|
@ -63,7 +64,7 @@
|
|||
<input id="fontSize" type="number" min="10" max="24" class="form-control" v-model="fontSize"/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="selectedTab == 'notifications'">
|
||||
<div v-show="selectedTab == 1">
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="playSound">
|
||||
<input type="checkbox" id="playSound" v-model="playSound"/>
|
||||
|
@ -111,8 +112,8 @@
|
|||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="selectedTab == 'import'" style="display:flex;padding-top:10px">
|
||||
<select id="import" class="form-control" v-model="importCharacter" style="flex:1;">
|
||||
<div v-show="selectedTab == 2" style="display:flex;padding-top:10px">
|
||||
<select id="import" class="form-control" v-model="importCharacter" style="flex:1;margin-right:10px">
|
||||
<option value="">{{l('settings.import.selectCharacter')}}</option>
|
||||
<option v-for="character in availableImports" :value="character">{{character}}</option>
|
||||
</select>
|
||||
|
@ -136,7 +137,7 @@
|
|||
export default class SettingsView extends CustomDialog {
|
||||
l = l;
|
||||
availableImports: ReadonlyArray<string> = [];
|
||||
selectedTab = 'general';
|
||||
selectedTab = '0';
|
||||
importCharacter = '';
|
||||
playSound!: boolean;
|
||||
clickOpensMessage!: boolean;
|
||||
|
@ -205,13 +206,6 @@
|
|||
core.conversations.reloadSettings();
|
||||
}
|
||||
|
||||
get tabs(): {readonly [key: string]: string} {
|
||||
const tabs: {[key: string]: string} = {};
|
||||
(this.availableImports.length > 0 ? ['general', 'notifications', 'import'] : ['general', 'notifications'])
|
||||
.forEach((item) => tabs[item] = l(`settings.tabs.${item}`));
|
||||
return tabs;
|
||||
}
|
||||
|
||||
async submit(): Promise<void> {
|
||||
core.state.settings = {
|
||||
playSound: this.playSound,
|
||||
|
|
|
@ -3,7 +3,7 @@ import {CoreBBCodeParser} from '../bbcode/core';
|
|||
//tslint:disable-next-line:match-default-export-name
|
||||
import BaseEditor from '../bbcode/Editor.vue';
|
||||
import {BBCodeTextTag} from '../bbcode/parser';
|
||||
import ChannelView from './ChannelView.vue';
|
||||
import ChannelView from './ChannelTagView.vue';
|
||||
import {characterImage} from './common';
|
||||
import core from './core';
|
||||
import {Character} from './interfaces';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {format, isToday} from 'date-fns';
|
||||
import {isToday} from 'date-fns';
|
||||
import {Keys} from '../keys';
|
||||
import {Character, Conversation, Settings as ISettings} from './interfaces';
|
||||
|
||||
|
@ -13,13 +13,10 @@ export function characterImage(this: void | never, character: string): string {
|
|||
export function getByteLength(this: void | never, str: string): number {
|
||||
let byteLen = 0;
|
||||
for(let i = 0; i < str.length; i++) {
|
||||
const c = str.charCodeAt(i);
|
||||
byteLen += c < (1 << 7) ? 1 :
|
||||
c < (1 << 11) ? 2 :
|
||||
c < (1 << 16) ? 3 :
|
||||
c < (1 << 21) ? 4 :
|
||||
c < (1 << 26) ? 5 :
|
||||
c < (1 << 31) ? 6 : Number.NaN;
|
||||
let c = str.charCodeAt(i);
|
||||
if(c > 0xD800 && c < 0xD8FF) //surrogate pairs
|
||||
c = (c - 0xD800) * 0x400 + str.charCodeAt(++i) - 0xDC00 + 0x10000;
|
||||
byteLen += c < 0x80 ? 1 : c < 0x800 ? 2 : c < 0x10000 ? 3 : c < 0x200000 ? 4 : c < 0x4000000 ? 5 : 6;
|
||||
}
|
||||
return byteLen;
|
||||
}
|
||||
|
@ -54,9 +51,13 @@ export class ConversationSettings implements Conversation.Settings {
|
|||
defaultHighlights = true;
|
||||
}
|
||||
|
||||
export function formatTime(this: void | never, date: Date): string {
|
||||
if(isToday(date)) return format(date, 'HH:mm');
|
||||
return format(date, 'YYYY-MM-DD HH:mm');
|
||||
function pad(num: number): string | number {
|
||||
return num < 10 ? `0${num}` : num;
|
||||
}
|
||||
|
||||
export function formatTime(this: void | never, date: Date, noDate: boolean = false): string {
|
||||
if(!noDate && isToday(date)) return `${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
||||
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
||||
}
|
||||
|
||||
export function messageToString(this: void | never, msg: Conversation.Message, timeFormatter: (date: Date) => string = formatTime): string {
|
||||
|
@ -75,6 +76,7 @@ export function getKey(e: KeyboardEvent): Keys {
|
|||
export function errorToString(e: any): string {
|
||||
return e instanceof Error ? e.message : e !== undefined ? e.toString() : '';
|
||||
}
|
||||
|
||||
//tslint:enable
|
||||
|
||||
let messageId = 0;
|
||||
|
|
|
@ -727,7 +727,8 @@ export default function(this: void): Interfaces.State {
|
|||
const message = new EventMessage(l(key, `[user]${data.character}[/user]`, status, decodeHTML(data.statusmsg)), time);
|
||||
await addEventMessage(message);
|
||||
const conv = state.privateMap[data.character.toLowerCase()];
|
||||
if(conv !== undefined && core.state.settings.eventMessages && conv !== state.selectedConversation) await conv.addMessage(message);
|
||||
if(conv !== undefined && (!core.state.settings.eventMessages || conv !== state.selectedConversation))
|
||||
await conv.addMessage(message);
|
||||
});
|
||||
connection.onMessage('SYS', async(data, time) => {
|
||||
state.selectedConversation.infoText = data.message;
|
||||
|
|
|
@ -133,6 +133,7 @@ export interface Logs {
|
|||
getLogs(character: string, key: string, date: Date): Promise<ReadonlyArray<Conversation.Message>>
|
||||
getLogDates(character: string, key: string): Promise<ReadonlyArray<Date>>
|
||||
getAvailableCharacters(): Promise<ReadonlyArray<string>>
|
||||
canZip: boolean;
|
||||
}
|
||||
|
||||
export namespace Settings {
|
||||
|
|
|
@ -7,6 +7,7 @@ const strings: {[key: string]: string | undefined} = {
|
|||
'action.copyWithoutBBCode': 'Copy without BBCode',
|
||||
'action.paste': 'Paste',
|
||||
'action.copyLink': 'Copy Link',
|
||||
'action.incognito': 'Open in Incognito mode',
|
||||
'action.suggestions': 'Suggestions',
|
||||
'action.open': 'Show',
|
||||
'action.close': 'Close',
|
||||
|
@ -81,6 +82,7 @@ const strings: {[key: string]: string | undefined} = {
|
|||
'logs.title': 'Logs',
|
||||
'logs.character': 'Character',
|
||||
'logs.conversation': 'Conversation',
|
||||
'logs.confirmExport': 'Are you sure you would like to export all logs for {0}? If you have a lot of logs, this might take a while.',
|
||||
'logs.date': 'Date',
|
||||
'logs.selectCharacter': 'Select a character...',
|
||||
'logs.selectConversation': 'Select a conversation...',
|
||||
|
@ -168,7 +170,7 @@ Current log location: {1}`,
|
|||
'settings.fontSize': 'Font size (experimental)',
|
||||
'settings.showNeedsReply': 'Show indicator on PM tabs with unanswered messages',
|
||||
'settings.defaultHighlights': 'Use global highlight words',
|
||||
'settings.colorBookmarks': 'Show bookmarks in a different colour',
|
||||
'settings.colorBookmarks': 'Show friends/bookmarks in a different colour',
|
||||
'settings.beta': 'Opt-in to test unstable prerelease updates',
|
||||
'fixLogs.action': 'Fix corrupted logs',
|
||||
'fixLogs.text': `There are a few reason log files can become corrupted - log files from old versions with bugs that have since been fixed or incomplete file operations caused by computer crashes are the most common.
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
let crcTable!: number[];
|
||||
|
||||
export default class Zip {
|
||||
private blob: (object | string)[] = [];
|
||||
private files: {header: object[], offset: number, name: string}[] = [];
|
||||
private offset = 0;
|
||||
|
||||
constructor() {
|
||||
if(crcTable !== undefined!) return;
|
||||
crcTable = [];
|
||||
for(let c, n = 0; n < 256; n++) {
|
||||
c = n;
|
||||
for(let k = 0; k < 8; k++)
|
||||
c = ((c & 1) ? ((c >>> 1) ^ 0xEDB88320) : (c >>> 1)); //tslint:disable-line:strict-boolean-expressions
|
||||
crcTable[n] = c;
|
||||
}
|
||||
}
|
||||
|
||||
addFile(name: string, content: string): void {
|
||||
let crc = -1;
|
||||
let length = 0;
|
||||
for(let i = 0, strlen = content.length; i < strlen; ++i) {
|
||||
let c = content.charCodeAt(i);
|
||||
if(c > 0xD800 && c < 0xD8FF) //surrogate pairs
|
||||
c = (c - 0xD800) * 0x400 + content.charCodeAt(++i) - 0xDC00 + 0x10000;
|
||||
let l = c < 0x80 ? 1 : c < 0x800 ? 2 : c < 0x10000 ? 3 : c < 0x200000 ? 4 : c < 0x4000000 ? 5 : 6;
|
||||
length += l;
|
||||
let byte = l === 1 ? c : ((0xFF00 >> l) % 256) | (c >>> (l - 1) * 6);
|
||||
--l;
|
||||
while(true) {
|
||||
crc = (crc >>> 8) ^ crcTable[(crc ^ byte) & 0xFF];
|
||||
if(--l >= 0) byte = ((c >>> (l * 6)) & 0x3F) | 0x80;
|
||||
else break;
|
||||
}
|
||||
}
|
||||
crc = (crc ^ (-1)) >>> 0;
|
||||
const file = {
|
||||
header: [Uint16Array.of(0, 0, 0, 0, 0), Uint32Array.of(crc, length, length), Uint16Array.of(name.length, 0)],
|
||||
offset: this.offset, name
|
||||
};
|
||||
this.blob.push(Uint32Array.of(0x04034B50));
|
||||
this.blob.push(...file.header);
|
||||
this.blob.push(name, content);
|
||||
this.offset += name.length + length + 30;
|
||||
this.files.push(file);
|
||||
}
|
||||
|
||||
build(): Blob {
|
||||
const start = this.offset;
|
||||
for(const file of this.files) {
|
||||
this.blob.push(Uint16Array.of(0x4B50, 0x0201, 0));
|
||||
this.blob.push(...file.header);
|
||||
this.blob.push(Uint16Array.of(0, 0, 0, 0, 0), Uint32Array.of(file.offset), file.name);
|
||||
this.offset += file.name.length + 46;
|
||||
}
|
||||
this.blob.push(Uint16Array.of(0x4B50, 0x0605, 0, 0, this.files.length, this.files.length),
|
||||
Uint32Array.of(this.offset - start, start), Uint16Array.of(0));
|
||||
return new Blob(this.blob);
|
||||
}
|
||||
}
|
|
@ -9,7 +9,7 @@
|
|||
</h4>
|
||||
<button type="button" class="close" @click="hide" aria-label="Close" v-show="!keepOpen">×</button>
|
||||
</div>
|
||||
<div class="modal-body" style="overflow:auto">
|
||||
<div class="modal-body" style="overflow:auto" tabindex="-1">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div class="modal-footer" v-if="buttons">
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "fchat",
|
||||
"version": "3.0.0",
|
||||
"version": "3.0.3",
|
||||
"author": "The F-List Team",
|
||||
"description": "F-List.net Chat Client",
|
||||
"main": "main.js",
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
* @see {@link https://github.com/f-list/exported|GitHub repo}
|
||||
*/
|
||||
import Axios from 'axios';
|
||||
import {exec} from 'child_process';
|
||||
import {exec, execSync} from 'child_process';
|
||||
import * as electron from 'electron';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
@ -87,6 +87,26 @@ if(process.env.NODE_ENV === 'production') {
|
|||
console.log(`%c${l('consoleWarning.body')}`, 'font-size: 16pt; color:red');
|
||||
});
|
||||
}
|
||||
let browser: string | undefined;
|
||||
function openIncognito(url: string): void {
|
||||
if(browser === undefined)
|
||||
try { //tslint:disable-next-line:max-line-length
|
||||
browser = execSync(`FOR /F "skip=2 tokens=3" %A IN ('REG QUERY HKCU\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\http\\UserChoice /v ProgId') DO @(echo %A)`)
|
||||
.toString().trim();
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
}
|
||||
switch(browser) {
|
||||
case 'FirefoxURL':
|
||||
exec(`start firefox.exe -private-window ${url}`);
|
||||
break;
|
||||
case 'ChromeHTML':
|
||||
exec(`start chrome.exe -incognito ${url}`);
|
||||
break;
|
||||
default:
|
||||
exec(`start iexplore.exe -private ${url}`);
|
||||
}
|
||||
}
|
||||
|
||||
const webContents = electron.remote.getCurrentWebContents();
|
||||
webContents.on('context-menu', (_, props) => {
|
||||
|
@ -116,7 +136,7 @@ webContents.on('context-menu', (_, props) => {
|
|||
accelerator: 'CmdOrCtrl+V',
|
||||
enabled: props.editFlags.canPaste
|
||||
});
|
||||
else if(props.linkURL.length > 0 && props.mediaType === 'none' && props.linkURL.substr(0, props.pageURL.length) !== props.pageURL)
|
||||
else if(props.linkURL.length > 0 && props.mediaType === 'none' && props.linkURL.substr(0, props.pageURL.length) !== props.pageURL) {
|
||||
menuTemplate.push({
|
||||
id: 'copyLink',
|
||||
label: l('action.copyLink'),
|
||||
|
@ -127,7 +147,13 @@ webContents.on('context-menu', (_, props) => {
|
|||
electron.clipboard.writeText(props.linkURL);
|
||||
}
|
||||
});
|
||||
else if(hasText)
|
||||
if(process.platform === 'win32')
|
||||
menuTemplate.push({
|
||||
id: 'incognito',
|
||||
label: l('action.incognito'),
|
||||
click: () => openIncognito(props.linkURL)
|
||||
});
|
||||
} else if(hasText)
|
||||
menuTemplate.push({
|
||||
label: l('action.copyWithoutBBCode'),
|
||||
enabled: can('Copy'),
|
||||
|
|
|
@ -93,18 +93,17 @@ export function serializeMessage(message: Message): {serialized: Buffer, size: n
|
|||
return {serialized: buffer, size: offset + 2};
|
||||
}
|
||||
|
||||
function deserializeMessage(buffer: Buffer, characterGetter: (name: string) => Character = (name) => core.characters.get(name),
|
||||
unsafe: boolean = noAssert): {end: number, message: Conversation.Message} {
|
||||
const time = buffer.readUInt32LE(0, unsafe);
|
||||
const type = buffer.readUInt8(4, unsafe);
|
||||
const senderLength = buffer.readUInt8(5, unsafe);
|
||||
let offset = senderLength + 6;
|
||||
const sender = buffer.toString('utf8', 6, offset);
|
||||
function deserializeMessage(buffer: Buffer, offset: number = 0,
|
||||
characterGetter: (name: string) => Character = (name) => core.characters.get(name),
|
||||
unsafe: boolean = noAssert): {size: number, message: Conversation.Message} {
|
||||
const time = buffer.readUInt32LE(offset, unsafe);
|
||||
const type = buffer.readUInt8(offset += 4, unsafe);
|
||||
const senderLength = buffer.readUInt8(offset += 1, unsafe);
|
||||
const sender = buffer.toString('utf8', offset += 1, offset += senderLength);
|
||||
const messageLength = buffer.readUInt16LE(offset, unsafe);
|
||||
offset += 2;
|
||||
const text = buffer.toString('utf8', offset, offset += messageLength);
|
||||
const text = buffer.toString('utf8', offset += 2, offset + messageLength);
|
||||
const message = new MessageImpl(type, characterGetter(sender), text, new Date(time * 1000));
|
||||
return {message, end: offset + 2};
|
||||
return {message, size: senderLength + messageLength + 10};
|
||||
}
|
||||
|
||||
export function fixLogs(character: string): void {
|
||||
|
@ -126,7 +125,7 @@ export function fixLogs(character: string): void {
|
|||
while(pos < size) {
|
||||
buffer.fill(-1);
|
||||
fs.readSync(fd, buffer, 0, 50100, pos);
|
||||
const deserialized = deserializeMessage(buffer, (name) => ({
|
||||
const deserialized = deserializeMessage(buffer, 0, (name) => ({
|
||||
gender: 'None', status: 'online', statusText: '', isFriend: false, isBookmarked: false, isChatOp: false,
|
||||
isIgnored: false, name
|
||||
}), false);
|
||||
|
@ -138,8 +137,8 @@ export function fixLogs(character: string): void {
|
|||
fs.writeSync(indexFd, buffer, 0, 7);
|
||||
lastDay = day;
|
||||
}
|
||||
if(buffer.readUInt16LE(deserialized.end - 2) !== deserialized.end - 2) throw new Error();
|
||||
pos += deserialized.end;
|
||||
if(buffer.readUInt16LE(deserialized.size - 2) !== deserialized.size - 2) throw new Error();
|
||||
pos += deserialized.size;
|
||||
}
|
||||
} catch {
|
||||
fs.ftruncateSync(fd, pos);
|
||||
|
@ -174,6 +173,7 @@ function loadIndex(name: string): Index {
|
|||
}
|
||||
|
||||
export class Logs implements Logging {
|
||||
canZip = true;
|
||||
private index: Index = {};
|
||||
private loadedIndex?: Index;
|
||||
private loadedCharacter?: string;
|
||||
|
@ -226,19 +226,20 @@ export class Logs implements Logging {
|
|||
if(index === undefined) return [];
|
||||
const dateOffset = index.index[Math.floor(date.getTime() / dayMs - date.getTimezoneOffset() / 1440)];
|
||||
if(dateOffset === undefined) return [];
|
||||
const buffer = Buffer.allocUnsafe(50100);
|
||||
const messages: Conversation.Message[] = [];
|
||||
const file = getLogFile(character, key);
|
||||
const fd = fs.openSync(file, 'r');
|
||||
let pos = index.offsets[dateOffset];
|
||||
const size = dateOffset + 1 < index.offsets.length ? index.offsets[dateOffset + 1] : (fs.fstatSync(fd)).size;
|
||||
while(pos < size) {
|
||||
fs.readSync(fd, buffer, 0, 50100, pos);
|
||||
const deserialized = deserializeMessage(buffer);
|
||||
messages.push(deserialized.message);
|
||||
pos += deserialized.end;
|
||||
}
|
||||
const pos = index.offsets[dateOffset];
|
||||
const fd = fs.openSync(getLogFile(character, key), 'r');
|
||||
const end = dateOffset + 1 < index.offsets.length ? index.offsets[dateOffset + 1] : (fs.fstatSync(fd)).size;
|
||||
const length = end - pos;
|
||||
const buffer = Buffer.allocUnsafe(length);
|
||||
fs.readSync(fd, buffer, 0, length, pos);
|
||||
fs.closeSync(fd);
|
||||
let offset = 0;
|
||||
while(offset < length) {
|
||||
const deserialized = deserializeMessage(buffer, offset);
|
||||
messages.push(deserialized.message);
|
||||
offset += deserialized.size;
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
|
||||
|
|
|
@ -86,6 +86,7 @@ class State implements Interfaces.State {
|
|||
joinedChannels: Channel[] = [];
|
||||
joinedMap: {[key: string]: Channel | undefined} = {};
|
||||
handlers: Interfaces.EventHandler[] = [];
|
||||
lastRequest = 0;
|
||||
|
||||
constructor(private connection: Connection) {
|
||||
}
|
||||
|
@ -110,13 +111,19 @@ class State implements Interfaces.State {
|
|||
getChannel(id: string): Channel | undefined {
|
||||
return this.joinedMap[id.toLowerCase()];
|
||||
}
|
||||
|
||||
requestChannelsIfNeeded(maxAge: number): void {
|
||||
if(this.lastRequest + maxAge > Date.now()) return;
|
||||
this.lastRequest = Date.now();
|
||||
this.connection.send('CHA');
|
||||
this.connection.send('ORS');
|
||||
}
|
||||
}
|
||||
|
||||
let state: State;
|
||||
|
||||
export default function(this: void, connection: Connection, characters: Character.State): Interfaces.State {
|
||||
state = new State(connection);
|
||||
let getChannelTimer: NodeJS.Timer | undefined;
|
||||
let rejoin: string[] | undefined;
|
||||
connection.onEvent('connecting', (isReconnect) => {
|
||||
if(isReconnect && rejoin === undefined) rejoin = Object.keys(state.joinedMap);
|
||||
|
@ -128,16 +135,6 @@ export default function(this: void, connection: Connection, characters: Characte
|
|||
queuedJoin(rejoin);
|
||||
rejoin = undefined;
|
||||
}
|
||||
const getChannels = () => {
|
||||
connection.send('CHA');
|
||||
connection.send('ORS');
|
||||
};
|
||||
getChannels();
|
||||
if(getChannelTimer !== undefined) clearInterval(getChannelTimer);
|
||||
getChannelTimer = setInterval(getChannels, 60000);
|
||||
});
|
||||
connection.onEvent('closed', () => {
|
||||
if(getChannelTimer !== undefined) clearInterval(getChannelTimer);
|
||||
});
|
||||
|
||||
connection.onMessage('CHA', (data) => {
|
||||
|
@ -269,5 +266,13 @@ export default function(this: void, connection: Connection, characters: Characte
|
|||
};
|
||||
connection.onMessage('AOP', globalHandler);
|
||||
connection.onMessage('DOP', globalHandler);
|
||||
connection.onMessage('RTB', (data) => {
|
||||
if(data.type !== 'trackadd' && data.type !== 'trackrem' && data.type !== 'friendadd' && data.type !== 'friendremove') return;
|
||||
for(const key in state.joinedMap) {
|
||||
const channel = state.joinedMap[key]!;
|
||||
const member = channel.members[data.name];
|
||||
if(member !== undefined) channel.reSortMember(member);
|
||||
}
|
||||
});
|
||||
return state;
|
||||
}
|
|
@ -36,6 +36,7 @@ export default class Connection implements Interfaces.Connection {
|
|||
try {
|
||||
this.ticket = await this.ticketProvider();
|
||||
} catch(e) {
|
||||
if(this.reconnectTimer !== undefined) this.reconnect();
|
||||
return this.invokeErrorHandlers(<Error>e, true);
|
||||
}
|
||||
try {
|
||||
|
@ -85,6 +86,7 @@ export default class Connection implements Interfaces.Connection {
|
|||
|
||||
close(): void {
|
||||
if(this.reconnectTimer !== undefined) clearTimeout(this.reconnectTimer);
|
||||
this.reconnectTimer = undefined;
|
||||
this.cleanClose = true;
|
||||
if(this.socket !== undefined) this.socket.close();
|
||||
}
|
||||
|
|
|
@ -193,6 +193,7 @@ export namespace Channel {
|
|||
onEvent(handler: EventHandler): void
|
||||
getChannelItem(id: string): ListItem | undefined
|
||||
getChannel(id: string): Channel | undefined
|
||||
requestChannelsIfNeeded(maxAge: number): void;
|
||||
}
|
||||
|
||||
export const enum Rank {
|
||||
|
|
|
@ -8,8 +8,8 @@ android {
|
|||
applicationId "net.f_list.fchat"
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 27
|
||||
versionCode 13
|
||||
versionName "3.0.0"
|
||||
versionCode 14
|
||||
versionName "3.0.3"
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
|
|
|
@ -37,6 +37,7 @@ export class GeneralSettings {
|
|||
type Index = {[key: string]: {name: string, dates: number[]} | undefined};
|
||||
|
||||
export class Logs implements Logging {
|
||||
canZip = false;
|
||||
private index: Index = {};
|
||||
private loadedIndex?: Index;
|
||||
private loadedCharacter?: string;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "net.f_list.fchat",
|
||||
"version": "3.0.0",
|
||||
"version": "3.0.3",
|
||||
"displayName": "F-Chat",
|
||||
"author": "The F-List Team",
|
||||
"description": "F-List.net Chat Client",
|
||||
|
|
19
package.json
19
package.json
|
@ -5,11 +5,11 @@
|
|||
"description": "F-List Exported",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@fortawesome/fontawesome-free-webfonts": "^1.0.5",
|
||||
"@types/node": "^9.6.0",
|
||||
"@fortawesome/fontawesome-free-webfonts": "^1.0.6",
|
||||
"@types/node": "^9.6.5",
|
||||
"@types/sortablejs": "^1.3.31",
|
||||
"axios": "^0.18.0",
|
||||
"bootstrap": "^4.0.0",
|
||||
"bootstrap": "^4.1.0",
|
||||
"css-loader": "^0.28.11",
|
||||
"date-fns": "^1.28.5",
|
||||
"electron": "^1.8.4",
|
||||
|
@ -23,10 +23,10 @@
|
|||
"node-sass": "^4.8.3",
|
||||
"optimize-css-assets-webpack-plugin": "^4.0.0",
|
||||
"qs": "^6.5.1",
|
||||
"raven-js": "^3.24.0",
|
||||
"sass-loader": "^6.0.7",
|
||||
"raven-js": "^3.24.1",
|
||||
"sass-loader": "^7.0.1",
|
||||
"sortablejs": "^1.6.0",
|
||||
"ts-loader": "^4.1.0",
|
||||
"ts-loader": "^4.2.0",
|
||||
"tslib": "^1.7.1",
|
||||
"tslint": "^5.7.0",
|
||||
"typescript": "^2.8.1",
|
||||
|
@ -35,11 +35,12 @@
|
|||
"vue-loader": "^14.2.2",
|
||||
"vue-property-decorator": "^6.0.0",
|
||||
"vue-template-compiler": "^2.5.16",
|
||||
"webpack": "^4.3.0"
|
||||
"webpack": "^4.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/lodash": "^4.14.106",
|
||||
"@types/lodash": "^4.14.107",
|
||||
"keytar": "^4.2.1",
|
||||
"spellchecker": "^3.4.3"
|
||||
"spellchecker": "^3.4.3",
|
||||
"style-loader": "^0.20.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,7 +42,8 @@ import Connection from '../fchat/connection';
|
|||
import '../scss/fa.scss'; //tslint:disable-line:no-import-side-effect
|
||||
import {Logs, SettingsStore} from './logs';
|
||||
|
||||
if(typeof Promise !== 'function' || typeof Notification !== 'function') //tslint:disable-line:strict-type-predicates
|
||||
//@ts-ignore
|
||||
if(typeof window.Promise !== 'function' || typeof window.Notification !== 'function') //tslint:disable-line:strict-type-predicates
|
||||
alert('Your browser is too old to be supported by F-Chat 3.0. Please update to a newer version.');
|
||||
|
||||
const version = (<{version: string}>require('./package.json')).version; //tslint:disable-line:no-require-imports
|
||||
|
|
|
@ -62,23 +62,29 @@ async function getIndex(db: IDBDatabase): Promise<Index> {
|
|||
}
|
||||
|
||||
export class Logs implements Logging {
|
||||
canZip = true;
|
||||
index?: Index;
|
||||
loadedDb?: IDBDatabase;
|
||||
loadedCharacter?: string;
|
||||
loadedIndex?: Index;
|
||||
db!: IDBDatabase;
|
||||
db?: IDBDatabase;
|
||||
|
||||
constructor() {
|
||||
core.connection.onEvent('connecting', async() => {
|
||||
const characters = (await this.getAvailableCharacters());
|
||||
if(characters.indexOf(core.connection.character) === -1)
|
||||
window.localStorage.setItem(charactersKey, JSON.stringify(characters.concat(core.connection.character)));
|
||||
this.db = await openDatabase(core.connection.character);
|
||||
this.index = await getIndex(this.db);
|
||||
try {
|
||||
this.db = await openDatabase(core.connection.character);
|
||||
this.index = await getIndex(this.db);
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async logMessage(conversation: Conversation, message: Conversation.Message): Promise<void> {
|
||||
if(this.db === undefined) return;
|
||||
let conv = this.index![conversation.key];
|
||||
if(conv === undefined) {
|
||||
const cTrans = this.db.transaction(['conversations'], 'readwrite');
|
||||
|
@ -95,6 +101,7 @@ export class Logs implements Logging {
|
|||
}
|
||||
|
||||
async getBacklog(conversation: Conversation): Promise<ReadonlyArray<Conversation.Message>> {
|
||||
if(this.db === undefined) return [];
|
||||
const trans = this.db.transaction(['logs']);
|
||||
const conv = this.index![conversation.key];
|
||||
if(conv === undefined) return [];
|
||||
|
@ -105,14 +112,18 @@ export class Logs implements Logging {
|
|||
|
||||
private async loadIndex(character: string): Promise<Index> {
|
||||
if(character === this.loadedCharacter) return this.loadedIndex!;
|
||||
this.loadedCharacter = character;
|
||||
if(character === core.connection.character) {
|
||||
this.loadedDb = this.db;
|
||||
this.loadedIndex = this.index;
|
||||
} else {
|
||||
this.loadedDb = await openDatabase(character);
|
||||
this.loadedIndex = await getIndex(this.loadedDb);
|
||||
}
|
||||
} else
|
||||
try {
|
||||
this.loadedDb = await openDatabase(character);
|
||||
this.loadedIndex = await getIndex(this.loadedDb);
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
return {};
|
||||
}
|
||||
this.loadedCharacter = character;
|
||||
return this.loadedIndex!;
|
||||
}
|
||||
|
||||
|
@ -122,8 +133,10 @@ export class Logs implements Logging {
|
|||
}
|
||||
|
||||
async getLogs(character: string, key: string, date: Date): Promise<ReadonlyArray<Conversation.Message>> {
|
||||
const id = (await this.loadIndex(character))[key]!.id;
|
||||
const trans = this.loadedDb!.transaction(['logs']);
|
||||
await this.loadIndex(character);
|
||||
if(this.loadedDb === undefined) return [];
|
||||
const id = this.loadedIndex![key]!.id;
|
||||
const trans = this.loadedDb.transaction(['logs']);
|
||||
const day = Math.floor(date.getTime() / dayMs - date.getTimezoneOffset() / 1440);
|
||||
return iterate(trans.objectStore('logs').index('conversation-day').openCursor(getComposite(id, day)),
|
||||
(value: StoredMessage) => value.type === Conversation.Message.Type.Event ? new EventMessage(value.text, value.time) :
|
||||
|
@ -131,8 +144,10 @@ 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']);
|
||||
await this.loadIndex(character);
|
||||
if(this.loadedDb === undefined) return [];
|
||||
const id = this.loadedIndex![key]!.id;
|
||||
const trans = this.loadedDb.transaction(['logs']);
|
||||
const bound = IDBKeyRange.bound(getComposite(id, 0), getComposite(id, 1000000));
|
||||
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);
|
||||
|
@ -147,11 +162,11 @@ 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}`);
|
||||
async get<K extends keyof Settings.Keys>(key: K, character: string = core.connection.character): Promise<Settings.Keys[K] | undefined> {
|
||||
const stored = window.localStorage.getItem(`${character}.settings.${key}`);
|
||||
if(stored === null) {
|
||||
if(key === 'pinned') {
|
||||
const tabs20 = window.localStorage.getItem(`tabs_${core.connection.character.toLowerCase()}`);
|
||||
const tabs20 = window.localStorage.getItem(`tabs_${character.toLowerCase().replace(' ', '_')}`);
|
||||
if(tabs20 !== null)
|
||||
try {
|
||||
const tabs = JSON.parse(tabs20) as {type: string, id: string, title: string}[];
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "net.f_list.fchat",
|
||||
"version": "3.0.0",
|
||||
"version": "3.0.3",
|
||||
"displayName": "F-Chat",
|
||||
"author": "The F-List Team",
|
||||
"description": "F-List.net Chat Client",
|
||||
|
|
184
yarn.lock
184
yarn.lock
|
@ -22,21 +22,21 @@
|
|||
"7zip-bin-mac" "~1.0.1"
|
||||
"7zip-bin-win" "~2.2.0"
|
||||
|
||||
"@fortawesome/fontawesome-free-webfonts@^1.0.5":
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free-webfonts/-/fontawesome-free-webfonts-1.0.5.tgz#3b9e023b6230cdb144204f08e77178c7694e9045"
|
||||
"@fortawesome/fontawesome-free-webfonts@^1.0.6":
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free-webfonts/-/fontawesome-free-webfonts-1.0.6.tgz#3dd13aa1a7466bff8fb0a401e1771c011ef2ca14"
|
||||
|
||||
"@types/lodash@^4.14.106":
|
||||
version "4.14.106"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.106.tgz#6093e9a02aa567ddecfe9afadca89e53e5dce4dd"
|
||||
"@types/lodash@^4.14.107":
|
||||
version "4.14.107"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.107.tgz#b2d2ae3958bfb8ff828495cbe12214af9e4d035e"
|
||||
|
||||
"@types/node@*", "@types/node@^9.6.0":
|
||||
version "9.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-9.6.0.tgz#d3480ee666df9784b1001a1872a2f6ccefb6c2d7"
|
||||
"@types/node@*", "@types/node@^9.6.5":
|
||||
version "9.6.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-9.6.5.tgz#ee700810fdf49ac1c399fc5980b7559b3e5a381d"
|
||||
|
||||
"@types/node@^8.0.24":
|
||||
version "8.10.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.0.tgz#f5d649cc49af8ed6507d15dc6e9b43fe8b927540"
|
||||
version "8.10.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.8.tgz#794cba23cc9f8d9715f6543fa8827433b5f5cd3b"
|
||||
|
||||
"@types/sortablejs@^1.3.31":
|
||||
version "1.3.32"
|
||||
|
@ -318,8 +318,8 @@ aws-sign2@~0.7.0:
|
|||
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
|
||||
|
||||
aws4@^1.2.1, aws4@^1.6.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.7.0.tgz#d4d0e9b9dbfca77bf08eeb0a8a471550fe39e289"
|
||||
|
||||
axios@^0.18.0:
|
||||
version "0.18.0"
|
||||
|
@ -423,9 +423,9 @@ boom@5.x.x:
|
|||
dependencies:
|
||||
hoek "4.x.x"
|
||||
|
||||
bootstrap@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.0.0.tgz#ceb03842c145fcc1b9b4e15da2a05656ba68469a"
|
||||
bootstrap@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.1.0.tgz#110b05c31a236d56dbc9adcda6dd16f53738a28a"
|
||||
|
||||
boxen@^1.2.1:
|
||||
version "1.3.0"
|
||||
|
@ -455,16 +455,14 @@ braces@^1.8.2:
|
|||
repeat-element "^1.1.2"
|
||||
|
||||
braces@^2.3.0, braces@^2.3.1:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.1.tgz#7086c913b4e5a08dbe37ac0ee6a2500c4ba691bb"
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729"
|
||||
dependencies:
|
||||
arr-flatten "^1.1.0"
|
||||
array-unique "^0.3.2"
|
||||
define-property "^1.0.0"
|
||||
extend-shallow "^2.0.1"
|
||||
fill-range "^4.0.0"
|
||||
isobject "^3.0.1"
|
||||
kind-of "^6.0.2"
|
||||
repeat-element "^1.1.2"
|
||||
snapdragon "^0.8.1"
|
||||
snapdragon-node "^2.0.1"
|
||||
|
@ -476,8 +474,8 @@ brorand@^1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
|
||||
|
||||
browserify-aes@^1.0.0, browserify-aes@^1.0.4:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.1.1.tgz#38b7ab55edb806ff2dcda1a7f1620773a477c49f"
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48"
|
||||
dependencies:
|
||||
buffer-xor "^1.0.3"
|
||||
cipher-base "^1.0.0"
|
||||
|
@ -487,16 +485,16 @@ browserify-aes@^1.0.0, browserify-aes@^1.0.4:
|
|||
safe-buffer "^5.0.1"
|
||||
|
||||
browserify-cipher@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.0.tgz#9988244874bf5ed4e28da95666dcd66ac8fc363a"
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0"
|
||||
dependencies:
|
||||
browserify-aes "^1.0.4"
|
||||
browserify-des "^1.0.0"
|
||||
evp_bytestokey "^1.0.0"
|
||||
|
||||
browserify-des@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.0.tgz#daa277717470922ed2fe18594118a175439721dd"
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.1.tgz#3343124db6d7ad53e26a8826318712bdc8450f9c"
|
||||
dependencies:
|
||||
cipher-base "^1.0.1"
|
||||
des.js "^1.0.0"
|
||||
|
@ -694,8 +692,8 @@ caniuse-api@^1.5.2:
|
|||
lodash.uniq "^4.5.0"
|
||||
|
||||
caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639:
|
||||
version "1.0.30000821"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000821.tgz#3fcdc67c446a94a9cdd848248a4e3e54b2da7419"
|
||||
version "1.0.30000830"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000830.tgz#6e45255b345649fd15ff59072da1e12bb3de2f13"
|
||||
|
||||
capture-stack-trace@^1.0.0:
|
||||
version "1.0.0"
|
||||
|
@ -765,8 +763,8 @@ chownr@^1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.0.1.tgz#e2a75042a9551908bebd25b8523d5f9769d79181"
|
||||
|
||||
chrome-trace-event@^0.1.1:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-0.1.2.tgz#90f36885d5345a50621332f0717b595883d5d982"
|
||||
version "0.1.3"
|
||||
resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-0.1.3.tgz#d395af2d31c87b90a716c831fe326f69768ec084"
|
||||
|
||||
chromium-pickle-js@^0.2.0:
|
||||
version "0.2.0"
|
||||
|
@ -1002,8 +1000,8 @@ cosmiconfig@^2.1.0, cosmiconfig@^2.1.1:
|
|||
require-from-string "^1.1.0"
|
||||
|
||||
create-ecdh@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.0.tgz#888c723596cdf7612f6498233eebd7a35301737d"
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.1.tgz#44223dfed533193ba5ba54e0df5709b89acf1f82"
|
||||
dependencies:
|
||||
bn.js "^4.1.0"
|
||||
elliptic "^6.0.0"
|
||||
|
@ -1015,17 +1013,18 @@ create-error-class@^3.0.0:
|
|||
capture-stack-trace "^1.0.0"
|
||||
|
||||
create-hash@^1.1.0, create-hash@^1.1.2:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.1.3.tgz#606042ac8b9262750f483caddab0f5819172d8fd"
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196"
|
||||
dependencies:
|
||||
cipher-base "^1.0.1"
|
||||
inherits "^2.0.1"
|
||||
ripemd160 "^2.0.0"
|
||||
md5.js "^1.3.4"
|
||||
ripemd160 "^2.0.1"
|
||||
sha.js "^2.4.0"
|
||||
|
||||
create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4:
|
||||
version "1.1.6"
|
||||
resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.6.tgz#acb9e221a4e17bdb076e90657c42b93e3726cf06"
|
||||
version "1.1.7"
|
||||
resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff"
|
||||
dependencies:
|
||||
cipher-base "^1.0.3"
|
||||
create-hash "^1.1.0"
|
||||
|
@ -1265,8 +1264,8 @@ diff@^3.2.0:
|
|||
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
|
||||
|
||||
diffie-hellman@^5.0.0:
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.2.tgz#b5835739270cfe26acf632099fded2a07f209e5e"
|
||||
version "5.0.3"
|
||||
resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875"
|
||||
dependencies:
|
||||
bn.js "^4.1.0"
|
||||
miller-rabin "^4.0.0"
|
||||
|
@ -1475,8 +1474,8 @@ electron-publish@20.8.1:
|
|||
mime "^2.2.0"
|
||||
|
||||
electron-to-chromium@^1.2.7:
|
||||
version "1.3.41"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.41.tgz#7e33643e00cd85edfd17e04194f6d00e73737235"
|
||||
version "1.3.42"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.42.tgz#95c33bf01d0cc405556aec899fe61fd4d76ea0f9"
|
||||
|
||||
electron-updater@^2.21.4:
|
||||
version "2.21.4"
|
||||
|
@ -2232,8 +2231,10 @@ https-browserify@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
|
||||
|
||||
iconv-lite@^0.4.19:
|
||||
version "0.4.19"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
|
||||
version "0.4.21"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.21.tgz#c47f8733d02171189ebc4a400f3218d348094798"
|
||||
dependencies:
|
||||
safer-buffer "^2.1.0"
|
||||
|
||||
icss-replace-symbols@^1.1.0:
|
||||
version "1.1.0"
|
||||
|
@ -2930,8 +2931,8 @@ mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.7:
|
|||
mime-db "~1.33.0"
|
||||
|
||||
mime@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-2.2.0.tgz#161e541965551d3b549fa1114391e3a3d55b923b"
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-2.3.1.tgz#b1621c54d63b97c47d3cfe7f7215f7d64517c369"
|
||||
|
||||
mimic-fn@^1.0.0:
|
||||
version "1.2.0"
|
||||
|
@ -2942,8 +2943,8 @@ mimic-response@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.0.tgz#df3d3652a73fded6b9b0b24146e6fd052353458e"
|
||||
|
||||
minimalistic-assert@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz#702be2dda6b37f4836bcb3f5db56641b64a1d3d3"
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
|
||||
|
||||
minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1:
|
||||
version "1.0.1"
|
||||
|
@ -3045,8 +3046,8 @@ nanomatch@^1.2.9:
|
|||
to-regex "^3.0.1"
|
||||
|
||||
neo-async@^2.5.0:
|
||||
version "2.5.0"
|
||||
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.5.0.tgz#76b1c823130cca26acfbaccc8fbaf0a2fa33b18f"
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.5.1.tgz#acb909e327b1e87ec9ef15f41b8a269512ad41ee"
|
||||
|
||||
node-abi@^2.2.0:
|
||||
version "2.3.0"
|
||||
|
@ -3347,8 +3348,8 @@ parallel-transform@^1.1.0:
|
|||
readable-stream "^2.1.5"
|
||||
|
||||
parse-asn1@^5.0.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.0.tgz#37c4f9b7ed3ab65c74817b5f2480937fbf97c712"
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.1.tgz#f6bf293818332bd0dab54efb16087724745e6ca8"
|
||||
dependencies:
|
||||
asn1.js "^4.0.0"
|
||||
browserify-aes "^1.0.0"
|
||||
|
@ -3765,8 +3766,8 @@ postcss@^6.0.1, postcss@^6.0.8:
|
|||
supports-color "^5.3.0"
|
||||
|
||||
prebuild-install@^2.4.1:
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-2.5.1.tgz#0f234140a73760813657c413cdccdda58296b1da"
|
||||
version "2.5.3"
|
||||
resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-2.5.3.tgz#9f65f242782d370296353710e9bc843490c19f69"
|
||||
dependencies:
|
||||
detect-libc "^1.0.3"
|
||||
expand-template "^1.0.2"
|
||||
|
@ -3793,8 +3794,8 @@ preserve@^0.2.0:
|
|||
resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
|
||||
|
||||
prettier@^1.7.0:
|
||||
version "1.11.1"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.11.1.tgz#61e43fc4cd44e68f2b0dfc2c38cd4bb0fccdcc75"
|
||||
version "1.12.0"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.12.0.tgz#d26fc5894b9230de97629b39cae225b503724ce8"
|
||||
|
||||
pretty-bytes@^1.0.2:
|
||||
version "1.0.4"
|
||||
|
@ -3831,8 +3832,8 @@ pseudomap@^1.0.2:
|
|||
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
|
||||
|
||||
public-encrypt@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.0.tgz#39f699f3a46560dd5ebacbca693caf7c65c18cc6"
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.2.tgz#46eb9107206bf73489f8b85b69d91334c6610994"
|
||||
dependencies:
|
||||
bn.js "^4.1.0"
|
||||
browserify-rsa "^4.0.0"
|
||||
|
@ -3925,9 +3926,9 @@ randomfill@^1.0.3:
|
|||
randombytes "^2.0.5"
|
||||
safe-buffer "^5.1.0"
|
||||
|
||||
raven-js@^3.24.0:
|
||||
version "3.24.0"
|
||||
resolved "https://registry.yarnpkg.com/raven-js/-/raven-js-3.24.0.tgz#59464d8bc4b3812ae87a282e9bb98ecad5b4b047"
|
||||
raven-js@^3.24.1:
|
||||
version "3.24.1"
|
||||
resolved "https://registry.yarnpkg.com/raven-js/-/raven-js-3.24.1.tgz#7327e08786248517eedd36205bf50f1047821ffa"
|
||||
|
||||
rc@^1.0.1, rc@^1.1.2, rc@^1.1.6, rc@^1.1.7, rc@^1.2.1:
|
||||
version "1.2.6"
|
||||
|
@ -3968,15 +3969,15 @@ read-pkg@^1.0.0:
|
|||
path-type "^1.0.0"
|
||||
|
||||
"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5:
|
||||
version "2.3.5"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.5.tgz#b4f85003a938cbb6ecbce2a124fb1012bd1a838d"
|
||||
version "2.3.6"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
|
||||
dependencies:
|
||||
core-util-is "~1.0.0"
|
||||
inherits "~2.0.3"
|
||||
isarray "~1.0.0"
|
||||
process-nextick-args "~2.0.0"
|
||||
safe-buffer "~5.1.1"
|
||||
string_decoder "~1.0.3"
|
||||
string_decoder "~1.1.1"
|
||||
util-deprecate "~1.0.1"
|
||||
|
||||
readable-stream@~1.1.9:
|
||||
|
@ -4184,8 +4185,8 @@ resolve-url@^0.2.1:
|
|||
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
|
||||
|
||||
resolve@^1.3.2, resolve@^1.4.0, resolve@^1.5.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.6.0.tgz#0fbd21278b27b4004481c395349e7aba60a9ff5c"
|
||||
version "1.7.1"
|
||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.7.1.tgz#aadd656374fd298aee895bc026b8297418677fd3"
|
||||
dependencies:
|
||||
path-parse "^1.0.5"
|
||||
|
||||
|
@ -4222,6 +4223,10 @@ safe-regex@^1.1.0:
|
|||
dependencies:
|
||||
ret "~0.1.10"
|
||||
|
||||
safer-buffer@^2.1.0:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||
|
||||
sanitize-filename@^1.6.1:
|
||||
version "1.6.1"
|
||||
resolved "https://registry.yarnpkg.com/sanitize-filename/-/sanitize-filename-1.6.1.tgz#612da1c96473fa02dccda92dcd5b4ab164a6772a"
|
||||
|
@ -4237,9 +4242,9 @@ sass-graph@^2.2.4:
|
|||
scss-tokenizer "^0.2.3"
|
||||
yargs "^7.0.0"
|
||||
|
||||
sass-loader@^6.0.7:
|
||||
version "6.0.7"
|
||||
resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-6.0.7.tgz#dd2fdb3e7eeff4a53f35ba6ac408715488353d00"
|
||||
sass-loader@^7.0.1:
|
||||
version "7.0.1"
|
||||
resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-7.0.1.tgz#fd937259ccba3a9cfe0d5f8a98746d48adfcc261"
|
||||
dependencies:
|
||||
clone-deep "^2.0.1"
|
||||
loader-utils "^1.0.1"
|
||||
|
@ -4574,9 +4579,9 @@ string-width@^2.0.0, string-width@^2.1.1:
|
|||
is-fullwidth-code-point "^2.0.0"
|
||||
strip-ansi "^4.0.0"
|
||||
|
||||
string_decoder@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.0.tgz#384f322ee8a848e500effde99901bba849c5d403"
|
||||
string_decoder@^1.0.0, string_decoder@~1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
|
||||
dependencies:
|
||||
safe-buffer "~5.1.0"
|
||||
|
||||
|
@ -4584,12 +4589,6 @@ string_decoder@~0.10.x:
|
|||
version "0.10.31"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
|
||||
|
||||
string_decoder@~1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab"
|
||||
dependencies:
|
||||
safe-buffer "~5.1.0"
|
||||
|
||||
stringstream@~0.0.4, stringstream@~0.0.5:
|
||||
version "0.0.5"
|
||||
resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878"
|
||||
|
@ -4626,6 +4625,13 @@ strip-json-comments@~2.0.1:
|
|||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
|
||||
|
||||
style-loader@^0.20.3:
|
||||
version "0.20.3"
|
||||
resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.20.3.tgz#ebef06b89dec491bcb1fdb3452e913a6fd1c10c4"
|
||||
dependencies:
|
||||
loader-utils "^1.1.0"
|
||||
schema-utils "^0.4.5"
|
||||
|
||||
sumchecker@^1.2.0:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-1.3.1.tgz#79bb3b4456dd04f18ebdbc0d703a1d1daec5105d"
|
||||
|
@ -4748,8 +4754,8 @@ timed-out@^4.0.0:
|
|||
resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f"
|
||||
|
||||
timers-browserify@^2.0.4:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.6.tgz#241e76927d9ca05f4d959819022f5b3664b64bae"
|
||||
version "2.0.7"
|
||||
resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.7.tgz#e74093629cb62c20332af587ddc0c86b4ba97a05"
|
||||
dependencies:
|
||||
setimmediate "^1.0.4"
|
||||
|
||||
|
@ -4801,9 +4807,9 @@ truncate-utf8-bytes@^1.0.0:
|
|||
dependencies:
|
||||
utf8-byte-length "^1.0.1"
|
||||
|
||||
ts-loader@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-4.1.0.tgz#6216e75600941df3270bc4a7125e20aefb2dc5ea"
|
||||
ts-loader@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-4.2.0.tgz#c380c399fc81f82cad0e3044f9c1f775ecde6efa"
|
||||
dependencies:
|
||||
chalk "^2.3.0"
|
||||
enhanced-resolve "^4.0.0"
|
||||
|
@ -4833,8 +4839,8 @@ tslint@^5.7.0:
|
|||
tsutils "^2.12.1"
|
||||
|
||||
tsutils@^2.12.1:
|
||||
version "2.24.0"
|
||||
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.24.0.tgz#a84134358e10c42039fed0a8f1548413f118aff7"
|
||||
version "2.26.1"
|
||||
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.26.1.tgz#9e4a0cb9ff173863f34c22a961969081270d1878"
|
||||
dependencies:
|
||||
tslib "^1.8.1"
|
||||
|
||||
|
@ -4949,8 +4955,8 @@ upath@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/upath/-/upath-1.0.4.tgz#ee2321ba0a786c50973db043a50b7bcba822361d"
|
||||
|
||||
update-notifier@^2.4.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.4.0.tgz#f9b4c700fbfd4ec12c811587258777d563d8c866"
|
||||
version "2.5.0"
|
||||
resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.5.0.tgz#d0744593e13f161e406acb1d9408b72cad08aff6"
|
||||
dependencies:
|
||||
boxen "^1.2.1"
|
||||
chalk "^2.0.1"
|
||||
|
@ -5111,9 +5117,9 @@ webpack-sources@^1.0.1, webpack-sources@^1.1.0:
|
|||
source-list-map "^2.0.0"
|
||||
source-map "~0.6.1"
|
||||
|
||||
webpack@^4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.3.0.tgz#0b0c1e211311b3995dd25aed47ab46ea658be070"
|
||||
webpack@^4.5.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.5.0.tgz#1e6f71e148ead02be265ff2879c9cd6bb30b8848"
|
||||
dependencies:
|
||||
acorn "^5.0.0"
|
||||
acorn-dynamic-import "^3.0.0"
|
||||
|
|
Loading…
Reference in New Issue