3.0.9
This commit is contained in:
parent
39f9365299
commit
8810b29552
|
@ -99,7 +99,7 @@
|
|||
return;
|
||||
}
|
||||
const selection = document.getSelection();
|
||||
if(selection.isCollapsed) return;
|
||||
if(selection === null || selection.isCollapsed) return;
|
||||
const range = selection.getRangeAt(0);
|
||||
let start = range.startContainer, end = range.endContainer;
|
||||
let startValue: string;
|
||||
|
|
|
@ -187,8 +187,9 @@
|
|||
}];
|
||||
window.addEventListener('resize', this.resizeHandler = () => this.keepScroll());
|
||||
window.addEventListener('keypress', this.keypressHandler = () => {
|
||||
if(document.getSelection().isCollapsed && !anyDialogsShown &&
|
||||
(document.activeElement === document.body || document.activeElement.tagName === 'A'))
|
||||
const selection = document.getSelection();
|
||||
if((selection === null || selection.isCollapsed) && !anyDialogsShown &&
|
||||
(document.activeElement === document.body || document.activeElement === null || document.activeElement.tagName === 'A'))
|
||||
(<Editor>this.$refs['textBox']).focus();
|
||||
});
|
||||
window.addEventListener('keydown', this.keydownHandler = ((e: KeyboardEvent) => {
|
||||
|
@ -211,11 +212,9 @@
|
|||
this.adsMode = l('channel.mode.ads');
|
||||
} else this.adsMode = l('channel.mode.ads.countdown', Math.floor(diff / 60), Math.floor(diff % 60));
|
||||
};
|
||||
if(Date.now() < value) {
|
||||
if(this.adCountdown === 0)
|
||||
this.adCountdown = window.setInterval(setAdCountdown, 1000);
|
||||
setAdCountdown();
|
||||
}
|
||||
if(Date.now() < value && this.adCountdown === 0)
|
||||
this.adCountdown = window.setInterval(setAdCountdown, 1000);
|
||||
setAdCountdown();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -261,11 +260,13 @@
|
|||
}
|
||||
|
||||
keepScroll(): void {
|
||||
if(this.scrolledDown)
|
||||
if(this.scrolledDown) {
|
||||
this.ignoreScroll = true;
|
||||
this.$nextTick(() => setTimeout(() => {
|
||||
this.ignoreScroll = true;
|
||||
this.messageView.scrollTop = this.messageView.scrollHeight;
|
||||
}, 0));
|
||||
}
|
||||
}
|
||||
|
||||
onMessagesScroll(): void {
|
||||
|
|
|
@ -205,6 +205,7 @@
|
|||
if(getKey(e) === Keys.KeyA && (e.ctrlKey || e.metaKey) && !e.altKey && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
const selection = document.getSelection();
|
||||
if(selection === null) return;
|
||||
selection.removeAllRanges();
|
||||
if(this.messages.length > 0) {
|
||||
const range = document.createRange();
|
||||
|
|
|
@ -216,7 +216,7 @@
|
|||
|
||||
async submit(): Promise<void> {
|
||||
const idleTimer = parseInt(this.idleTimer, 10);
|
||||
const fontSize = parseInt(this.fontSize, 10);
|
||||
const fontSize = parseFloat(this.fontSize);
|
||||
core.state.settings = {
|
||||
playSound: this.playSound,
|
||||
clickOpensMessage: this.clickOpensMessage,
|
||||
|
|
|
@ -17,6 +17,7 @@ export const BBCodeView: Component = {
|
|||
insert(node: VNode): void {
|
||||
node.elm!.appendChild(core.bbCodeParser.parseEverything(
|
||||
context.props.text !== undefined ? context.props.text : context.props.unsafeText));
|
||||
if(context.props.afterInsert !== undefined) context.props.afterInsert(node.elm);
|
||||
},
|
||||
destroy(node: VNode): void {
|
||||
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;
|
||||
});
|
||||
connection.onMessage('CBU', async(data, time) => {
|
||||
const text = l('events.ban', data.channel, data.character, data.operator);
|
||||
const conv = state.channelMap[data.channel.toLowerCase()];
|
||||
if(conv === undefined) return core.channels.leave(data.channel);
|
||||
const text = l('events.ban', conv.name, data.character, data.operator);
|
||||
conv.infoText = text;
|
||||
return addEventMessage(new EventMessage(text, 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()];
|
||||
if(conv === undefined) return core.channels.leave(data.channel);
|
||||
const text = l('events.kick', conv.name, data.character, data.operator);
|
||||
conv.infoText = text;
|
||||
return addEventMessage(new EventMessage(text, 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()];
|
||||
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;
|
||||
return addEventMessage(new EventMessage(text, time));
|
||||
});
|
||||
connection.onMessage('BRO', async(data, time) => {
|
||||
const text = data.character === undefined ? decodeHTML(data.message) :
|
||||
l('events.broadcast', `[user]${data.character}[/user]`, decodeHTML(data.message.substr(data.character.length + 23)));
|
||||
return addEventMessage(new EventMessage(text, time));
|
||||
if(data.character !== undefined) {
|
||||
const content = decodeHTML(data.message.substr(data.character.length + 24));
|
||||
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) => {
|
||||
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.colorBookmarks': 'Show friends/bookmarks in a different colour',
|
||||
'settings.beta': 'Opt-in to test unstable prerelease updates',
|
||||
'settings.hwAcceleration': 'Enable hardware acceleration (requires restart)',
|
||||
'settings.bbCodeBar': 'Show BBCode formatting bar',
|
||||
'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.
|
||||
|
@ -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.throttle': 'You must wait five seconds between searches.',
|
||||
'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.error': 'Error: {0}',
|
||||
'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]! : ' ');
|
||||
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);
|
||||
node.key = context.data.key;
|
||||
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> {
|
||||
if(!this.shouldNotify(conversation)) return;
|
||||
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));
|
||||
notification.onclick = () => {
|
||||
conversation.show();
|
||||
|
@ -69,6 +70,6 @@ export default class Notifications implements Interface {
|
|||
}
|
||||
|
||||
async requestPermission(): Promise<void> {
|
||||
await Notification.requestPermission();
|
||||
if((<{Notification?: object}>window).Notification !== undefined) await Notification.requestPermission();
|
||||
}
|
||||
}
|
|
@ -1,18 +1,18 @@
|
|||
<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 style="display:flex;align-items:stretch;" class="border-bottom" id="window-tabs">
|
||||
<h4>F-Chat</h4>
|
||||
<div style="display:flex;align-items:stretch;border-bottom-width:1px" class="border-bottom" id="window-tabs">
|
||||
<h4 style="padding:2px 0">F-Chat</h4>
|
||||
<div class="btn" :class="'btn-' + (hasUpdate ? 'warning' : 'light')" @click="openMenu" id="settings">
|
||||
<i class="fa fa-cog"></i>
|
||||
</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)">
|
||||
<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}">
|
||||
<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>
|
||||
<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>
|
||||
</a>
|
||||
</a>
|
||||
|
@ -21,7 +21,7 @@
|
|||
<a href="#" @click.prevent="addTab" class="nav-link"><i class="fa fa-plus"></i></a>
|
||||
</li>
|
||||
</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">
|
||||
<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>
|
||||
|
@ -225,7 +225,7 @@
|
|||
}
|
||||
|
||||
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);
|
||||
electron.ipcRenderer.send('has-new', this.tabs.reduce((cur, t) => cur || t.hasNew, false));
|
||||
delete this.tabMap[tab.view.webContents.id];
|
||||
|
@ -259,11 +259,12 @@
|
|||
#window-tabs {
|
||||
user-select: none;
|
||||
.btn {
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
padding: 2px 15px;
|
||||
padding: 0 18px;
|
||||
display: flex;
|
||||
margin: 0px -1px -1px 0;
|
||||
align-items: center;
|
||||
line-height: 1;
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
|
@ -287,10 +288,6 @@
|
|||
height: 28px;
|
||||
margin: -5px 3px -5px -5px;
|
||||
}
|
||||
|
||||
&.active {
|
||||
margin-bottom: -2px;
|
||||
}
|
||||
}
|
||||
|
||||
h4 {
|
||||
|
@ -307,8 +304,8 @@
|
|||
}
|
||||
|
||||
#windowButtons .btn {
|
||||
margin: -4px -1px -1px 0;
|
||||
border-top: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.platform-darwin {
|
||||
|
@ -322,8 +319,8 @@
|
|||
}
|
||||
|
||||
.btn, li a {
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
padding-top: 6px;
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ export class GeneralSettings {
|
|||
version = electron.app.getVersion();
|
||||
beta = false;
|
||||
customDictionary: string[] = [];
|
||||
hwAcceleration = true;
|
||||
}
|
||||
|
||||
export function mkdir(dir: string): void {
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<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>
|
||||
<link href="fa.css" rel="stylesheet">
|
||||
</head>
|
||||
|
|
|
@ -59,6 +59,19 @@ mkdir(settingsDir);
|
|||
const settingsFile = path.join(settingsDir, 'settings');
|
||||
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> {
|
||||
if(lang !== undefined) await ensureDictionary(lang);
|
||||
settings.spellcheckLang = lang;
|
||||
|
@ -142,14 +155,6 @@ function onReady(): void {
|
|||
log.transports.file.file = path.join(baseDir, 'log.txt');
|
||||
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.on('open-file', createWindow);
|
||||
|
||||
|
@ -271,6 +276,12 @@ function onReady(): void {
|
|||
label: x,
|
||||
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,
|
||||
click: async(item: Electron.MenuItem) => {
|
||||
|
@ -383,6 +394,7 @@ function onReady(): void {
|
|||
}
|
||||
|
||||
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);
|
||||
app.on('second-instance', createWindow);
|
||||
app.on('window-all-closed', () => app.quit());
|
|
@ -1,3 +1,4 @@
|
|||
process.env.DEBUG = 'electron-windows-installer:main';
|
||||
const path = require('path');
|
||||
const pkg = require(path.join(__dirname, 'package.json'));
|
||||
const fs = require('fs');
|
||||
|
@ -60,7 +61,7 @@ require('electron-packager')({
|
|||
require('electron-winstaller').createWindowsInstaller({
|
||||
appDirectory: appPaths[0],
|
||||
outputDirectory: distDir,
|
||||
iconUrl: icon,
|
||||
iconUrl: 'file:///%localappdata%\\fchat\\app.ico',
|
||||
setupIcon: icon,
|
||||
noMsi: true,
|
||||
exe: 'F-Chat.exe',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "fchat",
|
||||
"version": "3.0.8",
|
||||
"version": "3.0.9",
|
||||
"author": "The F-List Team",
|
||||
"description": "F-List.net Chat Client",
|
||||
"main": "main.js",
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
const path = require('path');
|
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin');
|
||||
const fs = require('fs');
|
||||
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
|
||||
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
|
||||
|
@ -109,20 +108,15 @@ const mainConfig = {
|
|||
module.exports = function(mode) {
|
||||
const themesDir = path.join(__dirname, '../scss/themes/chat');
|
||||
const themes = fs.readdirSync(themesDir);
|
||||
const cssOptions = {use: ['css-loader', 'sass-loader']};
|
||||
for(const theme of themes) {
|
||||
if(!theme.endsWith('.scss')) continue;
|
||||
const absPath = path.join(themesDir, theme);
|
||||
rendererConfig.entry.chat.push(absPath);
|
||||
const plugin = new ExtractTextPlugin('themes/' + theme.slice(0, -5) + '.css');
|
||||
rendererConfig.plugins.push(plugin);
|
||||
rendererConfig.module.rules.unshift({test: absPath, use: plugin.extract(cssOptions)});
|
||||
rendererConfig.module.rules.unshift({test: absPath, loader: ['file-loader?name=themes/[name].css', 'extract-loader', 'css-loader', 'sass-loader']});
|
||||
}
|
||||
const faPath = path.join(themesDir, '../../fa.scss');
|
||||
rendererConfig.entry.chat.push(faPath);
|
||||
const faPlugin = new ExtractTextPlugin('./fa.css');
|
||||
rendererConfig.plugins.push(faPlugin);
|
||||
rendererConfig.module.rules.unshift({test: faPath, use: faPlugin.extract(cssOptions)});
|
||||
rendererConfig.module.rules.unshift({test: faPath, loader: ['file-loader?name=fa.css', 'extract-loader', 'css-loader', 'sass-loader']});
|
||||
if(mode === 'production') {
|
||||
process.env.NODE_ENV = 'production';
|
||||
mainConfig.devtool = rendererConfig.devtool = 'source-map';
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<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>
|
||||
<link href="fa.css" rel="stylesheet">
|
||||
</head>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div id="page" style="position: relative; padding: 10px;" v-if="settings">
|
||||
<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;">
|
||||
<h3 class="card-header" style="margin-top:0">{{l('title')}}</h3>
|
||||
<div class="card-body">
|
||||
|
|
|
@ -8,8 +8,8 @@ android {
|
|||
applicationId "net.f_list.fchat"
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 27
|
||||
versionCode 19
|
||||
versionName "3.0.8"
|
||||
versionCode 20
|
||||
versionName "3.0.9"
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
|
|
|
@ -11,19 +11,27 @@ import android.net.Uri
|
|||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.os.Handler
|
||||
import android.view.KeyEvent
|
||||
import android.view.ViewGroup
|
||||
import android.webkit.JsResult
|
||||
import android.webkit.WebChromeClient
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import android.widget.EditText
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.net.URLDecoder
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
|
||||
class MainActivity : Activity() {
|
||||
private lateinit var webView: WebView
|
||||
private val profileRegex = Regex("^https?://(www\\.)?f-list.net/c/([^/#]+)/?#?")
|
||||
private val backgroundPlugin = Background(this)
|
||||
private var debugPressed = 0
|
||||
private val debugHandler = Handler()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
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() {
|
||||
webView.evaluateJavascript("var e=new Event('backbutton',{cancelable:true});document.dispatchEvent(e);e.defaultPrevented", {
|
||||
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)
|
||||
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) notification.setChannelId("messages")
|
||||
object : AsyncTask<String, Void, Bitmap>() {
|
||||
override fun doInBackground(vararg args: String): Bitmap {
|
||||
val connection = URL(args[0]).openConnection()
|
||||
return BitmapFactory.decodeStream(connection.getInputStream())
|
||||
override fun doInBackground(vararg args: String): Bitmap? {
|
||||
return try {
|
||||
val connection = URL(args[0]).openConnection()
|
||||
BitmapFactory.decodeStream(connection.getInputStream())
|
||||
} catch(e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPostExecute(result: Bitmap?) {
|
||||
|
|
|
@ -21,12 +21,15 @@ class ViewController: UIViewController, WKNavigationDelegate, WKUIDelegate {
|
|||
config.userContentController = controller
|
||||
config.mediaTypesRequiringUserActionForPlayback = [.video]
|
||||
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.navigationDelegate = self
|
||||
view = webView
|
||||
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)
|
||||
webView.scrollView.contentInsetAdjustmentBehavior = .never
|
||||
webView.scrollView.bounces = false
|
||||
UIApplication.shared.statusBarStyle = .lightContent
|
||||
(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 url = URL(fileURLWithPath: htmlPath!, isDirectory: false)
|
||||
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) {
|
||||
let info = notification.userInfo!
|
||||
let frame = webView.frame
|
||||
let newHeight = view.window!.frame.height - (info[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue.height
|
||||
UIView.animate(withDuration: (info[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue, animations: {
|
||||
self.webView.scrollView.bounds = CGRect(x: 0, y: 0, width: frame.width, height: newHeight)
|
||||
}, completion: { (_: Bool) in self.webView.evaluateJavaScript("window.dispatchEvent(new Event('resize'))", completionHandler: nil) })
|
||||
self.webView.frame = CGRect(x: 0, y: 0, width: self.webView.frame.width, height: newHeight)
|
||||
})
|
||||
}
|
||||
|
||||
@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) {
|
||||
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: {
|
||||
self.webView.scrollView.bounds = CGRect(x: 0, y: 0, width: frame.width, height: newHeight)
|
||||
}, completion: { (_: Bool) in self.webView.evaluateJavaScript("window.dispatchEvent(new Event('resize'))", completionHandler: nil) })
|
||||
self.webView.frame = UIApplication.shared.windows[0].frame
|
||||
})
|
||||
}
|
||||
|
||||
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!)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "net.f_list.fchat",
|
||||
"version": "3.0.8",
|
||||
"version": "3.0.9",
|
||||
"displayName": "F-Chat",
|
||||
"author": "The F-List Team",
|
||||
"description": "F-List.net Chat Client",
|
||||
|
|
38
package.json
38
package.json
|
@ -7,46 +7,46 @@
|
|||
"devDependencies": {
|
||||
"@fortawesome/fontawesome-free-webfonts": "^1.0.6",
|
||||
"@types/lodash": "^4.14.116",
|
||||
"@types/node": "^10.5.6",
|
||||
"@types/node": "^10.11.2",
|
||||
"@types/sortablejs": "^1.3.31",
|
||||
"axios": "^0.18.0",
|
||||
"bootstrap": "^4.1.3",
|
||||
"css-loader": "^1.0.0",
|
||||
"date-fns": "^1.28.5",
|
||||
"electron": "2.0.2",
|
||||
"electron-log": "^2.2.16",
|
||||
"electron-packager": "^12.1.0",
|
||||
"electron": "^3.0.2",
|
||||
"electron-log": "^2.2.17",
|
||||
"electron-packager": "^12.1.2",
|
||||
"electron-rebuild": "^1.8.2",
|
||||
"extract-text-webpack-plugin": "4.0.0-beta.0",
|
||||
"file-loader": "^1.1.10",
|
||||
"fork-ts-checker-webpack-plugin": "^0.4.4",
|
||||
"lodash": "^4.16.4",
|
||||
"node-sass": "^4.8.3",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.0",
|
||||
"extract-loader": "^3.0.0",
|
||||
"file-loader": "^2.0.0",
|
||||
"fork-ts-checker-webpack-plugin": "^0.4.9",
|
||||
"lodash": "^4.17.11",
|
||||
"node-sass": "^4.9.3",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.1",
|
||||
"qs": "^6.5.1",
|
||||
"raven-js": "^3.26.4",
|
||||
"raven-js": "^3.27.0",
|
||||
"sass-loader": "^7.1.0",
|
||||
"sortablejs": "^1.6.0",
|
||||
"style-loader": "^0.21.0",
|
||||
"ts-loader": "^4.2.0",
|
||||
"style-loader": "^0.23.0",
|
||||
"ts-loader": "^5.2.1",
|
||||
"tslib": "^1.7.1",
|
||||
"tslint": "^5.7.0",
|
||||
"typescript": "^3.0.1",
|
||||
"typescript": "^3.1.1",
|
||||
"vue": "^2.5.17",
|
||||
"vue-class-component": "^6.0.0",
|
||||
"vue-loader": "^15.2.6",
|
||||
"vue-property-decorator": "^7.0.0",
|
||||
"vue-loader": "^15.4.2",
|
||||
"vue-property-decorator": "^7.1.1",
|
||||
"vue-template-compiler": "^2.5.17",
|
||||
"webpack": "^4.16.4"
|
||||
"webpack": "^4.20.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"keytar": "^4.2.1",
|
||||
"spellchecker": "^3.4.3"
|
||||
"spellchecker": "^3.5.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"appdmg": "^0.5.2",
|
||||
"electron-squirrel-startup": "^1.0.0",
|
||||
"electron-winstaller": "^2.6.4"
|
||||
"electron-winstaller": "^2.7.0"
|
||||
},
|
||||
"scripts": {
|
||||
"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
|
||||
See https://electron.atom.io/docs/tutorial/application-distribution/
|
||||
- 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 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.
|
||||
|
|
|
@ -201,6 +201,35 @@
|
|||
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 {
|
||||
background-color: theme-color-level("success", -8);
|
||||
}
|
||||
|
@ -254,12 +283,17 @@ $genders: (
|
|||
max-width: 98%;
|
||||
}
|
||||
|
||||
#window-tabs .hasNew {
|
||||
background-color: theme-color-level("warning", -2);
|
||||
border-color: theme-color-level("warning", -4);
|
||||
color: color-yiq(theme-color("warning"));
|
||||
&:hover {
|
||||
background-color: theme-color-level("warning", -4);
|
||||
#window-tabs {
|
||||
.hasNew {
|
||||
background-color: theme-color-level("warning", -2);
|
||||
border-color: theme-color-level("warning", -4);
|
||||
color: color-yiq(theme-color("warning"));
|
||||
&: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;
|
||||
$secondary: $gray-400;
|
||||
|
||||
$body-bg: #eeeeee;
|
||||
$body-bg: $gray-100;
|
||||
$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 Notifications from './notifications';
|
||||
|
||||
//@ts-ignore
|
||||
if(typeof window.Promise !== 'function' || typeof window.Notification !== 'function') //tslint:disable-line:strict-type-predicates
|
||||
if(typeof (<{Promise?: object}>window).Promise !== '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
|
||||
|
|
|
@ -45,7 +45,7 @@ type Index = {[key: string]: StoredConversation | undefined};
|
|||
async function openDatabase(character: string): Promise<IDBDatabase> {
|
||||
const request = window.indexedDB.open(`logs-${character}`);
|
||||
request.onupgradeneeded = () => {
|
||||
const db = <IDBDatabase>request.result;
|
||||
const db = request.result;
|
||||
const logsStore = db.createObjectStore('logs', {keyPath: 'id', autoIncrement: true});
|
||||
logsStore.createIndex('conversation', 'conversation');
|
||||
logsStore.createIndex('conversation-day', hasComposite ? ['conversation', 'day'] : 'day');
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "net.f_list.fchat",
|
||||
"version": "3.0.8",
|
||||
"version": "3.0.9",
|
||||
"displayName": "F-Chat",
|
||||
"author": "The F-List Team",
|
||||
"description": "F-List.net Chat Client",
|
||||
|
|
Loading…
Reference in New Issue