This commit is contained in:
MayaWolf 2018-09-28 02:08:10 +02:00
parent 39f9365299
commit 8810b29552
31 changed files with 1733 additions and 1075 deletions

View File

@ -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;

View File

@ -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 {

View File

@ -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();

View File

@ -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,

View File

@ -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);

View File

@ -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]`);

View File

@ -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}',

View File

@ -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;

View File

@ -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();
}
}

View File

@ -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;
}
}
}

View File

@ -15,6 +15,7 @@ export class GeneralSettings {
version = electron.app.getVersion();
beta = false;
customDictionary: string[] = [];
hwAcceleration = true;
}
export function mkdir(dir: string): void {

View File

@ -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>

View File

@ -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());

View File

@ -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',

View File

@ -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",

View File

@ -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';

View File

@ -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>

View File

@ -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">

View File

@ -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 {

View File

@ -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()

View File

@ -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?) {

View File

@ -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!)
}
}

View File

@ -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",

View File

@ -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"

View File

@ -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.

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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

View File

@ -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');

View File

@ -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",

2474
yarn.lock

File diff suppressed because it is too large Load Diff