Merge remote-tracking branch 'f-list/master' into canary
This commit is contained in:
commit
88ad750d94
|
@ -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 {
|
||||
|
|
|
@ -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',
|
||||
|
|
136
bbcode/parser.ts
136
bbcode/parser.ts
|
@ -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) {
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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 {
|
||||
|
|
10
chat/core.ts
10
chat/core.ts
|
@ -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();
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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'}});
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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:.
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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: [
|
||||
|
|
Loading…
Reference in New Issue