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;
|
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 {
|
parseEverything(input: string): HTMLElement {
|
||||||
|
|
|
@ -89,6 +89,12 @@ export let defaultButtons: ReadonlyArray<EditorButton> = [
|
||||||
icon: 'fa-smile',
|
icon: 'fa-smile',
|
||||||
key: Keys.KeyE
|
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.',
|
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',
|
tag: 'noparse',
|
||||||
|
|
136
bbcode/parser.ts
136
bbcode/parser.ts
|
@ -77,7 +77,7 @@ export class BBCodeParser {
|
||||||
const parent = document.createElement('span');
|
const parent = document.createElement('span');
|
||||||
parent.className = 'bbcode';
|
parent.className = 'bbcode';
|
||||||
this._currentTag = {tag: '<root>', line: 1, column: 1};
|
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)
|
//if(process.env.NODE_ENV !== 'production' && this._warnings.length > 0)
|
||||||
// console.log(this._warnings);
|
// console.log(this._warnings);
|
||||||
|
@ -119,7 +119,7 @@ export class BBCodeParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
private parse(input: string, start: number, self: BBCodeTag | undefined, parent: HTMLElement | undefined,
|
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;
|
let currentTag = this._currentTag;
|
||||||
const selfAllowed = self !== undefined ? isAllowed(self.tag) : true;
|
const selfAllowed = self !== undefined ? isAllowed(self.tag) : true;
|
||||||
if(self !== undefined) {
|
if(self !== undefined) {
|
||||||
|
@ -127,10 +127,7 @@ export class BBCodeParser {
|
||||||
isAllowed = (name) => self.isAllowed(name) && parentAllowed(name);
|
isAllowed = (name) => self.isAllowed(name) && parentAllowed(name);
|
||||||
currentTag = this._currentTag = {tag: self.tag, line: this._line, column: this._column};
|
currentTag = this._currentTag = {tag: self.tag, line: this._line, column: this._column};
|
||||||
}
|
}
|
||||||
|
|
||||||
let tagStart = -1, paramStart = -1, mark = start;
|
let tagStart = -1, paramStart = -1, mark = start;
|
||||||
let depth = 0;
|
|
||||||
|
|
||||||
for(let i = start; i < input.length; ++i) {
|
for(let i = start; i < input.length; ++i) {
|
||||||
const c = input[i];
|
const c = input[i];
|
||||||
++this._column;
|
++this._column;
|
||||||
|
@ -139,83 +136,64 @@ export class BBCodeParser {
|
||||||
this._column = 1;
|
this._column = 1;
|
||||||
}
|
}
|
||||||
if(c === '[') {
|
if(c === '[') {
|
||||||
depth++;
|
tagStart = i;
|
||||||
|
paramStart = -1;
|
||||||
if (depth === 1) {
|
} else if(c === '=' && paramStart === -1)
|
||||||
tagStart = i;
|
paramStart = i;
|
||||||
paramStart = -1;
|
else if(c === ']') {
|
||||||
} else {
|
const paramIndex = paramStart === -1 ? i : paramStart;
|
||||||
// console.log('Hit depth tagOpen', depth);
|
let tagKey = input.substring(tagStart + 1, paramIndex).trim().toLowerCase();
|
||||||
|
if(tagKey.length === 0) {
|
||||||
|
tagStart = -1;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
} else if(c === '=' && paramStart === -1) {
|
const param = paramStart > tagStart ? input.substring(paramStart + 1, i).trim() : '';
|
||||||
if (depth <= 1) {
|
const close = tagKey[0] === '/';
|
||||||
paramStart = i;
|
if(close) tagKey = tagKey.substr(1).trim();
|
||||||
} else {
|
if(this._tags[tagKey] === undefined) {
|
||||||
// console.log('Hit depth paramStart', depth);
|
tagStart = -1;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
} else if(c === ']') {
|
if(!close) {
|
||||||
depth--;
|
const tag = this._tags[tagKey]!;
|
||||||
|
const allowed = isAllowed(tagKey);
|
||||||
if (depth !== 0) {
|
if(parent !== undefined) {
|
||||||
// console.log('Hit depth tagClose', depth);
|
parent.appendChild(document.createTextNode(input.substring(mark, allowed ? tagStart : i + 1)));
|
||||||
}
|
|
||||||
|
|
||||||
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)));
|
|
||||||
}
|
|
||||||
mark = i + 1;
|
mark = i + 1;
|
||||||
this._currentTag = currentTag;
|
}
|
||||||
if(element === undefined) continue;
|
if(!allowed || parent === undefined || depth > 100) {
|
||||||
(<HTMLElement & {bbcodeTag: string}>element).bbcodeTag = tagKey;
|
i = this.parse(input, i + 1, tag, parent, isAllowed, depth + 1);
|
||||||
if(param.length > 0) (<HTMLElement & {bbcodeParam: string}>element).bbcodeParam = param;
|
mark = i + 1;
|
||||||
} else if(self !== undefined) { //tslint:disable-line:curly
|
continue;
|
||||||
if(self.tag === tagKey) {
|
}
|
||||||
if(parent !== undefined)
|
let element: HTMLElement | undefined;
|
||||||
parent.appendChild(document.createTextNode(input.substring(mark, selfAllowed ? tagStart : i + 1)));
|
if(tag instanceof BBCodeTextTag) {
|
||||||
return i;
|
i = this.parse(input, i + 1, tag, undefined, isAllowed, depth + 1);
|
||||||
}
|
element = tag.createElement(this, parent, param, input.substring(mark, input.lastIndexOf('[', i)));
|
||||||
if(!selfAllowed) return mark - 1;
|
if(element === undefined) parent.appendChild(document.createTextNode(input.substring(tagStart, i + 1)));
|
||||||
if(isAllowed(tagKey))
|
} else {
|
||||||
this.warning(`Unexpected closing ${tagKey} tag. Needed ${self.tag} tag instead.`);
|
element = tag.createElement(this, parent, param, '');
|
||||||
} else if(isAllowed(tagKey)) this.warning(`Found closing ${tagKey} tag that was never opened.`);
|
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) {
|
if(mark < input.length && parent !== undefined) {
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
<label class="col-sm-2 col-form-label">{{l('logs.conversation')}}</label>
|
<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'">
|
<div :class="canZip ? 'col-sm-8 col-10 col-xl-9' : 'col-sm-10'">
|
||||||
<filterable-select v-model="selectedConversation" :options="conversations" :filterFunc="filterConversation"
|
<filterable-select v-model="selectedConversation" :options="conversations" :filterFunc="filterConversation"
|
||||||
:placeholder="l('filter')">
|
:placeholder="l('filter')">
|
||||||
<template slot-scope="s">
|
<template slot-scope="s">
|
||||||
{{s.option && ((s.option.key[0] == '#' ? '#' : '') + s.option.name) || l('logs.selectConversation')}}
|
{{s.option && ((s.option.key[0] == '#' ? '#' : '') + s.option.name) || l('logs.selectConversation')}}
|
||||||
</template>
|
</template>
|
||||||
|
@ -47,7 +47,8 @@
|
||||||
class="fa fa-download"></span></button>
|
class="fa fa-download"></span></button>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
<message-view v-for="message in displayedMessages" :message="message" :key="message.id" :logs="true"></message-view>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group" style="flex-shrink:0">
|
<div class="input-group" style="flex-shrink:0">
|
||||||
|
@ -77,8 +78,15 @@
|
||||||
return format(date, 'yyyy-MM-dd');
|
return format(date, 'yyyy-MM-dd');
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLogs(messages: ReadonlyArray<Conversation.Message>): string {
|
function getLogs(messages: ReadonlyArray<Conversation.Message>, html: boolean): string {
|
||||||
return messages.reduce((acc, x) => acc + messageToString(x, (date) => formatTime(date, true)), '');
|
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({
|
@Component({
|
||||||
|
@ -187,16 +195,18 @@
|
||||||
|
|
||||||
downloadDay(): void {
|
downloadDay(): void {
|
||||||
if(this.selectedConversation === undefined || this.selectedDate === undefined || this.messages.length === 0) return;
|
if(this.selectedConversation === undefined || this.selectedDate === undefined || this.messages.length === 0) return;
|
||||||
const name = `${this.selectedConversation.name}-${formatDate(new Date(this.selectedDate))}.txt`;
|
const html = confirm(l('logs.html'));
|
||||||
this.download(name, `data:${encodeURIComponent(name)},${encodeURIComponent(getLogs(this.messages))}`);
|
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> {
|
async downloadConversation(): Promise<void> {
|
||||||
if(this.selectedConversation === undefined) return;
|
if(this.selectedConversation === undefined) return;
|
||||||
const zip = new Zip();
|
const zip = new Zip();
|
||||||
|
const html = confirm(l('logs.html'));
|
||||||
for(const date of this.dates) {
|
for(const date of this.dates) {
|
||||||
const messages = await core.logs.getLogs(this.selectedCharacter, this.selectedConversation.key, date);
|
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()));
|
this.download(`${this.selectedConversation.name}.zip`, URL.createObjectURL(zip.build()));
|
||||||
}
|
}
|
||||||
|
@ -204,12 +214,13 @@
|
||||||
async downloadCharacter(): Promise<void> {
|
async downloadCharacter(): Promise<void> {
|
||||||
if(this.selectedCharacter === '' || !confirm(l('logs.confirmExport', this.selectedCharacter))) return;
|
if(this.selectedCharacter === '' || !confirm(l('logs.confirmExport', this.selectedCharacter))) return;
|
||||||
const zip = new Zip();
|
const zip = new Zip();
|
||||||
|
const html = confirm(l('logs.html'));
|
||||||
for(const conv of this.conversations) {
|
for(const conv of this.conversations) {
|
||||||
zip.addFile(`${conv.name}/`, '');
|
zip.addFile(`${conv.name}/`, '');
|
||||||
const dates = await core.logs.getLogDates(this.selectedCharacter, conv.key);
|
const dates = await core.logs.getLogDates(this.selectedCharacter, conv.key);
|
||||||
for(const date of dates) {
|
for(const date of dates) {
|
||||||
const messages = await core.logs.getLogs(this.selectedCharacter, conv.key, date);
|
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()));
|
this.download(`${this.selectedCharacter}.zip`, URL.createObjectURL(zip.build()));
|
||||||
|
@ -272,9 +283,11 @@
|
||||||
if(this.lockScroll) return;
|
if(this.lockScroll) return;
|
||||||
if(list === undefined || ev !== undefined && Math.abs(list.scrollTop - this.lastScroll) < 50) return;
|
if(list === undefined || ev !== undefined && Math.abs(list.scrollTop - this.lastScroll) < 50) return;
|
||||||
this.lockScroll = true;
|
this.lockScroll = true;
|
||||||
|
|
||||||
function getTop(index: number): number {
|
function getTop(index: number): number {
|
||||||
return (<HTMLElement>list!.children[index]).offsetTop;
|
return (<HTMLElement>list!.children[index]).offsetTop;
|
||||||
}
|
}
|
||||||
|
|
||||||
while(this.selectedConversation !== undefined && this.selectedDate === undefined && this.dialog.isShown) {
|
while(this.selectedConversation !== undefined && this.selectedDate === undefined && this.dialog.isShown) {
|
||||||
const oldHeight = list.scrollHeight, oldTop = list.scrollTop;
|
const oldHeight = list.scrollHeight, oldTop = list.scrollTop;
|
||||||
const oldFirst = this.displayedMessages[0];
|
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())}`;
|
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)}] `;
|
let text = `[${timeFormatter(msg.time)}] `;
|
||||||
if(msg.type !== Conversation.Message.Type.Event)
|
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 ? ':' : '');
|
(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 {
|
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,
|
characters: undefined,
|
||||||
conversations: undefined,
|
conversations: undefined,
|
||||||
state
|
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('channels', Channels(connection, core.characters));
|
||||||
data.register('conversations', Conversations());
|
data.register('conversations', Conversations());
|
||||||
|
|
||||||
|
data.watch(() => state.hiddenUsers, async(newValue) => {
|
||||||
|
if(data.settingsStore !== undefined) await data.settingsStore.set('hiddenUsers', newValue);
|
||||||
|
});
|
||||||
|
|
||||||
connection.onEvent('connecting', async() => {
|
connection.onEvent('connecting', async() => {
|
||||||
await data.reloadSettings();
|
await data.reloadSettings();
|
||||||
data.bbCodeParser = createBBCodeParser();
|
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.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.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.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.profile': 'Profile',
|
||||||
'user.message': 'Open conversation',
|
'user.message': 'Open conversation',
|
||||||
'user.messageJump': 'View conversation',
|
'user.messageJump': 'View conversation',
|
||||||
|
|
|
@ -510,7 +510,7 @@
|
||||||
|
|
||||||
get styling(): string {
|
get styling(): string {
|
||||||
try {
|
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) {
|
} catch(e) {
|
||||||
if((<Error & {code: string}>e).code === 'ENOENT' && this.settings.theme !== 'default') {
|
if((<Error & {code: string}>e).code === 'ENOENT' && this.settings.theme !== 'default') {
|
||||||
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'];
|
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]);
|
if(process.argv.length > 2) args.push('-s', '--sign-key', process.argv[2]);
|
||||||
else console.warn('Warning: Creating unsigned AppImage');
|
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);
|
fs.chmodSync(downloaded, 0o755);
|
||||||
child_process.spawn(downloaded, ['--appimage-extract'], {cwd: distDir}).on('close', () => {
|
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'}});
|
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 {
|
get styling(): string {
|
||||||
if(window.NativeView !== undefined) window.NativeView.setTheme(this.settings.theme);
|
if(window.NativeView !== undefined) window.NativeView.setTheme(this.settings.theme);
|
||||||
//tslint:disable-next-line:no-require-imports
|
//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> {
|
async login(): Promise<void> {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<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>
|
</project>
|
|
@ -2,8 +2,8 @@
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectModuleManager">
|
<component name="ProjectModuleManager">
|
||||||
<modules>
|
<modules>
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/android.iml" filepath="$PROJECT_DIR$/.idea/android.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$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
|
<module fileurl="file://$PROJECT_DIR$/mobile.android.iml" filepath="$PROJECT_DIR$/mobile.android.iml" />
|
||||||
</modules>
|
</modules>
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
|
@ -3,13 +3,13 @@ apply plugin: 'kotlin-android'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 28
|
compileSdkVersion 28
|
||||||
buildToolsVersion "28.0.3"
|
buildToolsVersion "29.0.3"
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "net.f_list.fchat"
|
applicationId "net.f_list.fchat"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 27
|
targetSdkVersion 27
|
||||||
versionCode 25
|
versionCode 28
|
||||||
versionName "3.0.12"
|
versionName "3.0.16"
|
||||||
}
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.2.30'
|
ext.kotlin_version = '1.3.60'
|
||||||
repositories {
|
repositories {
|
||||||
jcenter()
|
jcenter()
|
||||||
google()
|
google()
|
||||||
}
|
}
|
||||||
dependencies {
|
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"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-bin.zip
|
|
||||||
|
|
|
@ -112,11 +112,12 @@
|
||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
LastSwiftUpdateCheck = 0920;
|
LastSwiftUpdateCheck = 0920;
|
||||||
LastUpgradeCheck = 0920;
|
LastUpgradeCheck = 1130;
|
||||||
ORGANIZATIONNAME = "F-List";
|
ORGANIZATIONNAME = "F-List";
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
6CA94BA71FEFEE7800183A1A = {
|
6CA94BA71FEFEE7800183A1A = {
|
||||||
CreatedOnToolsVersion = 9.2;
|
CreatedOnToolsVersion = 9.2;
|
||||||
|
LastSwiftMigration = 1130;
|
||||||
ProvisioningStyle = Automatic;
|
ProvisioningStyle = Automatic;
|
||||||
SystemCapabilities = {
|
SystemCapabilities = {
|
||||||
com.apple.BackgroundModes = {
|
com.apple.BackgroundModes = {
|
||||||
|
@ -218,6 +219,7 @@
|
||||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
CLANG_WARN_COMMA = YES;
|
CLANG_WARN_COMMA = YES;
|
||||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
CLANG_WARN_EMPTY_BODY = YES;
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
@ -225,6 +227,7 @@
|
||||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
CLANG_WARN_INT_CONVERSION = YES;
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
CLANG_WARN_NON_LITERAL_NULL_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_LITERAL_CONVERSION = YES;
|
||||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
@ -275,6 +278,7 @@
|
||||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
CLANG_WARN_COMMA = YES;
|
CLANG_WARN_COMMA = YES;
|
||||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
CLANG_WARN_EMPTY_BODY = YES;
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
@ -282,6 +286,7 @@
|
||||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
CLANG_WARN_INT_CONVERSION = YES;
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
CLANG_WARN_NON_LITERAL_NULL_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_LITERAL_CONVERSION = YES;
|
||||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
@ -321,7 +326,7 @@
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "net.f-list.F-Chat";
|
PRODUCT_BUNDLE_IDENTIFIER = "net.f-list.F-Chat";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_VERSION = 4.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
|
@ -336,7 +341,7 @@
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "net.f-list.F-Chat";
|
PRODUCT_BUNDLE_IDENTIFIER = "net.f-list.F-Chat";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_VERSION = 4.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
|
|
|
@ -2,11 +2,9 @@ import UIKit
|
||||||
|
|
||||||
@UIApplicationMain
|
@UIApplicationMain
|
||||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
|
|
||||||
var window: UIWindow?
|
var window: UIWindow?
|
||||||
|
|
||||||
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
|
|
||||||
// Override point for customization after application launch.
|
// Override point for customization after application launch.
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -32,7 +30,4 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
func applicationWillTerminate(_ application: UIApplication) {
|
func applicationWillTerminate(_ application: UIApplication) {
|
||||||
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
|
// 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() {
|
override init() {
|
||||||
let session = AVAudioSession.sharedInstance();
|
let session = AVAudioSession.sharedInstance();
|
||||||
try! session.setCategory(AVAudioSessionCategoryPlayback, with: .mixWithOthers)
|
try! session.setCategory(AVAudioSession.Category.playback, options: .mixWithOthers)
|
||||||
player.volume = 0
|
player.volume = 0
|
||||||
player.numberOfLoops = -1;
|
player.numberOfLoops = -1;
|
||||||
player.play()
|
player.play()
|
||||||
|
|
|
@ -7,7 +7,7 @@ class Notification: NSObject, WKScriptMessageHandler, UNUserNotificationCenterDe
|
||||||
let center = UNUserNotificationCenter.current()
|
let center = UNUserNotificationCenter.current()
|
||||||
let baseDir = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
|
let baseDir = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
|
||||||
var webView: WKWebView!
|
var webView: WKWebView!
|
||||||
|
|
||||||
func userContentController(_ controller: WKUserContentController, didReceive message: WKScriptMessage) {
|
func userContentController(_ controller: WKUserContentController, didReceive message: WKScriptMessage) {
|
||||||
center.delegate = self
|
center.delegate = self
|
||||||
self.webView = message.webView
|
self.webView = message.webView
|
||||||
|
@ -29,14 +29,14 @@ class Notification: NSObject, WKScriptMessageHandler, UNUserNotificationCenterDe
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
|
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
|
||||||
if(response.actionIdentifier == UNNotificationDefaultActionIdentifier) {
|
if(response.actionIdentifier == UNNotificationDefaultActionIdentifier) {
|
||||||
webView.evaluateJavaScript("document.dispatchEvent(new CustomEvent('notification-clicked',{detail:{data:'\(response.notification.request.content.userInfo["data"]!)'}}))")
|
webView.evaluateJavaScript("document.dispatchEvent(new CustomEvent('notification-clicked',{detail:{data:'\(response.notification.request.content.userInfo["data"]!)'}}))")
|
||||||
}
|
}
|
||||||
completionHandler()
|
completionHandler()
|
||||||
}
|
}
|
||||||
|
|
||||||
func notify(_ notify: Bool, _ title: String, _ text: String, _ icon: String, _ sound: String?, _ data: String, _ cb: (String?) -> Void) {
|
func notify(_ notify: Bool, _ title: String, _ text: String, _ icon: String, _ sound: String?, _ data: String, _ cb: (String?) -> Void) {
|
||||||
if(!notify) {
|
if(!notify) {
|
||||||
if(sound != nil) {
|
if(sound != nil) {
|
||||||
|
@ -49,14 +49,14 @@ class Notification: NSObject, WKScriptMessageHandler, UNUserNotificationCenterDe
|
||||||
let content = UNMutableNotificationContent()
|
let content = UNMutableNotificationContent()
|
||||||
content.title = title
|
content.title = title
|
||||||
if(sound != nil) {
|
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.body = text
|
||||||
content.userInfo["data"] = data
|
content.userInfo["data"] = data
|
||||||
center.add(UNNotificationRequest(identifier: "1", content: content, trigger: UNTimeIntervalNotificationTrigger.init(timeInterval: 1, repeats: false)))
|
center.add(UNNotificationRequest(identifier: "1", content: content, trigger: UNTimeIntervalNotificationTrigger.init(timeInterval: 1, repeats: false)))
|
||||||
cb("1");
|
cb("1");
|
||||||
}
|
}
|
||||||
|
|
||||||
func requestPermission(_ cb: @escaping (String?) -> Void) {
|
func requestPermission(_ cb: @escaping (String?) -> Void) {
|
||||||
center.requestAuthorization(options: [.alert, .sound]) { (_, _) in
|
center.requestAuthorization(options: [.alert, .sound]) { (_, _) in
|
||||||
cb(nil)
|
cb(nil)
|
||||||
|
|
|
@ -29,13 +29,12 @@ class ViewController: UIViewController, WKNavigationDelegate, WKUIDelegate {
|
||||||
webView.uiDelegate = self
|
webView.uiDelegate = self
|
||||||
webView.navigationDelegate = self
|
webView.navigationDelegate = self
|
||||||
view = webView
|
view = webView
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardDidShow), name: NSNotification.Name.UIKeyboardDidShow, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardDidShow), name: UIResponder.keyboardDidShowNotification, object: nil)
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
|
||||||
webView.scrollView.contentInsetAdjustmentBehavior = .never
|
webView.scrollView.contentInsetAdjustmentBehavior = .never
|
||||||
webView.scrollView.bounces = false
|
webView.scrollView.bounces = false
|
||||||
UIApplication.shared.statusBarStyle = .lightContent
|
UIApplication.shared.statusBarStyle = .lightContent
|
||||||
(UIApplication.shared.value(forKey: "statusBar") as! UIView).backgroundColor = UIColor(white: 0, alpha: 0.5)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
|
@ -47,8 +46,8 @@ class ViewController: UIViewController, WKNavigationDelegate, WKUIDelegate {
|
||||||
|
|
||||||
@objc func keyboardWillShow(notification: NSNotification) {
|
@objc func keyboardWillShow(notification: NSNotification) {
|
||||||
let info = notification.userInfo!
|
let info = notification.userInfo!
|
||||||
let newHeight = view.window!.frame.height - (info[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue.height
|
let newHeight = view.window!.frame.height - (info[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue.height
|
||||||
UIView.animate(withDuration: (info[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue, animations: {
|
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)
|
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) {
|
@objc func keyboardWillHide(notification: NSNotification) {
|
||||||
let info = notification.userInfo!
|
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
|
self.webView.frame = UIApplication.shared.windows[0].frame
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -88,7 +87,7 @@ class ViewController: UIViewController, WKNavigationDelegate, WKUIDelegate {
|
||||||
decisionHandler(.cancel)
|
decisionHandler(.cancel)
|
||||||
let str = url.absoluteString
|
let str = url.absoluteString
|
||||||
if(url.scheme == "data") {
|
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!)
|
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)
|
try! str.suffix(from: str.index(after: start)).removingPercentEncoding!.write(to: file, atomically: false, encoding: .utf8)
|
||||||
let controller = UIActivityViewController(activityItems: [file], applicationActivities: nil)
|
let controller = UIActivityViewController(activityItems: [file], applicationActivities: nil)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "net.f_list.fchat",
|
"name": "net.f_list.fchat",
|
||||||
"version": "3.0.12",
|
"version": "3.0.16",
|
||||||
"displayName": "F-Chat",
|
"displayName": "F-Chat",
|
||||||
"author": "The F-List Team",
|
"author": "The F-List Team",
|
||||||
"description": "F-List.net Chat Client",
|
"description": "F-List.net Chat Client",
|
||||||
|
@ -11,4 +11,4 @@
|
||||||
"build:dist": "node ../webpack production",
|
"build:dist": "node ../webpack production",
|
||||||
"watch": "node ../webpack watch"
|
"watch": "node ../webpack watch"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,7 +71,7 @@ window.addEventListener('beforeunload', (e) => {
|
||||||
return l('chat.confirmLeave');
|
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
|
new Chat({ //tslint:disable-line:no-unused-expression
|
||||||
el: '#app',
|
el: '#app',
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "net.f_list.fchat",
|
"name": "net.f_list.fchat",
|
||||||
"version": "3.0.12",
|
"version": "3.0.16",
|
||||||
"displayName": "F-Chat",
|
"displayName": "F-Chat",
|
||||||
"author": "The F-List Team",
|
"author": "The F-List Team",
|
||||||
"description": "F-List.net Chat Client",
|
"description": "F-List.net Chat Client",
|
||||||
|
@ -11,4 +11,4 @@
|
||||||
"build:dist": "node ../webpack production",
|
"build:dist": "node ../webpack production",
|
||||||
"watch": "node ../webpack watch"
|
"watch": "node ../webpack watch"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,8 +36,8 @@ const config = {
|
||||||
{test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'file-loader'},
|
{test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'file-loader'},
|
||||||
{test: /\.(wav|mp3|ogg)$/, loader: 'file-loader?name=sounds/[name].[ext]'},
|
{test: /\.(wav|mp3|ogg)$/, loader: 'file-loader?name=sounds/[name].[ext]'},
|
||||||
{test: /\.(png|html)$/, loader: 'file-loader?name=[name].[ext]'},
|
{test: /\.(png|html)$/, loader: 'file-loader?name=[name].[ext]'},
|
||||||
{test: /\.scss/, use: ['vue-style-loader', 'css-loader', 'sass-loader']},
|
{test: /\.scss$/, use: ['vue-style-loader', 'css-loader', 'sass-loader']},
|
||||||
{test: /\.css/, use: ['vue-style-loader', 'css-loader']}
|
{test: /\.css$/, use: ['vue-style-loader', 'css-loader']},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
|
|
Loading…
Reference in New Issue