diff --git a/bbcode/core.ts b/bbcode/core.ts index 8d134a0..127e0f1 100644 --- a/bbcode/core.ts +++ b/bbcode/core.ts @@ -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 { diff --git a/bbcode/editor.ts b/bbcode/editor.ts index ae3a54f..5276dcd 100644 --- a/bbcode/editor.ts +++ b/bbcode/editor.ts @@ -89,6 +89,12 @@ export let defaultButtons: ReadonlyArray = [ 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', diff --git a/bbcode/parser.ts b/bbcode/parser.ts index d8bd799..7983ec3 100644 --- a/bbcode/parser.ts +++ b/bbcode/parser.ts @@ -77,7 +77,7 @@ export class BBCodeParser { const parent = document.createElement('span'); parent.className = 'bbcode'; this._currentTag = {tag: '', 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; - (element).bbcodeTag = tagKey; - if(param.length > 0) (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; + (element).bbcodeTag = tagKey; + if(param.length > 0) (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) { diff --git a/chat/Logs.vue b/chat/Logs.vue index 05b6968..5b48a34 100644 --- a/chat/Logs.vue +++ b/chat/Logs.vue @@ -23,7 +23,7 @@
+ :placeholder="l('filter')"> @@ -47,7 +47,8 @@ class="fa fa-download">
-
+
@@ -77,8 +78,15 @@ return format(date, 'yyyy-MM-dd'); } - function getLogs(messages: ReadonlyArray): string { - return messages.reduce((acc, x) => acc + messageToString(x, (date) => formatTime(date, true)), ''); + function getLogs(messages: ReadonlyArray, html: boolean): string { + const start = html ? + `` : ''; + return '
' + messages.reduce((acc, x) => acc + messageToString(x, (date) => formatTime(date, true), + html ? (c) => { + const gender = core.characters.get(c).gender; + return `${c}`; + } : undefined, + html ? (t) => `${core.bbCodeParser.parseEverything(t).innerHTML}` : undefined), start) + '
'; } @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 { 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 { 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 (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]; diff --git a/chat/common.ts b/chat/common.ts index b7d72fb..d9b5c2b 100644 --- a/chat/common.ts +++ b/chat/common.ts @@ -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 { diff --git a/chat/core.ts b/chat/core.ts index 3f647e1..a5e17d7 100644 --- a/chat/core.ts +++ b/chat/core.ts @@ -48,12 +48,6 @@ const vue = 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(); diff --git a/chat/localize.ts b/chat/localize.ts index 12920e3..dacdb05 100644 --- a/chat/localize.ts +++ b/chat/localize.ts @@ -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', diff --git a/electron/Index.vue b/electron/Index.vue index 108b046..f7c445a 100644 --- a/electron/Index.vue +++ b/electron/Index.vue @@ -510,7 +510,7 @@ get styling(): string { try { - return ``; + return ``; } catch(e) { if((e).code === 'ENOENT' && this.settings.theme !== 'default') { this.settings.theme = 'default'; diff --git a/electron/pack.js b/electron/pack.js index 8d3f783..35c0e9e 100644 --- a/electron/pack.js +++ b/electron/pack.js @@ -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'}}); diff --git a/mobile/Index.vue b/mobile/Index.vue index b786783..dcf32dd 100644 --- a/mobile/Index.vue +++ b/mobile/Index.vue @@ -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 ``; + return ``; } async login(): Promise { diff --git a/mobile/android/.idea/misc.xml b/mobile/android/.idea/misc.xml index 0d45e8d..5c02780 100644 --- a/mobile/android/.idea/misc.xml +++ b/mobile/android/.idea/misc.xml @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/mobile/android/.idea/modules.xml b/mobile/android/.idea/modules.xml index 816cb5f..09b6987 100644 --- a/mobile/android/.idea/modules.xml +++ b/mobile/android/.idea/modules.xml @@ -2,8 +2,8 @@ - - + + \ No newline at end of file diff --git a/mobile/android/app/build.gradle b/mobile/android/app/build.gradle index dee09bc..cab42af 100644 --- a/mobile/android/app/build.gradle +++ b/mobile/android/app/build.gradle @@ -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 { diff --git a/mobile/android/build.gradle b/mobile/android/build.gradle index 3b4d423..39ee91e 100644 --- a/mobile/android/build.gradle +++ b/mobile/android/build.gradle @@ -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 diff --git a/mobile/android/gradle/wrapper/gradle-wrapper.properties b/mobile/android/gradle/wrapper/gradle-wrapper.properties index ea720f9..1b16c34 100644 --- a/mobile/android/gradle/wrapper/gradle-wrapper.properties +++ b/mobile/android/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/mobile/ios/F-Chat.xcodeproj/project.pbxproj b/mobile/ios/F-Chat.xcodeproj/project.pbxproj index 4731c87..e5eadb5 100644 --- a/mobile/ios/F-Chat.xcodeproj/project.pbxproj +++ b/mobile/ios/F-Chat.xcodeproj/project.pbxproj @@ -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; diff --git a/mobile/ios/F-Chat/AppDelegate.swift b/mobile/ios/F-Chat/AppDelegate.swift index ecede7f..3bb5f31 100644 --- a/mobile/ios/F-Chat/AppDelegate.swift +++ b/mobile/ios/F-Chat/AppDelegate.swift @@ -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:. } - - } - diff --git a/mobile/ios/F-Chat/Background.swift b/mobile/ios/F-Chat/Background.swift index 790a12a..be53fe7 100644 --- a/mobile/ios/F-Chat/Background.swift +++ b/mobile/ios/F-Chat/Background.swift @@ -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() diff --git a/mobile/ios/F-Chat/Notification.swift b/mobile/ios/F-Chat/Notification.swift index 903baf1..f1332c1 100644 --- a/mobile/ios/F-Chat/Notification.swift +++ b/mobile/ios/F-Chat/Notification.swift @@ -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) diff --git a/mobile/ios/F-Chat/ViewController.swift b/mobile/ios/F-Chat/ViewController.swift index 9c57407..197d518 100644 --- a/mobile/ios/F-Chat/ViewController.swift +++ b/mobile/ios/F-Chat/ViewController.swift @@ -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).. { 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', diff --git a/webchat/package.json b/webchat/package.json index 1d2dee2..546774f 100644 --- a/webchat/package.json +++ b/webchat/package.json @@ -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" } -} \ No newline at end of file +} diff --git a/webchat/webpack.config.js b/webchat/webpack.config.js index e4e7676..6e0a9d2 100644 --- a/webchat/webpack.config.js +++ b/webchat/webpack.config.js @@ -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: [