3.0.9
This commit is contained in:
parent
39f9365299
commit
8810b29552
|
@ -99,7 +99,7 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const selection = document.getSelection();
|
const selection = document.getSelection();
|
||||||
if(selection.isCollapsed) return;
|
if(selection === null || selection.isCollapsed) return;
|
||||||
const range = selection.getRangeAt(0);
|
const range = selection.getRangeAt(0);
|
||||||
let start = range.startContainer, end = range.endContainer;
|
let start = range.startContainer, end = range.endContainer;
|
||||||
let startValue: string;
|
let startValue: string;
|
||||||
|
|
|
@ -187,8 +187,9 @@
|
||||||
}];
|
}];
|
||||||
window.addEventListener('resize', this.resizeHandler = () => this.keepScroll());
|
window.addEventListener('resize', this.resizeHandler = () => this.keepScroll());
|
||||||
window.addEventListener('keypress', this.keypressHandler = () => {
|
window.addEventListener('keypress', this.keypressHandler = () => {
|
||||||
if(document.getSelection().isCollapsed && !anyDialogsShown &&
|
const selection = document.getSelection();
|
||||||
(document.activeElement === document.body || document.activeElement.tagName === 'A'))
|
if((selection === null || selection.isCollapsed) && !anyDialogsShown &&
|
||||||
|
(document.activeElement === document.body || document.activeElement === null || document.activeElement.tagName === 'A'))
|
||||||
(<Editor>this.$refs['textBox']).focus();
|
(<Editor>this.$refs['textBox']).focus();
|
||||||
});
|
});
|
||||||
window.addEventListener('keydown', this.keydownHandler = ((e: KeyboardEvent) => {
|
window.addEventListener('keydown', this.keydownHandler = ((e: KeyboardEvent) => {
|
||||||
|
@ -211,11 +212,9 @@
|
||||||
this.adsMode = l('channel.mode.ads');
|
this.adsMode = l('channel.mode.ads');
|
||||||
} else this.adsMode = l('channel.mode.ads.countdown', Math.floor(diff / 60), Math.floor(diff % 60));
|
} else this.adsMode = l('channel.mode.ads.countdown', Math.floor(diff / 60), Math.floor(diff % 60));
|
||||||
};
|
};
|
||||||
if(Date.now() < value) {
|
if(Date.now() < value && this.adCountdown === 0)
|
||||||
if(this.adCountdown === 0)
|
this.adCountdown = window.setInterval(setAdCountdown, 1000);
|
||||||
this.adCountdown = window.setInterval(setAdCountdown, 1000);
|
setAdCountdown();
|
||||||
setAdCountdown();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -261,11 +260,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
keepScroll(): void {
|
keepScroll(): void {
|
||||||
if(this.scrolledDown)
|
if(this.scrolledDown) {
|
||||||
|
this.ignoreScroll = true;
|
||||||
this.$nextTick(() => setTimeout(() => {
|
this.$nextTick(() => setTimeout(() => {
|
||||||
this.ignoreScroll = true;
|
this.ignoreScroll = true;
|
||||||
this.messageView.scrollTop = this.messageView.scrollHeight;
|
this.messageView.scrollTop = this.messageView.scrollHeight;
|
||||||
}, 0));
|
}, 0));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMessagesScroll(): void {
|
onMessagesScroll(): void {
|
||||||
|
|
|
@ -205,6 +205,7 @@
|
||||||
if(getKey(e) === Keys.KeyA && (e.ctrlKey || e.metaKey) && !e.altKey && !e.shiftKey) {
|
if(getKey(e) === Keys.KeyA && (e.ctrlKey || e.metaKey) && !e.altKey && !e.shiftKey) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const selection = document.getSelection();
|
const selection = document.getSelection();
|
||||||
|
if(selection === null) return;
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
if(this.messages.length > 0) {
|
if(this.messages.length > 0) {
|
||||||
const range = document.createRange();
|
const range = document.createRange();
|
||||||
|
|
|
@ -216,7 +216,7 @@
|
||||||
|
|
||||||
async submit(): Promise<void> {
|
async submit(): Promise<void> {
|
||||||
const idleTimer = parseInt(this.idleTimer, 10);
|
const idleTimer = parseInt(this.idleTimer, 10);
|
||||||
const fontSize = parseInt(this.fontSize, 10);
|
const fontSize = parseFloat(this.fontSize);
|
||||||
core.state.settings = {
|
core.state.settings = {
|
||||||
playSound: this.playSound,
|
playSound: this.playSound,
|
||||||
clickOpensMessage: this.clickOpensMessage,
|
clickOpensMessage: this.clickOpensMessage,
|
||||||
|
|
|
@ -17,6 +17,7 @@ export const BBCodeView: Component = {
|
||||||
insert(node: VNode): void {
|
insert(node: VNode): void {
|
||||||
node.elm!.appendChild(core.bbCodeParser.parseEverything(
|
node.elm!.appendChild(core.bbCodeParser.parseEverything(
|
||||||
context.props.text !== undefined ? context.props.text : context.props.unsafeText));
|
context.props.text !== undefined ? context.props.text : context.props.unsafeText));
|
||||||
|
if(context.props.afterInsert !== undefined) context.props.afterInsert(node.elm);
|
||||||
},
|
},
|
||||||
destroy(node: VNode): void {
|
destroy(node: VNode): void {
|
||||||
const element = (<BBCodeElement>(<Element>node.elm).firstChild);
|
const element = (<BBCodeElement>(<Element>node.elm).firstChild);
|
||||||
|
|
|
@ -613,30 +613,36 @@ export default function(this: void): Interfaces.State {
|
||||||
if(conv !== undefined) conv.typingStatus = data.status;
|
if(conv !== undefined) conv.typingStatus = data.status;
|
||||||
});
|
});
|
||||||
connection.onMessage('CBU', async(data, time) => {
|
connection.onMessage('CBU', async(data, time) => {
|
||||||
const text = l('events.ban', data.channel, data.character, data.operator);
|
|
||||||
const conv = state.channelMap[data.channel.toLowerCase()];
|
const conv = state.channelMap[data.channel.toLowerCase()];
|
||||||
if(conv === undefined) return core.channels.leave(data.channel);
|
if(conv === undefined) return core.channels.leave(data.channel);
|
||||||
|
const text = l('events.ban', conv.name, data.character, data.operator);
|
||||||
conv.infoText = text;
|
conv.infoText = text;
|
||||||
return addEventMessage(new EventMessage(text, time));
|
return addEventMessage(new EventMessage(text, time));
|
||||||
});
|
});
|
||||||
connection.onMessage('CKU', async(data, time) => {
|
connection.onMessage('CKU', async(data, time) => {
|
||||||
const text = l('events.kick', data.channel, data.character, data.operator);
|
|
||||||
const conv = state.channelMap[data.channel.toLowerCase()];
|
const conv = state.channelMap[data.channel.toLowerCase()];
|
||||||
if(conv === undefined) return core.channels.leave(data.channel);
|
if(conv === undefined) return core.channels.leave(data.channel);
|
||||||
|
const text = l('events.kick', conv.name, data.character, data.operator);
|
||||||
conv.infoText = text;
|
conv.infoText = text;
|
||||||
return addEventMessage(new EventMessage(text, time));
|
return addEventMessage(new EventMessage(text, time));
|
||||||
});
|
});
|
||||||
connection.onMessage('CTU', async(data, time) => {
|
connection.onMessage('CTU', async(data, time) => {
|
||||||
const text = l('events.timeout', data.channel, data.character, data.operator, data.length.toString());
|
|
||||||
const conv = state.channelMap[data.channel.toLowerCase()];
|
const conv = state.channelMap[data.channel.toLowerCase()];
|
||||||
if(conv === undefined) return core.channels.leave(data.channel);
|
if(conv === undefined) return core.channels.leave(data.channel);
|
||||||
|
const text = l('events.timeout', conv.name, data.character, data.operator, data.length.toString());
|
||||||
conv.infoText = text;
|
conv.infoText = text;
|
||||||
return addEventMessage(new EventMessage(text, time));
|
return addEventMessage(new EventMessage(text, time));
|
||||||
});
|
});
|
||||||
connection.onMessage('BRO', async(data, time) => {
|
connection.onMessage('BRO', async(data, time) => {
|
||||||
const text = data.character === undefined ? decodeHTML(data.message) :
|
if(data.character !== undefined) {
|
||||||
l('events.broadcast', `[user]${data.character}[/user]`, decodeHTML(data.message.substr(data.character.length + 23)));
|
const content = decodeHTML(data.message.substr(data.character.length + 24));
|
||||||
return addEventMessage(new EventMessage(text, time));
|
const message = new EventMessage(l('events.broadcast', `[user]${data.character}[/user]`, content), time);
|
||||||
|
await state.consoleTab.addMessage(message);
|
||||||
|
await core.notifications.notify(state.consoleTab, l('events.broadcast.notification', data.character), content,
|
||||||
|
characterImage(data.character), 'attention');
|
||||||
|
for(const conv of (<Conversation[]>state.channelConversations).concat(state.privateConversations))
|
||||||
|
await conv.addMessage(message);
|
||||||
|
} else return addEventMessage(new EventMessage(decodeHTML(data.message), time));
|
||||||
});
|
});
|
||||||
connection.onMessage('CIU', async(data, time) => {
|
connection.onMessage('CIU', async(data, time) => {
|
||||||
const text = l('events.invite', `[user]${data.sender}[/user]`, `[session=${data.title}]${data.name}[/session]`);
|
const text = l('events.invite', `[user]${data.sender}[/user]`, `[session=${data.title}]${data.name}[/session]`);
|
||||||
|
|
|
@ -176,6 +176,7 @@ Current log location: {1}`,
|
||||||
'settings.defaultHighlights': 'Use global highlight words',
|
'settings.defaultHighlights': 'Use global highlight words',
|
||||||
'settings.colorBookmarks': 'Show friends/bookmarks in a different colour',
|
'settings.colorBookmarks': 'Show friends/bookmarks in a different colour',
|
||||||
'settings.beta': 'Opt-in to test unstable prerelease updates',
|
'settings.beta': 'Opt-in to test unstable prerelease updates',
|
||||||
|
'settings.hwAcceleration': 'Enable hardware acceleration (requires restart)',
|
||||||
'settings.bbCodeBar': 'Show BBCode formatting bar',
|
'settings.bbCodeBar': 'Show BBCode formatting bar',
|
||||||
'fixLogs.action': 'Fix corrupted logs',
|
'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.
|
'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.
|
||||||
|
@ -221,7 +222,8 @@ Once this process has started, do not interrupt it or your logs will get corrupt
|
||||||
'characterSearch.error.noResults': 'There were no search results.',
|
'characterSearch.error.noResults': 'There were no search results.',
|
||||||
'characterSearch.error.throttle': 'You must wait five seconds between searches.',
|
'characterSearch.error.throttle': 'You must wait five seconds between searches.',
|
||||||
'characterSearch.error.tooManyResults': 'There are too many search results, please narrow your search.',
|
'characterSearch.error.tooManyResults': 'There are too many search results, please narrow your search.',
|
||||||
'events.broadcast': '{0} has broadcast {1}',
|
'events.broadcast': '{0} has broadcast: {1}',
|
||||||
|
'events.broadcast.notification': 'Broadcast from {0}',
|
||||||
'events.invite': '{0} has invited you to join {1}',
|
'events.invite': '{0} has invited you to join {1}',
|
||||||
'events.error': 'Error: {0}',
|
'events.error': 'Error: {0}',
|
||||||
'events.rtbCommentReply': '{0} replied to your comment on the {1}: {2}',
|
'events.rtbCommentReply': '{0} replied to your comment on the {1}: {2}',
|
||||||
|
|
|
@ -37,7 +37,18 @@ const MessageView: Component = {
|
||||||
userPostfix[message.type] !== undefined ? userPostfix[message.type]! : ' ');
|
userPostfix[message.type] !== undefined ? userPostfix[message.type]! : ' ');
|
||||||
if(message.isHighlight) classes += ' message-highlight';
|
if(message.isHighlight) classes += ' message-highlight';
|
||||||
}
|
}
|
||||||
children.push(createElement(BBCodeView, {props: {unsafeText: message.text}}));
|
children.push(createElement(BBCodeView,
|
||||||
|
{props: {unsafeText: message.text, afterInsert: message.type === Conversation.Message.Type.Ad ? (elm: HTMLElement) => {
|
||||||
|
setImmediate(() => {
|
||||||
|
elm = elm.parentElement!;
|
||||||
|
if(elm.scrollHeight > elm.offsetHeight) {
|
||||||
|
const expand = document.createElement('div');
|
||||||
|
expand.className = 'expand fas fa-caret-down';
|
||||||
|
expand.addEventListener('click', function(): void { this.parentElement!.className += ' expanded'; });
|
||||||
|
elm.appendChild(expand);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} : undefined}}));
|
||||||
const node = createElement('div', {attrs: {class: classes}}, children);
|
const node = createElement('div', {attrs: {class: classes}}, children);
|
||||||
node.key = context.data.key;
|
node.key = context.data.key;
|
||||||
return node;
|
return node;
|
||||||
|
|
|
@ -14,7 +14,8 @@ export default class Notifications implements Interface {
|
||||||
async notify(conversation: Conversation, title: string, body: string, icon: string, sound: string): Promise<void> {
|
async notify(conversation: Conversation, title: string, body: string, icon: string, sound: string): Promise<void> {
|
||||||
if(!this.shouldNotify(conversation)) return;
|
if(!this.shouldNotify(conversation)) return;
|
||||||
this.playSound(sound);
|
this.playSound(sound);
|
||||||
if(core.state.settings.notifications && (<any>Notification).permission === 'granted') { //tslint:disable-line:no-any
|
if(core.state.settings.notifications && (<{Notification?: object}>window).Notification !== undefined
|
||||||
|
&& Notification.permission === 'granted') {
|
||||||
const notification = new Notification(title, this.getOptions(conversation, body, icon));
|
const notification = new Notification(title, this.getOptions(conversation, body, icon));
|
||||||
notification.onclick = () => {
|
notification.onclick = () => {
|
||||||
conversation.show();
|
conversation.show();
|
||||||
|
@ -69,6 +70,6 @@ export default class Notifications implements Interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
async requestPermission(): Promise<void> {
|
async requestPermission(): Promise<void> {
|
||||||
await Notification.requestPermission();
|
if((<{Notification?: object}>window).Notification !== undefined) await Notification.requestPermission();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,18 +1,18 @@
|
||||||
<template>
|
<template>
|
||||||
<div style="display:flex;flex-direction:column;height:100%;padding:1px" :class="'platform-' + platform" @auxclick.prevent>
|
<div style="display:flex;flex-direction:column;height:100%" :class="'platform-' + platform" @auxclick.prevent>
|
||||||
<div v-html="styling"></div>
|
<div v-html="styling"></div>
|
||||||
<div style="display:flex;align-items:stretch;" class="border-bottom" id="window-tabs">
|
<div style="display:flex;align-items:stretch;border-bottom-width:1px" class="border-bottom" id="window-tabs">
|
||||||
<h4>F-Chat</h4>
|
<h4 style="padding:2px 0">F-Chat</h4>
|
||||||
<div class="btn" :class="'btn-' + (hasUpdate ? 'warning' : 'light')" @click="openMenu" id="settings">
|
<div class="btn" :class="'btn-' + (hasUpdate ? 'warning' : 'light')" @click="openMenu" id="settings">
|
||||||
<i class="fa fa-cog"></i>
|
<i class="fa fa-cog"></i>
|
||||||
</div>
|
</div>
|
||||||
<ul class="nav nav-tabs" style="border-bottom:0;margin-bottom:-2px" ref="tabs">
|
<ul class="nav nav-tabs" style="border-bottom:0;margin-bottom:-1px;margin-top:1px" ref="tabs">
|
||||||
<li v-for="tab in tabs" :key="tab.view.id" class="nav-item" @click.middle="remove(tab)">
|
<li v-for="tab in tabs" :key="tab.view.id" class="nav-item" @click.middle="remove(tab)">
|
||||||
<a href="#" @click.prevent="show(tab)" class="nav-link"
|
<a href="#" @click.prevent="show(tab)" class="nav-link tab"
|
||||||
:class="{active: tab === activeTab, hasNew: tab.hasNew && tab !== activeTab}">
|
:class="{active: tab === activeTab, hasNew: tab.hasNew && tab !== activeTab}">
|
||||||
<img v-if="tab.user" :src="'https://static.f-list.net/images/avatar/' + tab.user.toLowerCase() + '.png'"/>
|
<img v-if="tab.user" :src="'https://static.f-list.net/images/avatar/' + tab.user.toLowerCase() + '.png'"/>
|
||||||
<span class="d-sm-inline d-none">{{tab.user || l('window.newTab')}}</span>
|
<span class="d-sm-inline d-none">{{tab.user || l('window.newTab')}}</span>
|
||||||
<a href="#" class="btn" :aria-label="l('action.close')" style="margin-left:10px;padding:0;color:inherit"
|
<a href="#" :aria-label="l('action.close')" style="margin-left:10px;padding:0;color:inherit;text-decoration:none"
|
||||||
@click.stop="remove(tab)"><i class="fa fa-times"></i>
|
@click.stop="remove(tab)"><i class="fa fa-times"></i>
|
||||||
</a>
|
</a>
|
||||||
</a>
|
</a>
|
||||||
|
@ -21,7 +21,7 @@
|
||||||
<a href="#" @click.prevent="addTab" class="nav-link"><i class="fa fa-plus"></i></a>
|
<a href="#" @click.prevent="addTab" class="nav-link"><i class="fa fa-plus"></i></a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div style="flex:1;display:flex;justify-content:flex-end;-webkit-app-region:drag;margin-top:3px" class="btn-group"
|
<div style="flex:1;display:flex;justify-content:flex-end;-webkit-app-region:drag" class="btn-group"
|
||||||
id="windowButtons">
|
id="windowButtons">
|
||||||
<i class="far fa-window-minimize btn btn-light" @click.stop="minimize"></i>
|
<i class="far fa-window-minimize btn btn-light" @click.stop="minimize"></i>
|
||||||
<i class="far btn btn-light" :class="'fa-window-' + (isMaximized ? 'restore' : 'maximize')" @click="maximize"></i>
|
<i class="far btn btn-light" :class="'fa-window-' + (isMaximized ? 'restore' : 'maximize')" @click="maximize"></i>
|
||||||
|
@ -225,7 +225,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
remove(tab: Tab, shouldConfirm: boolean = true): void {
|
remove(tab: Tab, shouldConfirm: boolean = true): void {
|
||||||
if(shouldConfirm && tab.user !== undefined && !confirm(l('chat.confirmLeave'))) return;
|
if(this.lockTab || shouldConfirm && tab.user !== undefined && !confirm(l('chat.confirmLeave'))) return;
|
||||||
this.tabs.splice(this.tabs.indexOf(tab), 1);
|
this.tabs.splice(this.tabs.indexOf(tab), 1);
|
||||||
electron.ipcRenderer.send('has-new', this.tabs.reduce((cur, t) => cur || t.hasNew, false));
|
electron.ipcRenderer.send('has-new', this.tabs.reduce((cur, t) => cur || t.hasNew, false));
|
||||||
delete this.tabMap[tab.view.webContents.id];
|
delete this.tabMap[tab.view.webContents.id];
|
||||||
|
@ -259,11 +259,12 @@
|
||||||
#window-tabs {
|
#window-tabs {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
.btn {
|
.btn {
|
||||||
|
border: 0;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
padding: 2px 15px;
|
padding: 0 18px;
|
||||||
display: flex;
|
display: flex;
|
||||||
margin: 0px -1px -1px 0;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
line-height: 1;
|
||||||
-webkit-app-region: no-drag;
|
-webkit-app-region: no-drag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -287,10 +288,6 @@
|
||||||
height: 28px;
|
height: 28px;
|
||||||
margin: -5px 3px -5px -5px;
|
margin: -5px 3px -5px -5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active {
|
|
||||||
margin-bottom: -2px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h4 {
|
h4 {
|
||||||
|
@ -307,8 +304,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
#windowButtons .btn {
|
#windowButtons .btn {
|
||||||
margin: -4px -1px -1px 0;
|
|
||||||
border-top: 0;
|
border-top: 0;
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.platform-darwin {
|
.platform-darwin {
|
||||||
|
@ -322,8 +319,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn, li a {
|
.btn, li a {
|
||||||
padding-top: 5px;
|
padding-top: 6px;
|
||||||
padding-bottom: 5px;
|
padding-bottom: 6px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ export class GeneralSettings {
|
||||||
version = electron.app.getVersion();
|
version = electron.app.getVersion();
|
||||||
beta = false;
|
beta = false;
|
||||||
customDictionary: string[] = [];
|
customDictionary: string[] = [];
|
||||||
|
hwAcceleration = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mkdir(dir: string): void {
|
export function mkdir(dir: string): void {
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline'; img-src file: data: https://static.f-list.net; connect-src *">
|
||||||
<title>F-Chat</title>
|
<title>F-Chat</title>
|
||||||
<link href="fa.css" rel="stylesheet">
|
<link href="fa.css" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
|
|
|
@ -59,6 +59,19 @@ mkdir(settingsDir);
|
||||||
const settingsFile = path.join(settingsDir, 'settings');
|
const settingsFile = path.join(settingsDir, 'settings');
|
||||||
const settings = new GeneralSettings();
|
const settings = new GeneralSettings();
|
||||||
|
|
||||||
|
if(!fs.existsSync(settingsFile)) shouldImportSettings = true;
|
||||||
|
else
|
||||||
|
try {
|
||||||
|
Object.assign(settings, <GeneralSettings>JSON.parse(fs.readFileSync(settingsFile, 'utf8')));
|
||||||
|
} catch(e) {
|
||||||
|
log.error(`Error loading settings: ${e}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!settings.hwAcceleration) {
|
||||||
|
log.info('Disabling hardware acceleration.');
|
||||||
|
app.disableHardwareAcceleration();
|
||||||
|
}
|
||||||
|
|
||||||
async function setDictionary(lang: string | undefined): Promise<void> {
|
async function setDictionary(lang: string | undefined): Promise<void> {
|
||||||
if(lang !== undefined) await ensureDictionary(lang);
|
if(lang !== undefined) await ensureDictionary(lang);
|
||||||
settings.spellcheckLang = lang;
|
settings.spellcheckLang = lang;
|
||||||
|
@ -142,14 +155,6 @@ function onReady(): void {
|
||||||
log.transports.file.file = path.join(baseDir, 'log.txt');
|
log.transports.file.file = path.join(baseDir, 'log.txt');
|
||||||
log.info('Starting application.');
|
log.info('Starting application.');
|
||||||
|
|
||||||
if(!fs.existsSync(settingsFile)) shouldImportSettings = true;
|
|
||||||
else
|
|
||||||
try {
|
|
||||||
Object.assign(settings, <GeneralSettings>JSON.parse(fs.readFileSync(settingsFile, 'utf8')));
|
|
||||||
} catch(e) {
|
|
||||||
log.error(`Error loading settings: ${e}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
app.setAppUserModelId('com.squirrel.fchat.F-Chat');
|
app.setAppUserModelId('com.squirrel.fchat.F-Chat');
|
||||||
app.on('open-file', createWindow);
|
app.on('open-file', createWindow);
|
||||||
|
|
||||||
|
@ -271,6 +276,12 @@ function onReady(): void {
|
||||||
label: x,
|
label: x,
|
||||||
type: <'radio'>'radio'
|
type: <'radio'>'radio'
|
||||||
}))
|
}))
|
||||||
|
}, {
|
||||||
|
label: l('settings.hwAcceleration'), type: 'checkbox', checked: settings.hwAcceleration,
|
||||||
|
click: (item: Electron.MenuItem) => {
|
||||||
|
settings.hwAcceleration = item.checked;
|
||||||
|
setGeneralSettings(settings);
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
label: l('settings.beta'), type: 'checkbox', checked: settings.beta,
|
label: l('settings.beta'), type: 'checkbox', checked: settings.beta,
|
||||||
click: async(item: Electron.MenuItem) => {
|
click: async(item: Electron.MenuItem) => {
|
||||||
|
@ -383,6 +394,7 @@ function onReady(): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
const isSquirrelStart = require('electron-squirrel-startup'); //tslint:disable-line:no-require-imports
|
const isSquirrelStart = require('electron-squirrel-startup'); //tslint:disable-line:no-require-imports
|
||||||
if(isSquirrelStart || process.env.NODE_ENV === 'production' && app.makeSingleInstance(createWindow)) app.quit();
|
if(isSquirrelStart || process.env.NODE_ENV === 'production' && !app.requestSingleInstanceLock()) app.quit();
|
||||||
else app.on('ready', onReady);
|
else app.on('ready', onReady);
|
||||||
|
app.on('second-instance', createWindow);
|
||||||
app.on('window-all-closed', () => app.quit());
|
app.on('window-all-closed', () => app.quit());
|
|
@ -1,3 +1,4 @@
|
||||||
|
process.env.DEBUG = 'electron-windows-installer:main';
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const pkg = require(path.join(__dirname, 'package.json'));
|
const pkg = require(path.join(__dirname, 'package.json'));
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
@ -60,7 +61,7 @@ require('electron-packager')({
|
||||||
require('electron-winstaller').createWindowsInstaller({
|
require('electron-winstaller').createWindowsInstaller({
|
||||||
appDirectory: appPaths[0],
|
appDirectory: appPaths[0],
|
||||||
outputDirectory: distDir,
|
outputDirectory: distDir,
|
||||||
iconUrl: icon,
|
iconUrl: 'file:///%localappdata%\\fchat\\app.ico',
|
||||||
setupIcon: icon,
|
setupIcon: icon,
|
||||||
noMsi: true,
|
noMsi: true,
|
||||||
exe: 'F-Chat.exe',
|
exe: 'F-Chat.exe',
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "fchat",
|
"name": "fchat",
|
||||||
"version": "3.0.8",
|
"version": "3.0.9",
|
||||||
"author": "The F-List Team",
|
"author": "The F-List Team",
|
||||||
"description": "F-List.net Chat Client",
|
"description": "F-List.net Chat Client",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const ExtractTextPlugin = require('extract-text-webpack-plugin');
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
|
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
|
||||||
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
|
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
|
||||||
|
@ -109,20 +108,15 @@ const mainConfig = {
|
||||||
module.exports = function(mode) {
|
module.exports = function(mode) {
|
||||||
const themesDir = path.join(__dirname, '../scss/themes/chat');
|
const themesDir = path.join(__dirname, '../scss/themes/chat');
|
||||||
const themes = fs.readdirSync(themesDir);
|
const themes = fs.readdirSync(themesDir);
|
||||||
const cssOptions = {use: ['css-loader', 'sass-loader']};
|
|
||||||
for(const theme of themes) {
|
for(const theme of themes) {
|
||||||
if(!theme.endsWith('.scss')) continue;
|
if(!theme.endsWith('.scss')) continue;
|
||||||
const absPath = path.join(themesDir, theme);
|
const absPath = path.join(themesDir, theme);
|
||||||
rendererConfig.entry.chat.push(absPath);
|
rendererConfig.entry.chat.push(absPath);
|
||||||
const plugin = new ExtractTextPlugin('themes/' + theme.slice(0, -5) + '.css');
|
rendererConfig.module.rules.unshift({test: absPath, loader: ['file-loader?name=themes/[name].css', 'extract-loader', 'css-loader', 'sass-loader']});
|
||||||
rendererConfig.plugins.push(plugin);
|
|
||||||
rendererConfig.module.rules.unshift({test: absPath, use: plugin.extract(cssOptions)});
|
|
||||||
}
|
}
|
||||||
const faPath = path.join(themesDir, '../../fa.scss');
|
const faPath = path.join(themesDir, '../../fa.scss');
|
||||||
rendererConfig.entry.chat.push(faPath);
|
rendererConfig.entry.chat.push(faPath);
|
||||||
const faPlugin = new ExtractTextPlugin('./fa.css');
|
rendererConfig.module.rules.unshift({test: faPath, loader: ['file-loader?name=fa.css', 'extract-loader', 'css-loader', 'sass-loader']});
|
||||||
rendererConfig.plugins.push(faPlugin);
|
|
||||||
rendererConfig.module.rules.unshift({test: faPath, use: faPlugin.extract(cssOptions)});
|
|
||||||
if(mode === 'production') {
|
if(mode === 'production') {
|
||||||
process.env.NODE_ENV = 'production';
|
process.env.NODE_ENV = 'production';
|
||||||
mainConfig.devtool = rendererConfig.devtool = 'source-map';
|
mainConfig.devtool = rendererConfig.devtool = 'source-map';
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline'; img-src https://static.f-list.net">
|
||||||
<title>F-Chat</title>
|
<title>F-Chat</title>
|
||||||
<link href="fa.css" rel="stylesheet">
|
<link href="fa.css" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div id="page" style="position: relative; padding: 10px;" v-if="settings">
|
<div id="page" style="position: relative; padding: 10px;" v-if="settings">
|
||||||
<div v-html="styling"></div>
|
<div v-html="styling"></div>
|
||||||
<div v-if="!characters" style="display:flex; align-items:center; justify-content:center; height: 100%;">
|
<div v-if="!characters" style="display:flex; align-items:center; justify-content:center; min-height: 100%;">
|
||||||
<div class="card bg-light" style="width: 400px;">
|
<div class="card bg-light" style="width: 400px;">
|
||||||
<h3 class="card-header" style="margin-top:0">{{l('title')}}</h3>
|
<h3 class="card-header" style="margin-top:0">{{l('title')}}</h3>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
|
|
@ -8,8 +8,8 @@ android {
|
||||||
applicationId "net.f_list.fchat"
|
applicationId "net.f_list.fchat"
|
||||||
minSdkVersion 19
|
minSdkVersion 19
|
||||||
targetSdkVersion 27
|
targetSdkVersion 27
|
||||||
versionCode 19
|
versionCode 20
|
||||||
versionName "3.0.8"
|
versionName "3.0.9"
|
||||||
}
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
|
|
|
@ -11,19 +11,27 @@ import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
|
import android.os.Handler
|
||||||
|
import android.view.KeyEvent
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.webkit.JsResult
|
import android.webkit.JsResult
|
||||||
import android.webkit.WebChromeClient
|
import android.webkit.WebChromeClient
|
||||||
import android.webkit.WebView
|
import android.webkit.WebView
|
||||||
import android.webkit.WebViewClient
|
import android.webkit.WebViewClient
|
||||||
|
import android.widget.EditText
|
||||||
|
import java.io.FileInputStream
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import java.net.URLDecoder
|
import java.net.URLDecoder
|
||||||
|
import java.util.zip.ZipEntry
|
||||||
|
import java.util.zip.ZipOutputStream
|
||||||
|
|
||||||
|
|
||||||
class MainActivity : Activity() {
|
class MainActivity : Activity() {
|
||||||
private lateinit var webView: WebView
|
private lateinit var webView: WebView
|
||||||
private val profileRegex = Regex("^https?://(www\\.)?f-list.net/c/([^/#]+)/?#?")
|
private val profileRegex = Regex("^https?://(www\\.)?f-list.net/c/([^/#]+)/?#?")
|
||||||
private val backgroundPlugin = Background(this)
|
private val backgroundPlugin = Background(this)
|
||||||
|
private var debugPressed = 0
|
||||||
|
private val debugHandler = Handler()
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
@ -91,6 +99,50 @@ class MainActivity : Activity() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun addFolder(folder: java.io.File, out: ZipOutputStream, path: String) {
|
||||||
|
for(file in folder.listFiles()) {
|
||||||
|
if(file.isDirectory) addFolder(file, out, "$path${file.name}/")
|
||||||
|
else {
|
||||||
|
out.putNextEntry(ZipEntry("$path${file.name}"))
|
||||||
|
FileInputStream(file).use { it.copyTo(out) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val debug = Runnable {
|
||||||
|
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
val permission = checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||||
|
if(permission != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
return@Runnable requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val view = EditText(this)
|
||||||
|
view.hint = "Enter character name"
|
||||||
|
AlertDialog.Builder(this).setView(view).setPositiveButton("OK", { _, _ ->
|
||||||
|
val file = java.io.File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "test.zip")
|
||||||
|
val dest = FileOutputStream(file)
|
||||||
|
val out = ZipOutputStream(dest)
|
||||||
|
addFolder(java.io.File(filesDir, view.text.toString()), out, "")
|
||||||
|
out.close()
|
||||||
|
val downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||||
|
downloadManager.addCompletedDownload(file.name, file.name, false, "text/plain", file.absolutePath, file.length(), true)
|
||||||
|
}).setNegativeButton("Cancel", { dialog, _ -> dialog.dismiss() }).setTitle("DEBUG").show()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
|
||||||
|
if(keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) debugPressed = debugPressed or 1
|
||||||
|
else if(keyCode == KeyEvent.KEYCODE_VOLUME_UP) debugPressed = debugPressed or 2
|
||||||
|
if(debugPressed == 3) debugHandler.postDelayed(debug, 5000)
|
||||||
|
return super.onKeyDown(keyCode, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
|
||||||
|
if(keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) debugPressed = debugPressed xor 1
|
||||||
|
else if(keyCode == KeyEvent.KEYCODE_VOLUME_UP) debugPressed = debugPressed xor 2
|
||||||
|
debugHandler.removeCallbacks(debug)
|
||||||
|
return super.onKeyUp(keyCode, event)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onBackPressed() {
|
override fun onBackPressed() {
|
||||||
webView.evaluateJavascript("var e=new Event('backbutton',{cancelable:true});document.dispatchEvent(e);e.defaultPrevented", {
|
webView.evaluateJavascript("var e=new Event('backbutton',{cancelable:true});document.dispatchEvent(e);e.defaultPrevented", {
|
||||||
if(it != "true") super.onBackPressed()
|
if(it != "true") super.onBackPressed()
|
||||||
|
|
|
@ -53,9 +53,13 @@ class Notifications(private val ctx: Context) {
|
||||||
.setContentIntent(PendingIntent.getActivity(ctx, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT)).setDefaults(Notification.DEFAULT_VIBRATE or Notification.DEFAULT_LIGHTS)
|
.setContentIntent(PendingIntent.getActivity(ctx, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT)).setDefaults(Notification.DEFAULT_VIBRATE or Notification.DEFAULT_LIGHTS)
|
||||||
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) notification.setChannelId("messages")
|
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) notification.setChannelId("messages")
|
||||||
object : AsyncTask<String, Void, Bitmap>() {
|
object : AsyncTask<String, Void, Bitmap>() {
|
||||||
override fun doInBackground(vararg args: String): Bitmap {
|
override fun doInBackground(vararg args: String): Bitmap? {
|
||||||
val connection = URL(args[0]).openConnection()
|
return try {
|
||||||
return BitmapFactory.decodeStream(connection.getInputStream())
|
val connection = URL(args[0]).openConnection()
|
||||||
|
BitmapFactory.decodeStream(connection.getInputStream())
|
||||||
|
} catch(e: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPostExecute(result: Bitmap?) {
|
override fun onPostExecute(result: Bitmap?) {
|
||||||
|
|
|
@ -21,12 +21,15 @@ class ViewController: UIViewController, WKNavigationDelegate, WKUIDelegate {
|
||||||
config.userContentController = controller
|
config.userContentController = controller
|
||||||
config.mediaTypesRequiringUserActionForPlayback = [.video]
|
config.mediaTypesRequiringUserActionForPlayback = [.video]
|
||||||
config.setValue(true, forKey: "_alwaysRunsAtForegroundPriority")
|
config.setValue(true, forKey: "_alwaysRunsAtForegroundPriority")
|
||||||
webView = WKWebView(frame: .zero, configuration: config)
|
webView = WKWebView(frame: UIApplication.shared.windows[0].frame, configuration: config)
|
||||||
webView.uiDelegate = self
|
webView.uiDelegate = self
|
||||||
webView.navigationDelegate = self
|
webView.navigationDelegate = self
|
||||||
view = webView
|
view = webView
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardDidShow), name: NSNotification.Name.UIKeyboardDidShow, object: nil)
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
|
||||||
|
webView.scrollView.contentInsetAdjustmentBehavior = .never
|
||||||
|
webView.scrollView.bounces = false
|
||||||
UIApplication.shared.statusBarStyle = .lightContent
|
UIApplication.shared.statusBarStyle = .lightContent
|
||||||
(UIApplication.shared.value(forKey: "statusBar") as! UIView).backgroundColor = UIColor(white: 0, alpha: 0.5)
|
(UIApplication.shared.value(forKey: "statusBar") as! UIView).backgroundColor = UIColor(white: 0, alpha: 0.5)
|
||||||
}
|
}
|
||||||
|
@ -36,30 +39,25 @@ class ViewController: UIViewController, WKNavigationDelegate, WKUIDelegate {
|
||||||
let htmlPath = Bundle.main.path(forResource: "www/index", ofType: "html")
|
let htmlPath = Bundle.main.path(forResource: "www/index", ofType: "html")
|
||||||
let url = URL(fileURLWithPath: htmlPath!, isDirectory: false)
|
let url = URL(fileURLWithPath: htmlPath!, isDirectory: false)
|
||||||
webView.loadFileURL(url, allowingReadAccessTo: url)
|
webView.loadFileURL(url, allowingReadAccessTo: url)
|
||||||
webView.scrollView.isScrollEnabled = false
|
|
||||||
}
|
|
||||||
|
|
||||||
override func didReceiveMemoryWarning() {
|
|
||||||
super.didReceiveMemoryWarning()
|
|
||||||
// Dispose of any resources that can be recreated.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func keyboardWillShow(notification: NSNotification) {
|
@objc func keyboardWillShow(notification: NSNotification) {
|
||||||
let info = notification.userInfo!
|
let info = notification.userInfo!
|
||||||
let frame = webView.frame
|
|
||||||
let newHeight = view.window!.frame.height - (info[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue.height
|
let newHeight = view.window!.frame.height - (info[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue.height
|
||||||
UIView.animate(withDuration: (info[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue, animations: {
|
UIView.animate(withDuration: (info[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue, animations: {
|
||||||
self.webView.scrollView.bounds = CGRect(x: 0, y: 0, width: frame.width, height: newHeight)
|
self.webView.frame = CGRect(x: 0, y: 0, width: self.webView.frame.width, height: newHeight)
|
||||||
}, completion: { (_: Bool) in self.webView.evaluateJavaScript("window.dispatchEvent(new Event('resize'))", completionHandler: nil) })
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func keyboardDidShow(notification: NSNotification) {
|
||||||
|
webView.scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: webView.scrollView.contentInset.bottom - webView.scrollView.adjustedContentInset.bottom, right: 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func keyboardWillHide(notification: NSNotification) {
|
@objc func keyboardWillHide(notification: NSNotification) {
|
||||||
let info = notification.userInfo!
|
let info = notification.userInfo!
|
||||||
let frame = webView.scrollView.bounds
|
|
||||||
let newHeight = frame.height + (info[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue.height
|
|
||||||
UIView.animate(withDuration: (info[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue, animations: {
|
UIView.animate(withDuration: (info[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue, animations: {
|
||||||
self.webView.scrollView.bounds = CGRect(x: 0, y: 0, width: frame.width, height: newHeight)
|
self.webView.frame = UIApplication.shared.windows[0].frame
|
||||||
}, completion: { (_: Bool) in self.webView.evaluateJavaScript("window.dispatchEvent(new Event('resize'))", completionHandler: nil) })
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo,
|
func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo,
|
||||||
|
@ -101,4 +99,3 @@ class ViewController: UIViewController, WKNavigationDelegate, WKUIDelegate {
|
||||||
UIApplication.shared.open(navigationAction.request.url!)
|
UIApplication.shared.open(navigationAction.request.url!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "net.f_list.fchat",
|
"name": "net.f_list.fchat",
|
||||||
"version": "3.0.8",
|
"version": "3.0.9",
|
||||||
"displayName": "F-Chat",
|
"displayName": "F-Chat",
|
||||||
"author": "The F-List Team",
|
"author": "The F-List Team",
|
||||||
"description": "F-List.net Chat Client",
|
"description": "F-List.net Chat Client",
|
||||||
|
|
38
package.json
38
package.json
|
@ -7,46 +7,46 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@fortawesome/fontawesome-free-webfonts": "^1.0.6",
|
"@fortawesome/fontawesome-free-webfonts": "^1.0.6",
|
||||||
"@types/lodash": "^4.14.116",
|
"@types/lodash": "^4.14.116",
|
||||||
"@types/node": "^10.5.6",
|
"@types/node": "^10.11.2",
|
||||||
"@types/sortablejs": "^1.3.31",
|
"@types/sortablejs": "^1.3.31",
|
||||||
"axios": "^0.18.0",
|
"axios": "^0.18.0",
|
||||||
"bootstrap": "^4.1.3",
|
"bootstrap": "^4.1.3",
|
||||||
"css-loader": "^1.0.0",
|
"css-loader": "^1.0.0",
|
||||||
"date-fns": "^1.28.5",
|
"date-fns": "^1.28.5",
|
||||||
"electron": "2.0.2",
|
"electron": "^3.0.2",
|
||||||
"electron-log": "^2.2.16",
|
"electron-log": "^2.2.17",
|
||||||
"electron-packager": "^12.1.0",
|
"electron-packager": "^12.1.2",
|
||||||
"electron-rebuild": "^1.8.2",
|
"electron-rebuild": "^1.8.2",
|
||||||
"extract-text-webpack-plugin": "4.0.0-beta.0",
|
"extract-loader": "^3.0.0",
|
||||||
"file-loader": "^1.1.10",
|
"file-loader": "^2.0.0",
|
||||||
"fork-ts-checker-webpack-plugin": "^0.4.4",
|
"fork-ts-checker-webpack-plugin": "^0.4.9",
|
||||||
"lodash": "^4.16.4",
|
"lodash": "^4.17.11",
|
||||||
"node-sass": "^4.8.3",
|
"node-sass": "^4.9.3",
|
||||||
"optimize-css-assets-webpack-plugin": "^5.0.0",
|
"optimize-css-assets-webpack-plugin": "^5.0.1",
|
||||||
"qs": "^6.5.1",
|
"qs": "^6.5.1",
|
||||||
"raven-js": "^3.26.4",
|
"raven-js": "^3.27.0",
|
||||||
"sass-loader": "^7.1.0",
|
"sass-loader": "^7.1.0",
|
||||||
"sortablejs": "^1.6.0",
|
"sortablejs": "^1.6.0",
|
||||||
"style-loader": "^0.21.0",
|
"style-loader": "^0.23.0",
|
||||||
"ts-loader": "^4.2.0",
|
"ts-loader": "^5.2.1",
|
||||||
"tslib": "^1.7.1",
|
"tslib": "^1.7.1",
|
||||||
"tslint": "^5.7.0",
|
"tslint": "^5.7.0",
|
||||||
"typescript": "^3.0.1",
|
"typescript": "^3.1.1",
|
||||||
"vue": "^2.5.17",
|
"vue": "^2.5.17",
|
||||||
"vue-class-component": "^6.0.0",
|
"vue-class-component": "^6.0.0",
|
||||||
"vue-loader": "^15.2.6",
|
"vue-loader": "^15.4.2",
|
||||||
"vue-property-decorator": "^7.0.0",
|
"vue-property-decorator": "^7.1.1",
|
||||||
"vue-template-compiler": "^2.5.17",
|
"vue-template-compiler": "^2.5.17",
|
||||||
"webpack": "^4.16.4"
|
"webpack": "^4.20.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"keytar": "^4.2.1",
|
"keytar": "^4.2.1",
|
||||||
"spellchecker": "^3.4.3"
|
"spellchecker": "^3.5.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"appdmg": "^0.5.2",
|
"appdmg": "^0.5.2",
|
||||||
"electron-squirrel-startup": "^1.0.0",
|
"electron-squirrel-startup": "^1.0.0",
|
||||||
"electron-winstaller": "^2.6.4"
|
"electron-winstaller": "^2.7.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "electron-rebuild -o spellchecker,keytar"
|
"postinstall": "electron-rebuild -o spellchecker,keytar"
|
||||||
|
|
|
@ -17,7 +17,7 @@ All necessary files to build F-Chat 3.0 as an Electron, mobile or web applicatio
|
||||||
### Packaging
|
### Packaging
|
||||||
See https://electron.atom.io/docs/tutorial/application-distribution/
|
See https://electron.atom.io/docs/tutorial/application-distribution/
|
||||||
- Run `yarn build:dist` to create a minified production build.
|
- Run `yarn build:dist` to create a minified production build.
|
||||||
- Run `yarn pack`. The generated installer is placed into the `dist` directory.
|
- Run `yarn run pack`. The generated installer is placed into the `dist` directory.
|
||||||
- On Windows you can add the path to and password for a code signing certificate as arguments.
|
- On Windows you can add the path to and password for a code signing certificate as arguments.
|
||||||
- On Mac you can add your code signing identity as an argument. `zip` is required to be installed.
|
- On Mac you can add your code signing identity as an argument. `zip` is required to be installed.
|
||||||
- On Linux you can add a GPG key for signing and its password as arguments. `mksquashfs` and `zsyncmake` are required to be installed.
|
- On Linux you can add a GPG key for signing and its password as arguments. `mksquashfs` and `zsyncmake` are required to be installed.
|
||||||
|
|
|
@ -201,6 +201,35 @@
|
||||||
color: $text-dark;
|
color: $text-dark;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message-ad {
|
||||||
|
&:not(.expanded) {
|
||||||
|
max-height: 100px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
> .expand {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: center;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
position: absolute;
|
||||||
|
top: 70px;
|
||||||
|
width: 100%;
|
||||||
|
left: 0;
|
||||||
|
height: 30px;
|
||||||
|
cursor: pointer;
|
||||||
|
background: linear-gradient(rgba($white, 0), $white);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: linear-gradient(rgba($white, 0) 50%, $white);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> .expand {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.message-highlight {
|
.message-highlight {
|
||||||
background-color: theme-color-level("success", -8);
|
background-color: theme-color-level("success", -8);
|
||||||
}
|
}
|
||||||
|
@ -254,12 +283,17 @@ $genders: (
|
||||||
max-width: 98%;
|
max-width: 98%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#window-tabs .hasNew {
|
#window-tabs {
|
||||||
background-color: theme-color-level("warning", -2);
|
.hasNew {
|
||||||
border-color: theme-color-level("warning", -4);
|
background-color: theme-color-level("warning", -2);
|
||||||
color: color-yiq(theme-color("warning"));
|
border-color: theme-color-level("warning", -4);
|
||||||
&:hover {
|
color: color-yiq(theme-color("warning"));
|
||||||
background-color: theme-color-level("warning", -4);
|
&:hover {
|
||||||
|
background-color: theme-color-level("warning", -4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.tab:not(.active):not(:hover) {
|
||||||
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,5 +10,5 @@ $gray-800: #333333 !default;
|
||||||
$gray-900: #191919 !default;
|
$gray-900: #191919 !default;
|
||||||
$secondary: $gray-400;
|
$secondary: $gray-400;
|
||||||
|
|
||||||
$body-bg: #eeeeee;
|
$body-bg: $gray-100;
|
||||||
$text-muted: $gray-500;
|
$text-muted: $gray-500;
|
|
@ -40,8 +40,7 @@ import '../scss/fa.scss'; //tslint:disable-line:no-import-side-effect
|
||||||
import {Logs, SettingsStore} from './logs';
|
import {Logs, SettingsStore} from './logs';
|
||||||
import Notifications from './notifications';
|
import Notifications from './notifications';
|
||||||
|
|
||||||
//@ts-ignore
|
if(typeof (<{Promise?: object}>window).Promise !== 'function') //tslint:disable-line:strict-type-predicates
|
||||||
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.');
|
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
|
const version = (<{version: string}>require('./package.json')).version; //tslint:disable-line:no-require-imports
|
||||||
|
|
|
@ -45,7 +45,7 @@ type Index = {[key: string]: StoredConversation | undefined};
|
||||||
async function openDatabase(character: string): Promise<IDBDatabase> {
|
async function openDatabase(character: string): Promise<IDBDatabase> {
|
||||||
const request = window.indexedDB.open(`logs-${character}`);
|
const request = window.indexedDB.open(`logs-${character}`);
|
||||||
request.onupgradeneeded = () => {
|
request.onupgradeneeded = () => {
|
||||||
const db = <IDBDatabase>request.result;
|
const db = request.result;
|
||||||
const logsStore = db.createObjectStore('logs', {keyPath: 'id', autoIncrement: true});
|
const logsStore = db.createObjectStore('logs', {keyPath: 'id', autoIncrement: true});
|
||||||
logsStore.createIndex('conversation', 'conversation');
|
logsStore.createIndex('conversation', 'conversation');
|
||||||
logsStore.createIndex('conversation-day', hasComposite ? ['conversation', 'day'] : 'day');
|
logsStore.createIndex('conversation-day', hasComposite ? ['conversation', 'day'] : 'day');
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "net.f_list.fchat",
|
"name": "net.f_list.fchat",
|
||||||
"version": "3.0.8",
|
"version": "3.0.9",
|
||||||
"displayName": "F-Chat",
|
"displayName": "F-Chat",
|
||||||
"author": "The F-List Team",
|
"author": "The F-List Team",
|
||||||
"description": "F-List.net Chat Client",
|
"description": "F-List.net Chat Client",
|
||||||
|
|
Loading…
Reference in New Issue