Merge remote-tracking branch 'f-list/master' into canary

This commit is contained in:
Mr. Stallion 2021-03-21 11:42:40 -05:00
commit 88ad750d94
24 changed files with 152 additions and 138 deletions

View File

@ -109,6 +109,19 @@ export class CoreBBCodeParser extends BBCodeParser {
return element;
}));
this.addTag(new BBCodeCustomTag('spoiler', (parser, parent) => {
const link = parser.createElement('a');
const content = parser.createElement('span');
link.href = '#';
link.onclick = (e) => {
const target = e.target as HTMLElement;
target.parentElement!.replaceChild(content, target);
return false;
};
link.appendChild(document.createTextNode('[click to show spoiler]'));
parent.appendChild(link);
return content;
}));
}
parseEverything(input: string): HTMLElement {

View File

@ -89,6 +89,12 @@ export let defaultButtons: ReadonlyArray<EditorButton> = [
icon: 'fa-smile',
key: Keys.KeyE
},
{
title: 'Spoiler (Ctrl+K)\n\nHidden until explicitly clicked by the viewer.',
tag: 'spoiler',
icon: 'fa-eye-slash',
key: Keys.KeyK
},
{
title: 'Noparse (Ctrl+N)\n\nAll BBCode placed within this tag will be ignored and treated as text. Great for sharing structure without it being rendered.',
tag: 'noparse',

View File

@ -77,7 +77,7 @@ export class BBCodeParser {
const parent = document.createElement('span');
parent.className = 'bbcode';
this._currentTag = {tag: '<root>', line: 1, column: 1};
this.parse(input, 0, undefined, parent, () => true);
this.parse(input, 0, undefined, parent, () => true, 0);
//if(process.env.NODE_ENV !== 'production' && this._warnings.length > 0)
// console.log(this._warnings);
@ -119,7 +119,7 @@ export class BBCodeParser {
}
private parse(input: string, start: number, self: BBCodeTag | undefined, parent: HTMLElement | undefined,
isAllowed: (tag: string) => boolean): number {
isAllowed: (tag: string) => boolean, depth: number): number {
let currentTag = this._currentTag;
const selfAllowed = self !== undefined ? isAllowed(self.tag) : true;
if(self !== undefined) {
@ -127,10 +127,7 @@ export class BBCodeParser {
isAllowed = (name) => self.isAllowed(name) && parentAllowed(name);
currentTag = this._currentTag = {tag: self.tag, line: this._line, column: this._column};
}
let tagStart = -1, paramStart = -1, mark = start;
let depth = 0;
for(let i = start; i < input.length; ++i) {
const c = input[i];
++this._column;
@ -139,83 +136,64 @@ export class BBCodeParser {
this._column = 1;
}
if(c === '[') {
depth++;
if (depth === 1) {
tagStart = i;
paramStart = -1;
} else {
// console.log('Hit depth tagOpen', depth);
tagStart = i;
paramStart = -1;
} else if(c === '=' && paramStart === -1)
paramStart = i;
else if(c === ']') {
const paramIndex = paramStart === -1 ? i : paramStart;
let tagKey = input.substring(tagStart + 1, paramIndex).trim().toLowerCase();
if(tagKey.length === 0) {
tagStart = -1;
continue;
}
} else if(c === '=' && paramStart === -1) {
if (depth <= 1) {
paramStart = i;
} else {
// console.log('Hit depth paramStart', depth);
const param = paramStart > tagStart ? input.substring(paramStart + 1, i).trim() : '';
const close = tagKey[0] === '/';
if(close) tagKey = tagKey.substr(1).trim();
if(this._tags[tagKey] === undefined) {
tagStart = -1;
continue;
}
} else if(c === ']') {
depth--;
if (depth !== 0) {
// console.log('Hit depth tagClose', depth);
}
if (depth === 0) {
const paramIndex = paramStart === -1 ? i : paramStart;
let tagKey = input.substring(tagStart + 1, paramIndex).trim().toLowerCase();
if(tagKey.length === 0) {
tagStart = -1;
continue;
}
const param = paramStart > tagStart ? input.substring(paramStart + 1, i).trim() : '';
const close = tagKey[0] === '/';
if(close) tagKey = tagKey.substr(1).trim();
if(this._tags[tagKey] === undefined) {
tagStart = -1;
continue;
}
if(!close) {
const tag = this._tags[tagKey]!;
const allowed = isAllowed(tagKey);
if(parent !== undefined) {
parent.appendChild(document.createTextNode(input.substring(mark, allowed ? tagStart : i + 1)));
mark = i + 1;
}
if(!allowed || parent === undefined) {
i = this.parse(input, i + 1, tag, parent, isAllowed);
mark = i + 1;
continue;
}
let element: HTMLElement | undefined;
if(tag instanceof BBCodeTextTag) {
i = this.parse(input, i + 1, tag, undefined, isAllowed);
element = tag.createElement(this, parent, param, input.substring(mark, input.lastIndexOf('[', i)));
if(element === undefined) parent.appendChild(document.createTextNode(input.substring(tagStart, i + 1)));
} else {
element = tag.createElement(this, parent, param, '');
if(element === undefined) parent.appendChild(document.createTextNode(input.substring(tagStart, i + 1)));
if(!tag.noClosingTag)
i = this.parse(input, i + 1, tag, element !== undefined ? element : parent, isAllowed);
if(element === undefined)
parent.appendChild(document.createTextNode(input.substring(input.lastIndexOf('[', i), i + 1)));
}
if(!close) {
const tag = this._tags[tagKey]!;
const allowed = isAllowed(tagKey);
if(parent !== undefined) {
parent.appendChild(document.createTextNode(input.substring(mark, allowed ? tagStart : i + 1)));
mark = i + 1;
this._currentTag = currentTag;
if(element === undefined) continue;
(<HTMLElement & {bbcodeTag: string}>element).bbcodeTag = tagKey;
if(param.length > 0) (<HTMLElement & {bbcodeParam: string}>element).bbcodeParam = param;
} else if(self !== undefined) { //tslint:disable-line:curly
if(self.tag === tagKey) {
if(parent !== undefined)
parent.appendChild(document.createTextNode(input.substring(mark, selfAllowed ? tagStart : i + 1)));
return i;
}
if(!selfAllowed) return mark - 1;
if(isAllowed(tagKey))
this.warning(`Unexpected closing ${tagKey} tag. Needed ${self.tag} tag instead.`);
} else if(isAllowed(tagKey)) this.warning(`Found closing ${tagKey} tag that was never opened.`);
}
}
if(!allowed || parent === undefined || depth > 100) {
i = this.parse(input, i + 1, tag, parent, isAllowed, depth + 1);
mark = i + 1;
continue;
}
let element: HTMLElement | undefined;
if(tag instanceof BBCodeTextTag) {
i = this.parse(input, i + 1, tag, undefined, isAllowed, depth + 1);
element = tag.createElement(this, parent, param, input.substring(mark, input.lastIndexOf('[', i)));
if(element === undefined) parent.appendChild(document.createTextNode(input.substring(tagStart, i + 1)));
} else {
element = tag.createElement(this, parent, param, '');
if(element === undefined) parent.appendChild(document.createTextNode(input.substring(tagStart, i + 1)));
if(!tag.noClosingTag)
i = this.parse(input, i + 1, tag, element !== undefined ? element : parent, isAllowed, depth + 1);
if(element === undefined)
parent.appendChild(document.createTextNode(input.substring(input.lastIndexOf('[', i), i + 1)));
}
mark = i + 1;
this._currentTag = currentTag;
if(element === undefined) continue;
(<HTMLElement & {bbcodeTag: string}>element).bbcodeTag = tagKey;
if(param.length > 0) (<HTMLElement & {bbcodeParam: string}>element).bbcodeParam = param;
} else if(self !== undefined) { //tslint:disable-line:curly
if(self.tag === tagKey) {
if(parent !== undefined)
parent.appendChild(document.createTextNode(input.substring(mark, selfAllowed ? tagStart : i + 1)));
return i;
}
if(!selfAllowed) return mark - 1;
if(isAllowed(tagKey))
this.warning(`Unexpected closing ${tagKey} tag. Needed ${self} tag instead.`);
} else if(isAllowed(tagKey)) this.warning(`Found closing ${tagKey} tag that was never opened.`);
}
}
if(mark < input.length && parent !== undefined) {

View File

@ -23,7 +23,7 @@
<label class="col-sm-2 col-form-label">{{l('logs.conversation')}}</label>
<div :class="canZip ? 'col-sm-8 col-10 col-xl-9' : 'col-sm-10'">
<filterable-select v-model="selectedConversation" :options="conversations" :filterFunc="filterConversation"
:placeholder="l('filter')">
:placeholder="l('filter')">
<template slot-scope="s">
{{s.option && ((s.option.key[0] == '#' ? '#' : '') + s.option.name) || l('logs.selectConversation')}}
</template>
@ -47,7 +47,8 @@
class="fa fa-download"></span></button>
</div>
</div>
<div class="messages messages-both" style="overflow:auto;overscroll-behavior:none;" ref="messages" tabindex="-1" @scroll="onMessagesScroll">
<div class="messages messages-both" style="overflow:auto;overscroll-behavior:none;" ref="messages" tabindex="-1"
@scroll="onMessagesScroll">
<message-view v-for="message in displayedMessages" :message="message" :key="message.id" :logs="true"></message-view>
</div>
<div class="input-group" style="flex-shrink:0">
@ -77,8 +78,15 @@
return format(date, 'yyyy-MM-dd');
}
function getLogs(messages: ReadonlyArray<Conversation.Message>): string {
return messages.reduce((acc, x) => acc + messageToString(x, (date) => formatTime(date, true)), '');
function getLogs(messages: ReadonlyArray<Conversation.Message>, html: boolean): string {
const start = html ?
`<meta charset="utf-8"><style>body { padding: 10px; }${document.getElementById('themeStyle')!.innerText}</style>` : '';
return '<div class="messages bbcode">' + messages.reduce((acc, x) => acc + messageToString(x, (date) => formatTime(date, true),
html ? (c) => {
const gender = core.characters.get(c).gender;
return `<span class="user-view gender-${gender ? gender.toLowerCase() : 'none'}">${c}</span>`;
} : undefined,
html ? (t) => `${core.bbCodeParser.parseEverything(t).innerHTML}` : undefined), start) + '</div>';
}
@Component({
@ -187,16 +195,18 @@
downloadDay(): void {
if(this.selectedConversation === undefined || this.selectedDate === undefined || this.messages.length === 0) return;
const name = `${this.selectedConversation.name}-${formatDate(new Date(this.selectedDate))}.txt`;
this.download(name, `data:${encodeURIComponent(name)},${encodeURIComponent(getLogs(this.messages))}`);
const html = confirm(l('logs.html'));
const name = `${this.selectedConversation.name}-${formatDate(new Date(this.selectedDate))}.${html ? 'html' : 'txt'}`;
this.download(name, `data:${encodeURIComponent(name)},${encodeURIComponent(getLogs(this.messages, html))}`);
}
async downloadConversation(): Promise<void> {
if(this.selectedConversation === undefined) return;
const zip = new Zip();
const html = confirm(l('logs.html'));
for(const date of this.dates) {
const messages = await core.logs.getLogs(this.selectedCharacter, this.selectedConversation.key, date);
zip.addFile(`${formatDate(date)}.txt`, getLogs(messages));
zip.addFile(`${formatDate(date)}.${html ? 'html' : 'txt'}`, getLogs(messages, html));
}
this.download(`${this.selectedConversation.name}.zip`, URL.createObjectURL(zip.build()));
}
@ -204,12 +214,13 @@
async downloadCharacter(): Promise<void> {
if(this.selectedCharacter === '' || !confirm(l('logs.confirmExport', this.selectedCharacter))) return;
const zip = new Zip();
const html = confirm(l('logs.html'));
for(const conv of this.conversations) {
zip.addFile(`${conv.name}/`, '');
const dates = await core.logs.getLogDates(this.selectedCharacter, conv.key);
for(const date of dates) {
const messages = await core.logs.getLogs(this.selectedCharacter, conv.key, date);
zip.addFile(`${conv.name}/${formatDate(date)}.txt`, getLogs(messages));
zip.addFile(`${conv.name}/${formatDate(date)}.${html ? 'html' : 'txt'}`, getLogs(messages, html));
}
}
this.download(`${this.selectedCharacter}.zip`, URL.createObjectURL(zip.build()));
@ -272,9 +283,11 @@
if(this.lockScroll) return;
if(list === undefined || ev !== undefined && Math.abs(list.scrollTop - this.lastScroll) < 50) return;
this.lockScroll = true;
function getTop(index: number): number {
return (<HTMLElement>list!.children[index]).offsetTop;
}
while(this.selectedConversation !== undefined && this.selectedDate === undefined && this.dialog.isShown) {
const oldHeight = list.scrollHeight, oldTop = list.scrollTop;
const oldFirst = this.displayedMessages[0];

View File

@ -81,12 +81,18 @@ export function formatTime(this: any | never, date: Date, noDate: boolean = fals
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
}
export function messageToString(this: any | never, msg: Conversation.Message, timeFormatter: (date: Date) => string = formatTime): string {
export function messageToString(
this: any | never,
msg: Conversation.Message,
timeFormatter: (date: Date) => string = formatTime,
characterTransform: (str: string) => string = (x) => x,
textTransform: (str: string) => string = (x) => x
): string {
let text = `[${timeFormatter(msg.time)}] `;
if(msg.type !== Conversation.Message.Type.Event)
text += (msg.type === Conversation.Message.Type.Action ? '*' : '') + msg.sender.name +
text += (msg.type === Conversation.Message.Type.Action ? '*' : '') + characterTransform(msg.sender.name) +
(msg.type === Conversation.Message.Type.Message ? ':' : '');
return `${text} ${msg.text}\r\n`;
return `${text} ${textTransform(msg.text)}\r\n`;
}
export function getKey(e: KeyboardEvent): Keys {

View File

@ -48,12 +48,6 @@ const vue = <Vue & VueState>new Vue({
characters: undefined,
conversations: undefined,
state
},
watch: {
'state.hiddenUsers': async(newValue: string[], oldValue: string[]) => {
if(data.settingsStore !== undefined && newValue !== oldValue)
await data.settingsStore.set('hiddenUsers', newValue);
}
}
});
@ -102,6 +96,10 @@ export function init(
data.register('channels', Channels(connection, core.characters));
data.register('conversations', Conversations());
data.watch(() => state.hiddenUsers, async(newValue) => {
if(data.settingsStore !== undefined) await data.settingsStore.set('hiddenUsers', newValue);
});
connection.onEvent('connecting', async() => {
await data.reloadSettings();
data.bbCodeParser = createBBCodeParser();

View File

@ -101,6 +101,7 @@ const strings: {[key: string]: string | undefined} = {
'logs.corruption.mobile.success': 'Your logs have been fixed.',
'logs.corruption.mobile.error': 'Unable to fix corrupted logs. Please clear the application data or reinstall the app.',
'logs.corruption.web': 'Error reading logs from browser storage. If this issue persists, please clear your stored browser data for F-Chat.',
'logs.html': 'Would you like to export these logs as HTML with formatting? Otherwise, they will be exported as plain text.',
'user.profile': 'Profile',
'user.message': 'Open conversation',
'user.messageJump': 'View conversation',

View File

@ -510,7 +510,7 @@
get styling(): string {
try {
return `<style>${fs.readFileSync(path.join(__dirname, `themes/${this.settings.theme}.css`), 'utf8').toString()}</style>`;
return `<style id="themeStyle">${fs.readFileSync(path.join(__dirname, `themes/${this.settings.theme}.css`), 'utf8').toString()}</style>`;
} catch(e) {
if((<Error & {code: string}>e).code === 'ENOENT' && this.settings.theme !== 'default') {
this.settings.theme = 'default';

View File

@ -129,7 +129,7 @@ require('electron-packager')({
const args = [appPaths[0], 'fchat.AppImage', '-u', 'zsync|https://client.f-list.net/fchat.AppImage.zsync'];
if(process.argv.length > 2) args.push('-s', '--sign-key', process.argv[2]);
else console.warn('Warning: Creating unsigned AppImage');
if(process.argv.length > 3) args.push('--sign-args', `--no-tty --passphrase=${process.argv[3]}`);
if(process.argv.length > 3) args.push('--sign-args', `--no-tty --pinentry-mode loopback --yes --passphrase=${process.argv[3]}`);
fs.chmodSync(downloaded, 0o755);
child_process.spawn(downloaded, ['--appimage-extract'], {cwd: distDir}).on('close', () => {
const child = child_process.spawn(path.join(distDir, 'squashfs-root', 'AppRun'), args, {cwd: distDir, env: {ARCH: 'x86_64'}});

View File

@ -127,7 +127,7 @@
get styling(): string {
if(window.NativeView !== undefined) window.NativeView.setTheme(this.settings.theme);
//tslint:disable-next-line:no-require-imports
return `<style>${require('../scss/fa.scss')}${require(`../scss/themes/chat/${this.settings.theme}.scss`)}</style>`;
return `<style id="themeStyle">${require('../scss/fa.scss')}${require(`../scss/themes/chat/${this.settings.theme}.scss`)}</style>`;
}
async login(): Promise<void> {

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="false" project-jdk-name="1.8" project-jdk-type="JavaSDK" />
</project>

View File

@ -2,8 +2,8 @@
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/android.iml" filepath="$PROJECT_DIR$/.idea/android.iml" />
<module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/android.app.iml" filepath="$PROJECT_DIR$/.idea/modules/app/android.app.iml" />
<module fileurl="file://$PROJECT_DIR$/mobile.android.iml" filepath="$PROJECT_DIR$/mobile.android.iml" />
</modules>
</component>
</project>

View File

@ -3,13 +3,13 @@ apply plugin: 'kotlin-android'
android {
compileSdkVersion 28
buildToolsVersion "28.0.3"
buildToolsVersion "29.0.3"
defaultConfig {
applicationId "net.f_list.fchat"
minSdkVersion 21
targetSdkVersion 27
versionCode 25
versionName "3.0.12"
versionCode 28
versionName "3.0.16"
}
buildTypes {
release {

View File

@ -1,13 +1,13 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.2.30'
ext.kotlin_version = '1.3.60'
repositories {
jcenter()
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.3'
classpath 'com.android.tools.build:gradle:3.5.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-bin.zip

View File

@ -112,11 +112,12 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 0920;
LastUpgradeCheck = 1130;
ORGANIZATIONNAME = "F-List";
TargetAttributes = {
6CA94BA71FEFEE7800183A1A = {
CreatedOnToolsVersion = 9.2;
LastSwiftMigration = 1130;
ProvisioningStyle = Automatic;
SystemCapabilities = {
com.apple.BackgroundModes = {
@ -218,6 +219,7 @@
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
@ -225,6 +227,7 @@
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
@ -275,6 +278,7 @@
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
@ -282,6 +286,7 @@
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
@ -321,7 +326,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "net.f-list.F-Chat";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.0;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
@ -336,7 +341,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "net.f-list.F-Chat";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.0;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;

View File

@ -2,11 +2,9 @@ import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
@ -32,7 +30,4 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
}

View File

@ -7,7 +7,7 @@ class Background: NSObject, WKScriptMessageHandler {
override init() {
let session = AVAudioSession.sharedInstance();
try! session.setCategory(AVAudioSessionCategoryPlayback, with: .mixWithOthers)
try! session.setCategory(AVAudioSession.Category.playback, options: .mixWithOthers)
player.volume = 0
player.numberOfLoops = -1;
player.play()

View File

@ -7,7 +7,7 @@ class Notification: NSObject, WKScriptMessageHandler, UNUserNotificationCenterDe
let center = UNUserNotificationCenter.current()
let baseDir = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
var webView: WKWebView!
func userContentController(_ controller: WKUserContentController, didReceive message: WKScriptMessage) {
center.delegate = self
self.webView = message.webView
@ -29,14 +29,14 @@ class Notification: NSObject, WKScriptMessageHandler, UNUserNotificationCenterDe
return
}
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
if(response.actionIdentifier == UNNotificationDefaultActionIdentifier) {
webView.evaluateJavaScript("document.dispatchEvent(new CustomEvent('notification-clicked',{detail:{data:'\(response.notification.request.content.userInfo["data"]!)'}}))")
}
completionHandler()
}
func notify(_ notify: Bool, _ title: String, _ text: String, _ icon: String, _ sound: String?, _ data: String, _ cb: (String?) -> Void) {
if(!notify) {
if(sound != nil) {
@ -49,14 +49,14 @@ class Notification: NSObject, WKScriptMessageHandler, UNUserNotificationCenterDe
let content = UNMutableNotificationContent()
content.title = title
if(sound != nil) {
content.sound = UNNotificationSound(named: Bundle.main.path(forResource: "www/sounds/" + sound!, ofType: "wav")!)
content.sound = UNNotificationSound(named: UNNotificationSoundName(Bundle.main.path(forResource: "www/sounds/" + sound!, ofType: "wav")!))
}
content.body = text
content.userInfo["data"] = data
center.add(UNNotificationRequest(identifier: "1", content: content, trigger: UNTimeIntervalNotificationTrigger.init(timeInterval: 1, repeats: false)))
cb("1");
}
func requestPermission(_ cb: @escaping (String?) -> Void) {
center.requestAuthorization(options: [.alert, .sound]) { (_, _) in
cb(nil)

View File

@ -29,13 +29,12 @@ class ViewController: UIViewController, WKNavigationDelegate, WKUIDelegate {
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)
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardDidShow), name: UIResponder.keyboardDidShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardWillHide), name: UIResponder.keyboardWillHideNotification, 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)
}
override func viewDidLoad() {
@ -47,8 +46,8 @@ class ViewController: UIViewController, WKNavigationDelegate, WKUIDelegate {
@objc func keyboardWillShow(notification: NSNotification) {
let info = notification.userInfo!
let newHeight = view.window!.frame.height - (info[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue.height
UIView.animate(withDuration: (info[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue, animations: {
let newHeight = view.window!.frame.height - (info[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue.height
UIView.animate(withDuration: (info[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue, animations: {
self.webView.frame = CGRect(x: 0, y: 0, width: self.webView.frame.width, height: newHeight)
})
}
@ -59,7 +58,7 @@ class ViewController: UIViewController, WKNavigationDelegate, WKUIDelegate {
@objc func keyboardWillHide(notification: NSNotification) {
let info = notification.userInfo!
UIView.animate(withDuration: (info[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue, animations: {
UIView.animate(withDuration: (info[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue, animations: {
self.webView.frame = UIApplication.shared.windows[0].frame
})
}
@ -88,7 +87,7 @@ class ViewController: UIViewController, WKNavigationDelegate, WKUIDelegate {
decisionHandler(.cancel)
let str = url.absoluteString
if(url.scheme == "data") {
let start = str.index(of: ",")!
let start = str.firstIndex(of: ",")!
let file = FileManager.default.temporaryDirectory.appendingPathComponent(str[str.index(str.startIndex, offsetBy: 5)..<start].removingPercentEncoding!)
try! str.suffix(from: str.index(after: start)).removingPercentEncoding!.write(to: file, atomically: false, encoding: .utf8)
let controller = UIActivityViewController(activityItems: [file], applicationActivities: nil)

View File

@ -1,6 +1,6 @@
{
"name": "net.f_list.fchat",
"version": "3.0.12",
"version": "3.0.16",
"displayName": "F-Chat",
"author": "The F-List Team",
"description": "F-List.net Chat Client",
@ -11,4 +11,4 @@
"build:dist": "node ../webpack production",
"watch": "node ../webpack watch"
}
}
}

View File

@ -71,7 +71,7 @@ window.addEventListener('beforeunload', (e) => {
return l('chat.confirmLeave');
});
require(`../scss/themes/chat/${chatSettings.theme}.scss`);
require(`!style-loader?{"attrs":{"id":"themeStyle"}}!css-loader!sass-loader!../scss/themes/chat/${chatSettings.theme}.scss`);
new Chat({ //tslint:disable-line:no-unused-expression
el: '#app',

View File

@ -1,6 +1,6 @@
{
"name": "net.f_list.fchat",
"version": "3.0.12",
"version": "3.0.16",
"displayName": "F-Chat",
"author": "The F-List Team",
"description": "F-List.net Chat Client",
@ -11,4 +11,4 @@
"build:dist": "node ../webpack production",
"watch": "node ../webpack watch"
}
}
}

View File

@ -36,8 +36,8 @@ const config = {
{test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'file-loader'},
{test: /\.(wav|mp3|ogg)$/, loader: 'file-loader?name=sounds/[name].[ext]'},
{test: /\.(png|html)$/, loader: 'file-loader?name=[name].[ext]'},
{test: /\.scss/, use: ['vue-style-loader', 'css-loader', 'sass-loader']},
{test: /\.css/, use: ['vue-style-loader', 'css-loader']}
{test: /\.scss$/, use: ['vue-style-loader', 'css-loader', 'sass-loader']},
{test: /\.css$/, use: ['vue-style-loader', 'css-loader']},
]
},
plugins: [