Merge pull request #224 from greyhoof/browser-option

Browser option
This commit is contained in:
hearmeneigh 2023-09-29 12:29:16 -07:00 committed by GitHub
commit 4f93b944a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 364 additions and 11 deletions

View File

@ -189,6 +189,14 @@ Current log location: {1}`,
'settings.beta': 'Opt-in to test unstable prerelease updates',
'settings.hwAcceleration': 'Enable hardware acceleration (requires restart)',
'settings.bbCodeBar': 'Show BBCode formatting bar',
'settings.browserOption': 'Set command for opening clicked links',
'settings.browserOptionHeader': 'Browser Settings',
'settings.browserOptionTitle': 'Browser Path',
'settings.browserOptionPath': 'Path to browser executable',
'settings.browserOptionArguments': 'Arguments to pass to executable',
'settings.browserOptionArgumentsHelp': 'Arguments are separated by spaces. Use %s to insert the URL.',
'settings.browserOptionBrowse': 'Browse',
'settings.browserOptionSave': 'Save',
'fixLogs.action': 'Fix corrupted logs',
'fixLogs.text': `There are a few reason log files can become corrupted - log files from old versions with bugs that have since been fixed or incomplete file operations caused by computer crashes are the most common.
If one of your log files is corrupted, you may get an "Unknown Type" error when you log in or when you open a specific tab. You may also experience other issues.

204
electron/BrowserOption.vue Normal file
View File

@ -0,0 +1,204 @@
<template>
<div class="card-full" style="display:flex;flex-direction:column;height:100%;" :class="getThemeClass()" @auxclick.prevent>
<div v-html="styling"></div>
<div style="display:flex;align-items:stretch;border-bottom-width:1px" class="border-bottom" id="window-browser-settings">
<h4 style="padding:2px 0">{{l('settings.browserOptionHeader')}}</h4>
<div style="flex:1;display:flex;justify-content:flex-end;-webkit-app-region:drag" class="btn-group"
id="windowButtons">
<i class="far fa-window-minimize btn btn-light" @click.stop="minimize()"></i>
<!-- <i class="far btn btn-light" :class="'fa-window-' + (isMaximized ? 'restore' : 'maximize')" @click="maximize()"></i>-->
<span class="btn btn-light" @click.stop="close()">
<i class="fa fa-times fa-lg"></i>
</span>
</div>
</div>
<div class="bg-light" style="display:flex; flex-direction: column; height:100%; justify-content: center; margin: 0;">
<div class="card bg-light" style="height:100%;width:100%;">
<div class="card-body row" style="height:100%;width:100%;">
<h4 class="card-title">{{l('settings.browserOptionTitle')}}</h4>
<div class="form-group col-12">
<label class="control-label" for="browserPath">{{l('settings.browserOptionPath')}}</label>
<div class="row">
<div class="col-10">
<input class="form-control" id="browserPath" v-model="browserPath"/>
</div>
<div class="col-2">
<button class="btn btn-primary" @click.prevent.stop="browseForPath()">{{l('settings.browserOptionBrowse')}}</button>
</div>
</div>
</div>
<div class="form-group col-12">
<label class="control-label" for="browserArgs">{{l('settings.browserOptionArguments')}}</label>
<div class="row">
<div class="col-12">
<input class="form-control" id="browserArgs" v-model="browserArgs"/>
</div>
</div>
<div class="row">
<div class="col-12">
<small class="form-text text-muted">{{l('settings.browserOptionArgumentsHelp')}}</small>
</div>
</div>
</div>
<div class="form-group col-12">
<div class="row">
<div class="col-2">
<button class="btn btn-primary" @click.prevent.stop="submit()">{{l('settings.browserOptionSave')}}</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import * as electron from 'electron';
import {Component, Hook} from '@f-list/vue-ts';
import * as remote from '@electron/remote';
import Vue from 'vue';
import l from '../chat/localize';
import {GeneralSettings} from './common';
import fs from "fs";
import path from "path";
import Modal from "../components/Modal.vue";
import modal from "../components/Modal.vue";
import tabs from "../components/tabs";
import logs from "../chat/Logs.vue";
import chat from "../chat/ChatView.vue";
import {ipcRenderer} from "electron";
import {EIconStore} from "../learn/eicon/store";
import log from "electron-log";
const browserWindow = remote.getCurrentWindow();
@Component({
components: {chat, logs, modal, tabs, Modal}
})
export default class BrowserOption extends Vue {
settings!: GeneralSettings;
isMaximized = false;
l = l;
platform = process.platform;
hasCompletedUpgrades = false;
browserPath = '';
browserArgs = '';
get styling(): string {
try {
return `<style>${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';
return this.styling;
}
throw e;
}
}
@Hook('mounted')
async mounted(): Promise<void> {
this.browserPath = this.settings.browserPath;
this.browserArgs = this.settings.browserArgs;
}
minimize(): void {
browserWindow.minimize();
}
maximize(): void {
if (browserWindow.isMaximized()) browserWindow.unmaximize();
else browserWindow.maximize();
}
close(): void {
browserWindow.close();
}
getThemeClass() {
// console.log('getThemeClassWindow', this.settings?.risingDisableWindowsHighContrast);
try {
// Hack!
if (process.platform === 'win32') {
if (this.settings?.risingDisableWindowsHighContrast) {
document.querySelector('html')?.classList.add('disableWindowsHighContrast');
} else {
document.querySelector('html')?.classList.remove('disableWindowsHighContrast');
}
}
return {
['platform-' + this.platform]: true,
disableWindowsHighContrast: this.settings?.risingDisableWindowsHighContrast || false
};
} catch (err) {
return {
['platform-' + this.platform]: true
};
}
}
submit(): void {
ipcRenderer.send('browser-option-update', this.browserPath, this.browserArgs);
this.close();
}
browseForPath(): void {
ipcRenderer.invoke('browser-option-browse').then((result) => {
this.browserPath = result;
});
}
}
</script>
<style lang="scss">
.card-full {
height: 100%;
left: 0;
position: fixed;
top: 0;
width: 100%;
z-index: 100;
}
#windowButtons .btn {
border-top: 0;
font-size: 14px;
}
#window-browser-settings {
user-select: none;
.btn {
border: 0;
border-radius: 0;
padding: 0 18px;
display: flex;
align-items: center;
line-height: 1;
-webkit-app-region: no-drag;
flex-grow: 0;
}
.btn-default {
background: transparent;
}
h4 {
margin: 0 10px;
user-select: none;
cursor: default;
align-self: center;
-webkit-app-region: drag;
}
.fa {
line-height: inherit;
}
}
.disableWindowsHighContrast, .disableWindowsHighContrast * {
forced-color-adjust: none;
}
</style>

View File

@ -496,7 +496,8 @@
}
async openProfileInBrowser(): Promise<void> {
await remote.shell.openExternal(`https://www.f-list.net/c/${this.profileName}`);
electron.ipcRenderer.send('open-url-externally', `https://www.f-list.net/c/${this.profileName}`);
//await remote.shell.openExternal(`https://www.f-list.net/c/${this.profileName}`);
// tslint:disable-next-line: no-any no-unsafe-any
(this.$refs.profileViewer as any).hide();
@ -628,7 +629,8 @@
async openWordDefinitionInBrowser(): Promise<void> {
await remote.shell.openExternal((this.$refs.wordDefinitionLookup as any).getWebUrl());
electron.ipcRenderer.send('open-url-externally', (this.$refs.wordDefinitionLookup as any).getWebUrl());
//await remote.shell.openExternal((this.$refs.wordDefinitionLookup as any).getWebUrl());
// tslint:disable-next-line: no-any no-unsafe-any
(this.$refs.wordDefinitionViewer as any).hide();

View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline'; img-src https://static.f-list.net">
<title>F-Chat</title>
<link href="fa.css" rel="stylesheet">
</head>
<body>
<div id="browserOption"></div>
<script type="text/javascript" src="common.js"></script>
<script type="text/javascript" src="browser_option.js"></script>
</body>
</html>

View File

@ -0,0 +1,25 @@
import * as qs from 'querystring';
import log from 'electron-log'; //tslint:disable-line:match-default-export-name
import {GeneralSettings} from './common';
import BrowserOption from './BrowserOption.vue';
log.info('init.browser_option');
const params = <{[key: string]: string | undefined}>qs.parse(window.location.search.substr(1));
const settings = <GeneralSettings>JSON.parse(params['settings']!);
const logLevel = (process.env.NODE_ENV === 'production') ? 'info' : 'silly';
log.transports.file.level = settings.risingSystemLogLevel || logLevel;
log.transports.console.level = settings.risingSystemLogLevel || logLevel;
log.transports.file.maxSize = 5 * 1024 * 1024;
log.info('init.browser_option.vue');
new BrowserOption({
el: '#browserOption',
data: {settings}
});
log.debug('init.browser_option.vue.done');

View File

@ -31,6 +31,8 @@ export class GeneralSettings {
risingCacheExpiryDays = 30;
risingSystemLogLevel: log.LevelOption = 'info';
risingDisableWindowsHighContrast = false;
browserPath = '';
browserArgs = '%s';
}
// //tslint:disable

View File

@ -53,6 +53,7 @@ import DownloadItem = electron.DownloadItem;
import { AdCoordinatorHost } from '../chat/ads/ad-coordinator-host';
import { IpcMainEvent } from 'electron';
import { BlockerIntegration } from './blocker/blocker';
import core from "../chat/core";
//tslint:disable-next-line:no-require-imports
const pck = require('./package.json');
@ -171,14 +172,43 @@ async function addSpellcheckerItems(menu: electron.Menu): Promise<void> {
}));
}
function openURLExternally(linkUrl: string): void {
// check if user set a path, whether it exists and if it is a file
if(settings.browserPath !== '' &&
fs.existsSync(settings.browserPath) &&
fs.lstatSync(settings.browserPath).isFile()) {
// encode URL so if it contains spaces, it remains a single argument for the browser
linkUrl = encodeURI(linkUrl);
if(!settings.browserArgs.includes('%s')) {
// append %s to params if it is not already there
settings.browserArgs += ' %s';
}
// replace %s in arguments with URL and encapsulate in quotes to prevent issues with spaces and special characters in the path
let link = settings.browserArgs.replace('%s', '\"'+linkUrl+'\"');
const execFile = require('child_process').exec;
execFile(`"${settings.browserPath}" ${link}`);
} else {
electron.shell.openExternal(linkUrl);
}
}
function setUpWebContents(webContents: electron.WebContents): void {
remoteMain.enable(webContents);
const openLinkExternally = (e: Event, linkUrl: string) => {
e.preventDefault();
const profileMatch = linkUrl.match(/^https?:\/\/(www\.)?f-list.net\/c\/([^/#]+)\/?#?/);
if(profileMatch !== null && settings.profileViewer) webContents.send('open-profile', decodeURIComponent(profileMatch[2]));
else return electron.shell.openExternal(linkUrl);
if(profileMatch !== null && settings.profileViewer) {
webContents.send('open-profile', decodeURIComponent(profileMatch[2]));
return;
}
// otherwise, try to open externally
openURLExternally(linkUrl);
};
webContents.setVisualZoomLevelLimits(1, 5);
@ -281,7 +311,39 @@ function createWindow(): electron.BrowserWindow | undefined {
function showPatchNotes(): void {
//tslint:disable-next-line: no-floating-promises
electron.shell.openExternal('https://github.com/hearmeneigh/fchat-rising/blob/master/CHANGELOG.md');
openURLExternally('https://github.com/hearmeneigh/fchat-rising/blob/master/CHANGELOG.md');
}
function openBrowserSettings(): electron.BrowserWindow | undefined {
const windowProperties: electron.BrowserWindowConstructorOptions = {
center: true,
show: false,
icon: process.platform === 'win32' ? winIcon : pngIcon,
frame: false,
width: 500,
height: 350,
minWidth: 500,
minHeight: 368,
maxWidth: 500,
maxHeight: 368,
maximizable: false,
webPreferences: {
webviewTag: true, nodeIntegration: true, nodeIntegrationInWorker: true, spellcheck: true,
enableRemoteModule: true, contextIsolation: false, partition: 'persist:fchat'
} as any
};
const browserWindow = new electron.BrowserWindow(windowProperties);
remoteMain.enable(browserWindow.webContents);
browserWindow.loadFile(path.join(__dirname, 'browser_option.html'), {
query: { settings: JSON.stringify(settings), import: shouldImportSettings ? 'true' : '' }
});
browserWindow.once('ready-to-show', () => {
browserWindow.show();
});
return browserWindow;
}
@ -529,6 +591,12 @@ function onReady(): void {
settings.risingDisableWindowsHighContrast = item.checked;
setGeneralSettings(settings);
}
},
{
label: l('settings.browserOption'),
click: () => {
openBrowserSettings();
}
}
]
},
@ -576,23 +644,23 @@ function onReady(): void {
submenu: [
{
label: l('help.fchat'),
click: () => electron.shell.openExternal('https://github.com/hearmeneigh/fchat-rising/blob/master/README.md')
click: () => openURLExternally('https://github.com/hearmeneigh/fchat-rising/blob/master/README.md')
},
// {
// label: l('help.feedback'),
// click: () => electron.shell.openExternal('https://goo.gl/forms/WnLt3Qm3TPt64jQt2')
// click: () => openURLExternally('https://goo.gl/forms/WnLt3Qm3TPt64jQt2')
// },
{
label: l('help.rules'),
click: () => electron.shell.openExternal('https://wiki.f-list.net/Rules')
click: () => openURLExternally('https://wiki.f-list.net/Rules')
},
{
label: l('help.faq'),
click: () => electron.shell.openExternal('https://wiki.f-list.net/Frequently_Asked_Questions')
click: () => openURLExternally('https://wiki.f-list.net/Frequently_Asked_Questions')
},
{
label: l('help.report'),
click: () => electron.shell.openExternal('https://wiki.f-list.net/How_to_Report_a_User#In_chat')
click: () => openURLExternally('https://wiki.f-list.net/How_to_Report_a_User#In_chat')
},
{label: l('version', app.getVersion()), click: showPatchNotes}
]
@ -665,6 +733,35 @@ function onReady(): void {
for(const w of electron.webContents.getAllWebContents()) w.send('update-zoom', zl);
});
electron.ipcMain.handle('browser-option-browse', async () => {
log.debug('settings.browserOption.browse');
console.log('settings.browserOption.browse', JSON.stringify(settings));
const dir = electron.dialog.showOpenDialogSync(
{
defaultPath: settings.browserPath,
properties: ['openFile'],
filters: [{ name: 'Executables', extensions: ['exe'] }]
});
if(dir !== undefined) {
return dir[0];
}
// we keep the current path if the user cancels the dialog
return settings.browserPath;
});
electron.ipcMain.on('browser-option-update', (_e, _path: string, _args: string) => {
log.debug('Browser Path settings update:', _path, _args);
// store the new path and args in our general settings
settings.browserPath = _path;
settings.browserArgs = _args;
setGeneralSettings(settings);
});
electron.ipcMain.on('open-url-externally', (_e, _url: string) => {
openURLExternally(_url);
});
createWindow();
}

View File

@ -47,7 +47,8 @@ const mainConfig = {
}, rendererConfig = {
entry: {
chat: [path.join(__dirname, 'chat.ts'), path.join(__dirname, 'index.html')],
window: [path.join(__dirname, 'window.ts'), path.join(__dirname, 'window.html'), path.join(__dirname, 'build', 'tray@2x.png')]
window: [path.join(__dirname, 'window.ts'), path.join(__dirname, 'window.html'), path.join(__dirname, 'build', 'tray@2x.png')],
browser_option: [path.join(__dirname, 'browser_option.ts'), path.join(__dirname, 'browser_option.html'), path.join(__dirname, 'build', 'tray@2x.png')]
},
output: {
path: __dirname + '/app',