This commit is contained in:
MayaWolf 2018-08-18 21:37:53 +02:00
parent 7151bf916e
commit 39f9365299
22 changed files with 108 additions and 58 deletions

View File

@ -216,12 +216,14 @@
if(this.undoIndex === 0 && this.undoStack[0] !== this.text) this.undoStack.unshift(this.text);
if(this.undoStack.length > this.undoIndex + 1) {
this.text = this.undoStack[++this.undoIndex];
this.$emit('input', this.text);
this.lastInput = Date.now();
}
} else if(key === Keys.KeyY) {
e.preventDefault();
if(this.undoIndex > 0) {
this.text = this.undoStack[--this.undoIndex];
this.$emit('input', this.text);
this.lastInput = Date.now();
}
}

View File

@ -174,6 +174,7 @@
keypressHandler!: EventListener;
scrolledDown = true;
scrolledUp = false;
ignoreScroll = false;
adCountdown = 0;
adsMode = l('channel.mode.ads');
@ -184,11 +185,7 @@
icon: 'fa-question',
handler: () => (<CommandHelp>this.$refs['helpDialog']).show()
}];
window.addEventListener('resize', this.resizeHandler = () => {
if(this.scrolledDown)
this.messageView.scrollTop = this.messageView.scrollHeight - this.messageView.offsetHeight;
this.onMessagesScroll();
});
window.addEventListener('resize', this.resizeHandler = () => this.keepScroll());
window.addEventListener('keypress', this.keypressHandler = () => {
if(document.getSelection().isCollapsed && !anyDialogsShown &&
(document.activeElement === document.body || document.activeElement.tagName === 'A'))
@ -244,14 +241,14 @@
return this.conversation.messages.filter((x) => filter.test(x.text));
}
sendButton(): void {
setImmediate(async() => this.conversation.send());
async sendButton(): Promise<void> {
return this.conversation.send();
}
@Watch('conversation')
conversationChanged(): void {
if(!anyDialogsShown) (<Editor>this.$refs['textBox']).focus();
setTimeout(() => this.messageView.scrollTop = this.messageView.scrollHeight - this.messageView.offsetHeight);
this.$nextTick(() => setTimeout(() => this.messageView.scrollTop = this.messageView.scrollHeight));
this.scrolledDown = true;
}
@ -265,10 +262,17 @@
keepScroll(): void {
if(this.scrolledDown)
this.$nextTick(() => setTimeout(() => this.messageView.scrollTop = this.messageView.scrollHeight, 0));
this.$nextTick(() => setTimeout(() => {
this.ignoreScroll = true;
this.messageView.scrollTop = this.messageView.scrollHeight;
}, 0));
}
onMessagesScroll(): void {
if(this.ignoreScroll) {
this.ignoreScroll = false;
return;
}
if(this.messageView.scrollTop < 20) {
if(!this.scrolledUp) {
const firstMessage = this.messageView.firstElementChild;
@ -334,7 +338,7 @@
else if(getKey(e) === Keys.Enter) {
if(e.shiftKey === this.settings.enterSend) return;
e.preventDefault();
setImmediate(async() => this.conversation.send());
await this.conversation.send();
}
}
}

View File

@ -59,6 +59,10 @@ abstract class Conversation implements Interfaces.Conversation {
state.savePinned(); //tslint:disable-line:no-floating-promises
}
clearText(): void {
setImmediate(() => this.enteredText = '');
}
async send(): Promise<void> {
if(this.enteredText.length === 0) return;
if(isCommand(this.enteredText)) {
@ -67,7 +71,7 @@ abstract class Conversation implements Interfaces.Conversation {
else {
parsed.call(this);
this.lastSent = this.enteredText;
this.enteredText = '';
this.clearText();
}
} else {
this.lastSent = this.enteredText;
@ -186,7 +190,7 @@ class PrivateConversation extends Conversation implements Interfaces.PrivateConv
const message = createMessage(MessageType.Message, core.characters.ownCharacter, this.enteredText);
this.safeAddMessage(message);
if(core.state.settings.logMessages) await core.logs.logMessage(this, message);
this.enteredText = '';
this.clearText();
}
private setOwnTyping(status: Interfaces.TypingStatus): void {
@ -257,8 +261,8 @@ class ChannelConversation extends Conversation implements Interfaces.ChannelConv
}
addModeMessage(mode: Channel.Mode, message: Interfaces.Message): void {
if(this._mode === mode) this.safeAddMessage(message);
else safeAddMessage(this[mode], message, 500);
safeAddMessage(this[mode], message, 500);
if(this._mode === mode) safeAddMessage(this.messages, message, this.maxMessages);
}
async addMessage(message: Interfaces.Message): Promise<void> {
@ -283,6 +287,8 @@ class ChannelConversation extends Conversation implements Interfaces.ChannelConv
} else this.addModeMessage('ads', message);
}
this.addModeMessage('both', message);
if(message.type !== Interfaces.Message.Type.Event)
safeAddMessage(this.reportMessages, message, 500);
}
clear(): void {
@ -310,7 +316,7 @@ class ChannelConversation extends Conversation implements Interfaces.ChannelConv
createMessage(isAd ? MessageType.Ad : MessageType.Message, core.characters.ownCharacter, this.enteredText, new Date()));
if(isAd)
this.nextAd = Date.now() + core.connection.vars.lfrp_flood * 1000;
else this.enteredText = '';
else this.clearText();
}
}

View File

@ -355,10 +355,36 @@ const commands: {readonly [key: string]: Command | undefined} = {
params: [{type: ParamType.String, delimiter: ' '}, {type: ParamType.String}]
},
elf: {
exec: (conv: Conversation) => conv.infoText =
'Now no one can say there\'s "not enough Elf." It\'s a well-kept secret, but elves love headpets. You should try it sometime.',
exec: (conv: Conversation) => conv.infoText = elf[Math.floor(Math.random() * elf.length)],
documented: false
}
};
const elf = [ //Ceran is to be thanked for most of these.
`Now no one can say there's "not enough Elf." It's a well-kept secret, but elves love headpets. You should try it sometime.`,
`Your task for the day: provide a soft cushion in a sunny area for maximum elf comfort, earn +2 Bliss pts.`,
`Your task for the day: pet an elf at least (3) times, receive Good Karma.`,
`Your task for the day: make dinner for an elf, receive +3 Luck pts.`,
`The reason that elves' ears are so long is so that they can better hear your sins. Now that's an Elf Fact!`,
`A "straight" elf is basically an oxymoron.`,
`Don't forget to water your elf today!`,
`Please don't let your elf eat out of the trash can.`,
`Elves are not allowed to have soda after 12pm, but they will anyway.`,
`Pet an elf on the ears (4) times. Any more and the elf will bite. Now that's an Elf Fact!`,
`There are two kinds of people in the world. People who like elves. And people with questionable taste.`,
`Love yourself, pet an elf!!!`,
`Your task for the day: leave out snacks for your local elves, they'll help dispel vermin!`,
`An elf in your home will discourage the presence of predators! Or summon them. I forget which.`,
`If you crack open an elf, there's just a smaller elf within. It's just smaller and smaller elves all the way down.`,
`In case of an emergency, an elf's ass can be used as a flotation device. Now that's an Elf Fact!`,
`Running low on inventory space? An elf can be used to slot additional cylindrical artifacts! Now that's an Elf Fact!`,
`The average elf can consume half their body weight in cum. Now that's an Elf Fact!`,
`Your task for the day: subjugate yourself to your elven overlords in order to achieve righteous bliss.`,
`Your task for the day: Rub an elf's tummy. Be wary of them bear-trapping your arm as the forces of darkness consume their soul.`,
`Listen, that elfroot you found in my sock drawer is PURELY medicinal!`,
`What are elves? We just don't know.`,
`As long as you pet an elf's head, they will be content. What will happen if you stop? No one's ever come back to tell the tale.`,
`Elves are very helpful creatures. Just ask them to carry something for you, and they will. Especially eggs.`
];
export default commands;

View File

@ -267,7 +267,7 @@
height: 100%;
}
*:not([draggable]), *::after, *::before {
a[href^="#"]:not([draggable]) {
-webkit-user-drag: none;
-webkit-app-region: no-drag;
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -175,11 +175,12 @@ let dictDir = path.join(electron.remote.app.getPath('userData'), 'spellchecker')
if(process.platform === 'win32')
exec(`for /d %I in ("${dictDir}") do @echo %~sI`, (_, stdout) => { dictDir = stdout.trim(); });
electron.webFrame.setSpellCheckProvider('', false, {spellCheck: (text) => !spellchecker.isMisspelled(text)});
electron.ipcRenderer.on('settings', async(_: Event, s: GeneralSettings) => {
function onSettings(s: GeneralSettings): void {
settings = s;
spellchecker.setDictionary(s.spellcheckLang, dictDir);
for(const word of s.customDictionary) spellchecker.add(word);
});
}
electron.ipcRenderer.on('settings', (_: Event, s: GeneralSettings) => onSettings(s));
const params = <{[key: string]: string | undefined}>qs.parse(window.location.search.substr(1));
let settings = <GeneralSettings>JSON.parse(params['settings']!);
@ -192,7 +193,7 @@ if(params['import'] !== undefined)
} catch {
alert(l('importer.error'));
}
onSettings(settings);
//tslint:disable-next-line:no-unused-expression
new Index({
el: '#app',

View File

@ -163,7 +163,7 @@ function loadIndex(name: string): Index {
const dir = getLogDir(name);
const files = fs.readdirSync(dir);
for(const file of files)
if(file.substr(-4) === '.idx') {
if(file.substr(-4) === '.idx')
try {
const content = fs.readFileSync(path.join(dir, file));
let offset = content.readUInt8(0, noAssert) + 1;
@ -178,10 +178,10 @@ function loadIndex(name: string): Index {
item.offsets.push(content.readUIntLE(offset + 2, 5, noAssert));
}
index[file.slice(0, -4).toLowerCase()] = item;
} catch {
} catch(e) {
console.error(e);
alert(l('logs.corruption.desktop'));
}
}
return index;
}
@ -215,7 +215,8 @@ export class Logs implements Logging {
}
if(count !== 0) messages = messages.slice(count);
return messages;
} catch {
} catch(e) {
console.error(e);
alert(l('logs.corruption.desktop'));
return [];
} finally {
@ -253,7 +254,6 @@ export class Logs implements Logging {
const length = end - pos;
const buffer = Buffer.allocUnsafe(length);
await read(fd, buffer, 0, length, pos);
fs.closeSync(fd);
let offset = 0;
while(offset < length) {
const deserialized = deserializeMessage(buffer, offset);
@ -261,7 +261,8 @@ export class Logs implements Logging {
offset += deserialized.size;
}
return messages;
} catch {
} catch(e) {
console.error(e);
alert(l('logs.corruption.desktop'));
return [];
} finally {

View File

@ -29,12 +29,13 @@ function mkdir(dir) {
const distDir = path.join(__dirname, 'dist');
const isBeta = pkg.version.indexOf('beta') !== -1;
const spellcheckerPath = 'node_modules/spellchecker/build/Release/spellchecker.node',
keytarPath = 'node_modules/keytar/build/Release/keytar.node';
mkdir(path.dirname(path.join(__dirname, 'app', spellcheckerPath)));
mkdir(path.dirname(path.join(__dirname, 'app', keytarPath)));
fs.copyFileSync(spellcheckerPath, path.join(__dirname, 'app', spellcheckerPath));
fs.copyFileSync(keytarPath, path.join(__dirname, 'app', keytarPath));
const spellcheckerPath = 'spellchecker/build/Release/spellchecker.node', keytarPath = 'keytar/build/Release/keytar.node';
const modules = path.join(__dirname, 'app', 'node_modules');
mkdir(path.dirname(path.join(modules, spellcheckerPath)));
mkdir(path.dirname(path.join(modules, keytarPath)));
fs.copyFileSync(require.resolve(spellcheckerPath), path.join(modules, spellcheckerPath));
fs.copyFileSync(require.resolve(keytarPath), path.join(modules, keytarPath));
require('electron-packager')({
dir: path.join(__dirname, 'app'),
@ -101,16 +102,22 @@ require('electron-packager')({
console.log('Creating Linux AppImage');
fs.renameSync(path.join(appPaths[0], 'F-Chat'), path.join(appPaths[0], 'AppRun'));
fs.copyFileSync(path.join(__dirname, 'build', 'icon.png'), path.join(appPaths[0], 'icon.png'));
const libDir = path.join(appPaths[0], 'usr', 'lib'), libSource = path.join(__dirname, 'build', 'linux-libs');
mkdir(libDir);
for(const file of fs.readdirSync(libSource))
fs.copyFileSync(path.join(libSource, file), path.join(libDir, file));
fs.symlinkSync(path.join(appPaths[0], 'icon.png'), path.join(appPaths[0], '.DirIcon'));
fs.writeFileSync(path.join(appPaths[0], 'fchat.desktop'), '[Desktop Entry]\nName=F-Chat\nExec=AppRun\nIcon=icon\nType=Application\nCategories=GTK;GNOME;Utility;');
require('axios').get('https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage', {responseType: 'stream'}).then((res) => {
const downloaded = path.join(distDir, 'appimagetool.AppImage');
res.data.pipe(fs.createWriteStream(downloaded));
res.data.on('end', () => {
const args = [appPaths[0], 'fchat.AppImage', '-u', 'zsync|https://client.f-list.net/fchat.AppImage.zsync'];
const stream = fs.createWriteStream(downloaded);
res.data.pipe(stream);
stream.on('close', () => {
const args = [appPaths[0], 'fchat.AppImage', '-u', 'zsync|httpos://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', `--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});
child.stdout.on('data', (data) => console.log(data.toString()));

View File

@ -1,6 +1,6 @@
{
"name": "fchat",
"version": "3.0.7",
"version": "3.0.8",
"author": "The F-List Team",
"description": "F-List.net Chat Client",
"main": "main.js",

View File

@ -8,8 +8,8 @@ android {
applicationId "net.f_list.fchat"
minSdkVersion 19
targetSdkVersion 27
versionCode 18
versionName "3.0.7"
versionCode 19
versionName "3.0.8"
}
buildTypes {
release {

View File

@ -16,10 +16,8 @@ import android.webkit.JsResult
import android.webkit.WebChromeClient
import android.webkit.WebView
import android.webkit.WebViewClient
import org.json.JSONTokener
import java.io.FileOutputStream
import java.net.URLDecoder
import java.util.*
class MainActivity : Activity() {
@ -55,7 +53,7 @@ class MainActivity : Activity() {
}
webView.webChromeClient = object : WebChromeClient() {
override fun onJsAlert(view: WebView, url: String, message: String, result: JsResult): Boolean {
AlertDialog.Builder(this@MainActivity).setTitle(R.string.app_name).setMessage(message).setPositiveButton(R.string.ok, { _, _ -> result.confirm() }).show()
AlertDialog.Builder(this@MainActivity).setTitle(R.string.app_name).setMessage(message).setPositiveButton(R.string.ok, { _, _ -> }).setOnDismissListener({ result.confirm() }).show()
return true
}

View File

@ -8,11 +8,13 @@ import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.media.AudioAttributes
import android.media.AudioManager
import android.media.MediaPlayer
import android.os.AsyncTask
import android.os.Build
import android.os.Vibrator
import android.provider.Settings
import android.webkit.JavascriptInterface
import java.net.URL
@ -20,19 +22,12 @@ class Notifications(private val ctx: Context) {
init {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val manager = ctx.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager;
manager.createNotificationChannel(NotificationChannel("messages", ctx.getString(R.string.channel_messages), NotificationManager.IMPORTANCE_LOW))
manager.createNotificationChannel(NotificationChannel("messages", ctx.getString(R.string.channel_messages), NotificationManager.IMPORTANCE_DEFAULT))
}
}
@JavascriptInterface
fun notify(notify: Boolean, title: String, text: String, icon: String, sound: String?, data: String?): Int {
if(!notify) {
val vibrator = (ctx.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator)
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
vibrator.vibrate(400, Notification.AUDIO_ATTRIBUTES_DEFAULT)
else vibrator.vibrate(400)
return 0
}
if(sound != null) {
val player = MediaPlayer()
val asset = ctx.assets.openFd("www/sounds/$sound.mp3")
@ -42,6 +37,15 @@ class Notifications(private val ctx: Context) {
player.start()
player.setOnCompletionListener { it.release() }
}
if(!notify) {
if((ctx.getSystemService(Context.AUDIO_SERVICE) as AudioManager).ringerMode != AudioManager.RINGER_MODE_SILENT) {
val vibrator = (ctx.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator)
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
vibrator.vibrate(400, Notification.AUDIO_ATTRIBUTES_DEFAULT)
else vibrator.vibrate(400)
}
return 0
}
val intent = Intent(ctx, MainActivity::class.java)
intent.action = "notification"
intent.putExtra("data", data)

View File

@ -1,6 +1,6 @@
{
"name": "net.f_list.fchat",
"version": "3.0.7",
"version": "3.0.8",
"displayName": "F-Chat",
"author": "The F-List Team",
"description": "F-List.net Chat Client",

View File

@ -20,7 +20,7 @@ See https://electron.atom.io/docs/tutorial/application-distribution/
- Run `yarn pack`. The generated installer is placed into the `dist` directory.
- On Windows you can add the path to and password for a code signing certificate as arguments.
- On Mac you can add your code signing identity as an argument. `zip` is required to be installed.
- On Linux you can add a GPG key for signing as an argument. `mksquashfs` and `zsyncmake` are required to be installed.
- On Linux you can add a GPG key for signing and its password as arguments. `mksquashfs` and `zsyncmake` are required to be installed.
## Building for Mobile
- Change into the `mobile` directory.

View File

@ -2,10 +2,10 @@
<div class="row character-page" id="pageBody">
<div class="alert alert-info" v-show="loading" style="margin:0 15px;flex:1">Loading character information.</div>
<div class="alert alert-danger" v-show="error" style="margin:0 15px;flex:1">{{error}}</div>
<div class="col-md-4 col-lg-3 col-xl-2" v-if="!loading">
<div class="col-md-4 col-lg-3 col-xl-2" v-if="!loading && character">
<sidebar :character="character" @memo="memo" @bookmarked="bookmarked" :oldApi="oldApi"></sidebar>
</div>
<div class="col-md-8 col-lg-9 col-xl-10 profile-body" v-if="!loading">
<div class="col-md-8 col-lg-9 col-xl-10 profile-body" v-if="!loading && character">
<div id="characterView">
<div>
<div v-if="character.ban_reason" id="headerBanReason" class="alert alert-warning">
@ -146,6 +146,8 @@
}
private async _getCharacter(): Promise<void> {
this.error = '';
this.character = null;
if(this.name === undefined || this.name.length === 0)
return;
try {
@ -154,12 +156,11 @@
this.character = await methods.characterData(this.name, this.characterid);
standardParser.allowInlines = true;
standardParser.inlines = this.character.character.inlines;
this.loading = false;
} catch(e) {
if(Utils.isJSONError(e))
this.error = <string>e.response.data.error;
this.error = Utils.isJSONError(e) ? <string>e.response.data.error : (<Error>e).message;
Utils.ajaxError(e, 'Failed to load character information.');
}
this.loading = false;
}
}
</script>

View File

@ -1,6 +1,6 @@
{
"name": "net.f_list.fchat",
"version": "3.0.7",
"version": "3.0.8",
"displayName": "F-Chat",
"author": "The F-List Team",
"description": "F-List.net Chat Client",