Merge pull request #273 from greyhoof/browser-option

Browser option
This commit is contained in:
hearmeneigh 2023-12-01 17:40:49 -08:00 committed by GitHub
commit c91e5c2197
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 444 additions and 11 deletions

View File

@ -189,6 +189,15 @@ 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',
'settings.browserOptionReset': 'Reset to default',
'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.

239
electron/BrowserOption.vue Normal file
View File

@ -0,0 +1,239 @@
<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">
<div class="row">
<div class="col-12">
<div class="warning">
<h5>Danger Zone!</h5>
<div>This is an advanced setting. By changing this setting to an unsupported program (i.e. not a browser), you might not be able to open links from F-Chat anymore.</div>
<div v-if="isMac"><hr/>
<p>Mac User: As of writing, MacOS has a bug in how it handles opening links.</p>
<p>When your default browser is something other than Safari and you select Safari in this settings window, links might be opened twice.</p>
<p>Once in Safari and a second time in your default browser. This tends to happen when Safari is not running when clicking a link.</p></div>
</div>
</div>
</div>
</div>
<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 no-gutters">
<div class="col-2">
<button class="btn btn-primary" @click.prevent.stop="submit()">{{l('settings.browserOptionSave')}}</button>
</div>
<div class="col"></div>
<div class="col-4">
<button class="btn btn-danger" style="float: right;" @click.prevent.stop="resetToDefault()">{{l('settings.browserOptionReset')}}</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 = '';
isMac = process.platform === 'darwin';
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();
}
resetToDefault(): void {
this.browserPath = '';
this.browserArgs = '';
}
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;
}
}
.warning {
border: 1px solid var(--warning);
padding: 10px;
margin-bottom: 20px;
border-radius: 3px;
div {
margin-top: 10px;
}
}
.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,70 @@ async function addSpellcheckerItems(menu: electron.Menu): Promise<void> {
}));
}
function openURLExternally(linkUrl: string): void {
// check if user set a path and whether it exists
const pathIsValid = (settings.browserPath !== '' && fs.existsSync(settings.browserPath));
if(pathIsValid) {
// also check if the user can execute whatever is located at the selected path
let fileIsExecutable = false;
try {
fs.accessSync(settings.browserPath, fs.constants.X_OK);
fileIsExecutable = true;
} catch (err) {
log.error(`Selected browser is not executable by user. Path: "${settings.browserPath}"`);
}
if (fileIsExecutable) {
// check if URL is already encoded
// (this should work almost all the time, but there might be edge-cases with very unusual URLs)
let isEncoded = (linkUrl !== decodeURI(linkUrl));
// only encode URL if it isn't encoded yet
if (!isEncoded) {
// 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;
if (process.platform === "darwin") {
// NOTE: This is seemingly bugged on MacOS when setting Safari as the external browser while using a different default browser.
// In that case, this will open the URL in both the selected application AND the default browser.
// Other browsers work fine. (Tested with Chrome with Firefox as the default browser.)
// https://developer.apple.com/forums/thread/685385
execFile(`open -a "${settings.browserPath}" ${link}`);
} else {
execFile(`"${settings.browserPath}" ${link}`);
}
return;
}
}
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 +338,44 @@ 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 {
let desiredHeight = 520;
if(process.platform === 'darwin') {
desiredHeight = 750;
}
const windowProperties: electron.BrowserWindowConstructorOptions = {
center: true,
show: false,
icon: process.platform === 'win32' ? winIcon : pngIcon,
frame: false,
width: 650,
height: desiredHeight,
minWidth: 650,
minHeight: desiredHeight,
maxWidth: 650,
maxHeight: desiredHeight,
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 +623,12 @@ function onReady(): void {
settings.risingDisableWindowsHighContrast = item.checked;
setGeneralSettings(settings);
}
},
{
label: l('settings.browserOption'),
click: () => {
openBrowserSettings();
}
}
]
},
@ -576,23 +676,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 +765,47 @@ 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));
let filters;
if(process.platform === "win32") {
filters = [{ name: 'Executables', extensions: ['exe'] }];
} else if (process.platform === "darwin") {
filters = [{ name: 'Executables', extensions: ['app'] }];
} else {
// linux and anything else that might be supported
// no specific extension for executables
filters = [{ name: 'Executables', extensions: ['*'] }];
}
const dir = electron.dialog.showOpenDialogSync(
{
defaultPath: settings.browserPath,
properties: ['openFile'],
filters: filters
});
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',