mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
style: run prettier
This commit is contained in:
@@ -11,10 +11,7 @@ const addPortableToPortableExecutableFileName = () => {
|
|||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
if (file.endsWith('.exe') && !file.match('Setup')) {
|
if (file.endsWith('.exe') && !file.match('Setup')) {
|
||||||
const filePath = path.resolve(distFolderPath, file);
|
const filePath = path.resolve(distFolderPath, file);
|
||||||
const renamedFilePath = path.resolve(
|
const renamedFilePath = path.resolve(distFolderPath, file.replace('plebchan', 'plebchan Portable'));
|
||||||
distFolderPath,
|
|
||||||
file.replace('plebchan', 'plebchan Portable')
|
|
||||||
);
|
|
||||||
fs.moveSync(filePath, renamedFilePath);
|
fs.moveSync(filePath, renamedFilePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+122
-143
@@ -1,52 +1,40 @@
|
|||||||
require('./log')
|
require('./log');
|
||||||
const {
|
const { app, BrowserWindow, Menu, MenuItem, Tray, screen: electronScreen, shell, dialog } = require('electron');
|
||||||
app,
|
const isDev = require('electron-is-dev');
|
||||||
BrowserWindow,
|
const path = require('path');
|
||||||
Menu,
|
const startIpfs = require('./start-ipfs');
|
||||||
MenuItem,
|
const startPlebbitRpcServer = require('./start-plebbit-rpc');
|
||||||
Tray,
|
const { URL } = require('node:url');
|
||||||
screen: electronScreen,
|
const tcpPortUsed = require('tcp-port-used');
|
||||||
shell,
|
|
||||||
dialog
|
|
||||||
} = require('electron')
|
|
||||||
const isDev = require('electron-is-dev')
|
|
||||||
const path = require('path')
|
|
||||||
const startIpfs = require('./start-ipfs')
|
|
||||||
const startPlebbitRpcServer = require('./start-plebbit-rpc')
|
|
||||||
const { URL } = require('node:url')
|
|
||||||
const tcpPortUsed = require('tcp-port-used')
|
|
||||||
|
|
||||||
// retry starting ipfs every 10 second,
|
// retry starting ipfs every 10 second,
|
||||||
// in case it was started by another client that shut down and shut down ipfs with it
|
// in case it was started by another client that shut down and shut down ipfs with it
|
||||||
let startIpfsError
|
let startIpfsError;
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
try {
|
try {
|
||||||
const started = await tcpPortUsed.check(5001, '127.0.0.1')
|
const started = await tcpPortUsed.check(5001, '127.0.0.1');
|
||||||
if (started) {
|
if (started) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
await startIpfs()
|
await startIpfs();
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
startIpfsError = e;
|
||||||
|
dialog.showErrorBox('IPFS error', startIpfsError.message);
|
||||||
}
|
}
|
||||||
catch (e) {
|
}, 10000);
|
||||||
console.log(e)
|
|
||||||
startIpfsError = e
|
|
||||||
dialog.showErrorBox('IPFS error', startIpfsError.message)
|
|
||||||
}
|
|
||||||
}, 10000)
|
|
||||||
|
|
||||||
// use common user agent instead of electron so img, video, audio, iframe elements don't get blocked
|
// use common user agent instead of electron so img, video, audio, iframe elements don't get blocked
|
||||||
// https://www.whatismybrowser.com/guides/the-latest-version/chrome
|
// https://www.whatismybrowser.com/guides/the-latest-version/chrome
|
||||||
// https://www.whatismybrowser.com/guides/the-latest-user-agent/chrome
|
// https://www.whatismybrowser.com/guides/the-latest-user-agent/chrome
|
||||||
// NOTE: eventually should probably fake sec-ch-ua header as well
|
// NOTE: eventually should probably fake sec-ch-ua header as well
|
||||||
let fakeUserAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36'
|
let fakeUserAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36';
|
||||||
if (process.platform === 'darwin')
|
if (process.platform === 'darwin') fakeUserAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 13_5_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36';
|
||||||
fakeUserAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 13_5_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36'
|
if (process.platform === 'linux') fakeUserAgent = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36';
|
||||||
if (process.platform === 'linux')
|
const realUserAgent = `plebchan/${require('../package.json').version}`;
|
||||||
fakeUserAgent = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36'
|
|
||||||
const realUserAgent = `plebchan/${require('../package.json').version}`
|
|
||||||
|
|
||||||
// add right click menu
|
// add right click menu
|
||||||
const contextMenu = require('electron-context-menu')
|
const contextMenu = require('electron-context-menu');
|
||||||
contextMenu({
|
contextMenu({
|
||||||
// prepend custom buttons to top
|
// prepend custom buttons to top
|
||||||
prepend: (defaultActions, parameters, browserWindow) => [
|
prepend: (defaultActions, parameters, browserWindow) => [
|
||||||
@@ -76,7 +64,7 @@ contextMenu({
|
|||||||
showInspectElement: true,
|
showInspectElement: true,
|
||||||
showServices: false,
|
showServices: false,
|
||||||
showSearchWithGoogle: false,
|
showSearchWithGoogle: false,
|
||||||
})
|
});
|
||||||
|
|
||||||
const createMainWindow = () => {
|
const createMainWindow = () => {
|
||||||
let mainWindow = new BrowserWindow({
|
let mainWindow = new BrowserWindow({
|
||||||
@@ -91,199 +79,192 @@ const createMainWindow = () => {
|
|||||||
devTools: true, // TODO: change to isDev when no bugs left
|
devTools: true, // TODO: change to isDev when no bugs left
|
||||||
preload: path.join(__dirname, 'preload.js'),
|
preload: path.join(__dirname, 'preload.js'),
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
// set fake user agent
|
// set fake user agent
|
||||||
mainWindow.webContents.userAgent = fakeUserAgent
|
mainWindow.webContents.userAgent = fakeUserAgent;
|
||||||
|
|
||||||
// set custom user agent and other headers for window.fetch requests to prevent origin errors
|
// set custom user agent and other headers for window.fetch requests to prevent origin errors
|
||||||
mainWindow.webContents.session.webRequest.onBeforeSendHeaders({urls: ['*://*/*']}, (details, callback) => {
|
mainWindow.webContents.session.webRequest.onBeforeSendHeaders({ urls: ['*://*/*'] }, (details, callback) => {
|
||||||
const isIframe = !!details.frame?.parent
|
const isIframe = !!details.frame?.parent;
|
||||||
// if not a fetch request (or fetch request is from within iframe), do nothing, filtering webRequest by types doesn't seem to work
|
// if not a fetch request (or fetch request is from within iframe), do nothing, filtering webRequest by types doesn't seem to work
|
||||||
if (details.resourceType !== 'xhr' || isIframe) {
|
if (details.resourceType !== 'xhr' || isIframe) {
|
||||||
return callback({requestHeaders: details.requestHeaders})
|
return callback({ requestHeaders: details.requestHeaders });
|
||||||
}
|
}
|
||||||
// add privacy
|
// add privacy
|
||||||
details.requestHeaders['User-Agent'] = realUserAgent
|
details.requestHeaders['User-Agent'] = realUserAgent;
|
||||||
details.requestHeaders['sec-ch-ua'] = undefined
|
details.requestHeaders['sec-ch-ua'] = undefined;
|
||||||
details.requestHeaders['sec-ch-ua-platform'] = undefined
|
details.requestHeaders['sec-ch-ua-platform'] = undefined;
|
||||||
details.requestHeaders['sec-ch-ua-mobile'] = undefined
|
details.requestHeaders['sec-ch-ua-mobile'] = undefined;
|
||||||
details.requestHeaders['Sec-Fetch-Dest'] = undefined
|
details.requestHeaders['Sec-Fetch-Dest'] = undefined;
|
||||||
details.requestHeaders['Sec-Fetch-Mode'] = undefined
|
details.requestHeaders['Sec-Fetch-Mode'] = undefined;
|
||||||
details.requestHeaders['Sec-Fetch-Site'] = undefined
|
details.requestHeaders['Sec-Fetch-Site'] = undefined;
|
||||||
// prevent origin errors
|
// prevent origin errors
|
||||||
details.requestHeaders['Origin'] = undefined
|
details.requestHeaders['Origin'] = undefined;
|
||||||
callback({requestHeaders: details.requestHeaders})
|
callback({ requestHeaders: details.requestHeaders });
|
||||||
})
|
});
|
||||||
|
|
||||||
// fix cors errors for window.fetch. must not be enabled for iframe or can cause remote code execution
|
// fix cors errors for window.fetch. must not be enabled for iframe or can cause remote code execution
|
||||||
mainWindow.webContents.session.webRequest.onHeadersReceived({urls: ['*://*/*']}, (details, callback) => {
|
mainWindow.webContents.session.webRequest.onHeadersReceived({ urls: ['*://*/*'] }, (details, callback) => {
|
||||||
const isIframe = !!details.frame?.parent
|
const isIframe = !!details.frame?.parent;
|
||||||
// if not a fetch request (or fetch request is from within iframe), do nothing, filtering webRequest by types doesn't seem to work
|
// if not a fetch request (or fetch request is from within iframe), do nothing, filtering webRequest by types doesn't seem to work
|
||||||
if (details.resourceType !== 'xhr' || isIframe) {
|
if (details.resourceType !== 'xhr' || isIframe) {
|
||||||
return callback({responseHeaders: details.responseHeaders})
|
return callback({ responseHeaders: details.responseHeaders });
|
||||||
}
|
}
|
||||||
// must delete lower case headers or both '*, *' could get added
|
// must delete lower case headers or both '*, *' could get added
|
||||||
delete details.responseHeaders['access-control-allow-origin']
|
delete details.responseHeaders['access-control-allow-origin'];
|
||||||
delete details.responseHeaders['access-control-allow-headers']
|
delete details.responseHeaders['access-control-allow-headers'];
|
||||||
delete details.responseHeaders['access-control-allow-methods']
|
delete details.responseHeaders['access-control-allow-methods'];
|
||||||
delete details.responseHeaders['access-control-expose-headers']
|
delete details.responseHeaders['access-control-expose-headers'];
|
||||||
details.responseHeaders['Access-Control-Allow-Origin'] = '*'
|
details.responseHeaders['Access-Control-Allow-Origin'] = '*';
|
||||||
details.responseHeaders['Access-Control-Allow-Headers'] = '*'
|
details.responseHeaders['Access-Control-Allow-Headers'] = '*';
|
||||||
details.responseHeaders['Access-Control-Allow-Methods'] = '*'
|
details.responseHeaders['Access-Control-Allow-Methods'] = '*';
|
||||||
details.responseHeaders['Access-Control-Expose-Headers'] = '*'
|
details.responseHeaders['Access-Control-Expose-Headers'] = '*';
|
||||||
callback({responseHeaders: details.responseHeaders})
|
callback({ responseHeaders: details.responseHeaders });
|
||||||
})
|
});
|
||||||
|
|
||||||
const startURL = isDev
|
const startURL = isDev ? 'http://localhost:3000' : `file://${path.join(__dirname, '../build/index.html')}`;
|
||||||
? 'http://localhost:3000'
|
|
||||||
: `file://${path.join(__dirname, '../build/index.html')}`
|
|
||||||
|
|
||||||
mainWindow.loadURL(startURL)
|
mainWindow.loadURL(startURL);
|
||||||
|
|
||||||
mainWindow.once('ready-to-show', async () => {
|
mainWindow.once('ready-to-show', async () => {
|
||||||
// make sure back button is disabled on launch
|
// make sure back button is disabled on launch
|
||||||
mainWindow.webContents.clearHistory()
|
mainWindow.webContents.clearHistory();
|
||||||
|
|
||||||
mainWindow.show()
|
mainWindow.show();
|
||||||
|
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
mainWindow.openDevTools()
|
mainWindow.openDevTools();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (startIpfsError) {
|
if (startIpfsError) {
|
||||||
dialog.showErrorBox('IPFS error', startIpfsError.message)
|
dialog.showErrorBox('IPFS error', startIpfsError.message);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
mainWindow.on('closed', () => {
|
mainWindow.on('closed', () => {
|
||||||
mainWindow = null
|
mainWindow = null;
|
||||||
})
|
});
|
||||||
|
|
||||||
// don't open new windows
|
// don't open new windows
|
||||||
mainWindow.webContents.on('new-window', (event, url) => {
|
mainWindow.webContents.on('new-window', (event, url) => {
|
||||||
event.preventDefault()
|
event.preventDefault();
|
||||||
mainWindow.loadURL(url)
|
mainWindow.loadURL(url);
|
||||||
})
|
});
|
||||||
|
|
||||||
// open links in external browser
|
// open links in external browser
|
||||||
// do not open links in plebchan or will lead to remote execution
|
// do not open links in plebchan or will lead to remote execution
|
||||||
mainWindow.webContents.on('will-navigate', (e, originalUrl) => {
|
mainWindow.webContents.on('will-navigate', (e, originalUrl) => {
|
||||||
if (originalUrl != mainWindow.webContents.getURL()) {
|
if (originalUrl != mainWindow.webContents.getURL()) {
|
||||||
e.preventDefault()
|
e.preventDefault();
|
||||||
try {
|
try {
|
||||||
// do not let the user open any url with shell.openExternal
|
// do not let the user open any url with shell.openExternal
|
||||||
// or it will lead to remote execution https://benjamin-altpeter.de/shell-openexternal-dangers/
|
// or it will lead to remote execution https://benjamin-altpeter.de/shell-openexternal-dangers/
|
||||||
|
|
||||||
// only open valid https urls to prevent remote execution
|
// only open valid https urls to prevent remote execution
|
||||||
// will throw if url isn't valid
|
// will throw if url isn't valid
|
||||||
const validatedUrl = new URL(originalUrl)
|
const validatedUrl = new URL(originalUrl);
|
||||||
let serializedUrl = ''
|
let serializedUrl = '';
|
||||||
|
|
||||||
// make an exception for ipfs stats
|
// make an exception for ipfs stats
|
||||||
if (validatedUrl.toString() === 'http://localhost:5001/webui/') {
|
if (validatedUrl.toString() === 'http://localhost:5001/webui/') {
|
||||||
serializedUrl = validatedUrl.toString()
|
serializedUrl = validatedUrl.toString();
|
||||||
} else if (validatedUrl.protocol === 'https:') {
|
} else if (validatedUrl.protocol === 'https:') {
|
||||||
// open serialized url to prevent remote execution
|
// open serialized url to prevent remote execution
|
||||||
serializedUrl = validatedUrl.toString()
|
serializedUrl = validatedUrl.toString();
|
||||||
} else {
|
} else {
|
||||||
throw Error(`can't open url '${originalUrl}', it's not https and not the allowed http exception`)
|
throw Error(`can't open url '${originalUrl}', it's not https and not the allowed http exception`);
|
||||||
}
|
}
|
||||||
|
|
||||||
shell.openExternal(serializedUrl)
|
shell.openExternal(serializedUrl);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn(e)
|
console.warn(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
// open links (with target="_blank") in external browser
|
// open links (with target="_blank") in external browser
|
||||||
// do not open links in plebchan or will lead to remote execution
|
// do not open links in plebchan or will lead to remote execution
|
||||||
mainWindow.webContents.setWindowOpenHandler(({url}) => {
|
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
|
||||||
const originalUrl = url
|
const originalUrl = url;
|
||||||
try {
|
try {
|
||||||
// do not let the user open any url with shell.openExternal
|
// do not let the user open any url with shell.openExternal
|
||||||
// or it will lead to remote execution https://benjamin-altpeter.de/shell-openexternal-dangers/
|
// or it will lead to remote execution https://benjamin-altpeter.de/shell-openexternal-dangers/
|
||||||
|
|
||||||
// only open valid https urls to prevent remote execution
|
// only open valid https urls to prevent remote execution
|
||||||
// will throw if url isn't valid
|
// will throw if url isn't valid
|
||||||
const validatedUrl = new URL(originalUrl)
|
const validatedUrl = new URL(originalUrl);
|
||||||
let serializedUrl = ''
|
let serializedUrl = '';
|
||||||
|
|
||||||
// make an exception for ipfs stats
|
// make an exception for ipfs stats
|
||||||
if (validatedUrl.toString() === 'http://localhost:5001/webui/') {
|
if (validatedUrl.toString() === 'http://localhost:5001/webui/') {
|
||||||
serializedUrl = validatedUrl.toString()
|
serializedUrl = validatedUrl.toString();
|
||||||
} else if (validatedUrl.protocol === 'https:') {
|
} else if (validatedUrl.protocol === 'https:') {
|
||||||
// open serialized url to prevent remote execution
|
// open serialized url to prevent remote execution
|
||||||
serializedUrl = validatedUrl.toString()
|
serializedUrl = validatedUrl.toString();
|
||||||
} else {
|
} else {
|
||||||
throw Error(`can't open url '${originalUrl}', it's not https and not the allowed http exception`)
|
throw Error(`can't open url '${originalUrl}', it's not https and not the allowed http exception`);
|
||||||
}
|
}
|
||||||
|
|
||||||
shell.openExternal(serializedUrl)
|
shell.openExternal(serializedUrl);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn(e)
|
console.warn(e);
|
||||||
}
|
}
|
||||||
return {action: 'deny'}
|
return { action: 'deny' };
|
||||||
})
|
});
|
||||||
|
|
||||||
// deny permissions like location, notifications, etc https://www.electronjs.org/docs/latest/tutorial/security#5-handle-session-permission-requests-from-remote-content
|
// deny permissions like location, notifications, etc https://www.electronjs.org/docs/latest/tutorial/security#5-handle-session-permission-requests-from-remote-content
|
||||||
mainWindow.webContents.session.setPermissionRequestHandler((webContents, permission, callback) => {
|
mainWindow.webContents.session.setPermissionRequestHandler((webContents, permission, callback) => {
|
||||||
// deny all permissions
|
// deny all permissions
|
||||||
return callback(false)
|
return callback(false);
|
||||||
})
|
});
|
||||||
|
|
||||||
// deny attaching webview https://www.electronjs.org/docs/latest/tutorial/security#12-verify-webview-options-before-creation
|
// deny attaching webview https://www.electronjs.org/docs/latest/tutorial/security#12-verify-webview-options-before-creation
|
||||||
mainWindow.webContents.on('will-attach-webview', (e, webPreferences, params) => {
|
mainWindow.webContents.on('will-attach-webview', (e, webPreferences, params) => {
|
||||||
// deny all
|
// deny all
|
||||||
e.preventDefault()
|
e.preventDefault();
|
||||||
})
|
});
|
||||||
|
|
||||||
if (process.platform !== 'darwin') {
|
if (process.platform !== 'darwin') {
|
||||||
// tray
|
// tray
|
||||||
const trayIconPath = path.join(
|
const trayIconPath = path.join(__dirname, '..', isDev ? 'public' : 'build', 'electron-tray-icon.png');
|
||||||
__dirname,
|
const tray = new Tray(trayIconPath);
|
||||||
'..',
|
tray.setToolTip('plebchan');
|
||||||
isDev ? 'public' : 'build',
|
|
||||||
'electron-tray-icon.png'
|
|
||||||
)
|
|
||||||
const tray = new Tray(trayIconPath)
|
|
||||||
tray.setToolTip('plebchan')
|
|
||||||
const trayMenu = Menu.buildFromTemplate([
|
const trayMenu = Menu.buildFromTemplate([
|
||||||
{
|
{
|
||||||
label: 'Open plebchan',
|
label: 'Open plebchan',
|
||||||
click: () => {
|
click: () => {
|
||||||
mainWindow.show()
|
mainWindow.show();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Quit plebchan',
|
label: 'Quit plebchan',
|
||||||
click: () => {
|
click: () => {
|
||||||
mainWindow.destroy()
|
mainWindow.destroy();
|
||||||
app.quit()
|
app.quit();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
])
|
]);
|
||||||
tray.setContextMenu(trayMenu)
|
tray.setContextMenu(trayMenu);
|
||||||
|
|
||||||
// show/hide on tray right click
|
// show/hide on tray right click
|
||||||
tray.on('right-click', () => {
|
tray.on('right-click', () => {
|
||||||
mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show()
|
mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show();
|
||||||
})
|
});
|
||||||
|
|
||||||
// close to tray
|
// close to tray
|
||||||
if (!isDev) {
|
if (!isDev) {
|
||||||
let isQuiting = false
|
let isQuiting = false;
|
||||||
app.on('before-quit', () => {
|
app.on('before-quit', () => {
|
||||||
isQuiting = true
|
isQuiting = true;
|
||||||
})
|
});
|
||||||
mainWindow.on('close', (event) => {
|
mainWindow.on('close', (event) => {
|
||||||
if (!isQuiting) {
|
if (!isQuiting) {
|
||||||
event.preventDefault()
|
event.preventDefault();
|
||||||
mainWindow.hide()
|
mainWindow.hide();
|
||||||
event.returnValue = false
|
event.returnValue = false;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,48 +272,46 @@ const createMainWindow = () => {
|
|||||||
label: '←',
|
label: '←',
|
||||||
enabled: mainWindow?.webContents?.canGoBack(),
|
enabled: mainWindow?.webContents?.canGoBack(),
|
||||||
click: () => mainWindow?.webContents?.goBack(),
|
click: () => mainWindow?.webContents?.goBack(),
|
||||||
})
|
});
|
||||||
const appMenuForward = new MenuItem({
|
const appMenuForward = new MenuItem({
|
||||||
label: '→',
|
label: '→',
|
||||||
enabled: mainWindow?.webContents?.canGoForward(),
|
enabled: mainWindow?.webContents?.canGoForward(),
|
||||||
click: () => mainWindow?.webContents?.goForward(),
|
click: () => mainWindow?.webContents?.goForward(),
|
||||||
})
|
});
|
||||||
const appMenuReload = new MenuItem({
|
const appMenuReload = new MenuItem({
|
||||||
label: '⟳',
|
label: '⟳',
|
||||||
role: 'reload',
|
role: 'reload',
|
||||||
click: () => mainWindow?.webContents?.reload(),
|
click: () => mainWindow?.webContents?.reload(),
|
||||||
})
|
});
|
||||||
|
|
||||||
// application menu
|
// application menu
|
||||||
// hide useless electron help menu
|
// hide useless electron help menu
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
const appMenu = Menu.getApplicationMenu()
|
const appMenu = Menu.getApplicationMenu();
|
||||||
appMenu.insert(1, appMenuBack)
|
appMenu.insert(1, appMenuBack);
|
||||||
appMenu.insert(2, appMenuForward)
|
appMenu.insert(2, appMenuForward);
|
||||||
appMenu.insert(3, appMenuReload)
|
appMenu.insert(3, appMenuReload);
|
||||||
Menu.setApplicationMenu(appMenu)
|
Menu.setApplicationMenu(appMenu);
|
||||||
} else {
|
} else {
|
||||||
// Other platforms
|
// Other platforms
|
||||||
const originalAppMenuWithoutHelp = Menu.getApplicationMenu()?.items.filter(
|
const originalAppMenuWithoutHelp = Menu.getApplicationMenu()?.items.filter((item) => item.role !== 'help');
|
||||||
(item) => item.role !== 'help'
|
const appMenu = [appMenuBack, appMenuForward, appMenuReload, ...originalAppMenuWithoutHelp];
|
||||||
)
|
Menu.setApplicationMenu(Menu.buildFromTemplate(appMenu));
|
||||||
const appMenu = [appMenuBack, appMenuForward, appMenuReload, ...originalAppMenuWithoutHelp]
|
|
||||||
Menu.setApplicationMenu(Menu.buildFromTemplate(appMenu))
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
app.whenReady().then(() => {
|
app.whenReady().then(() => {
|
||||||
createMainWindow()
|
createMainWindow();
|
||||||
|
|
||||||
app.on('activate', () => {
|
app.on('activate', () => {
|
||||||
if (!BrowserWindow.getAllWindows().length) {
|
if (!BrowserWindow.getAllWindows().length) {
|
||||||
createMainWindow()
|
createMainWindow();
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
app.on('window-all-closed', () => {
|
app.on('window-all-closed', () => {
|
||||||
if (process.platform !== 'darwin') {
|
if (process.platform !== 'darwin') {
|
||||||
app.quit()
|
app.quit();
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|||||||
+6
-6
@@ -1,16 +1,16 @@
|
|||||||
const { contextBridge } = require('electron')
|
const { contextBridge } = require('electron');
|
||||||
|
|
||||||
// dev uses http://localhost, prod uses file://...index.html
|
// dev uses http://localhost, prod uses file://...index.html
|
||||||
const isDev = window.location.protocol === 'http:'
|
const isDev = window.location.protocol === 'http:';
|
||||||
|
|
||||||
const defaultPlebbitOptions = {
|
const defaultPlebbitOptions = {
|
||||||
plebbitRpcClientsOptions: ['ws://localhost:9138']
|
plebbitRpcClientsOptions: ['ws://localhost:9138'],
|
||||||
}
|
};
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld('defaultPlebbitOptions', defaultPlebbitOptions)
|
contextBridge.exposeInMainWorld('defaultPlebbitOptions', defaultPlebbitOptions);
|
||||||
|
|
||||||
// expose a flag to indicate that we are running in electron
|
// expose a flag to indicate that we are running in electron
|
||||||
contextBridge.exposeInMainWorld('electron', { isElectron: true })
|
contextBridge.exposeInMainWorld('electron', { isElectron: true });
|
||||||
|
|
||||||
// uncomment for logs
|
// uncomment for logs
|
||||||
// localStorage.debug = 'plebbit-js:*,plebbit-react-hooks:*,plebchan:*'
|
// localStorage.debug = 'plebbit-js:*,plebbit-react-hooks:*,plebchan:*'
|
||||||
|
|||||||
+3
-12
@@ -13,12 +13,7 @@ const spawnAsync = (...args) =>
|
|||||||
const spawedProcess = spawn(...args);
|
const spawedProcess = spawn(...args);
|
||||||
spawedProcess.on('exit', (exitCode, signal) => {
|
spawedProcess.on('exit', (exitCode, signal) => {
|
||||||
if (exitCode === 0) resolve();
|
if (exitCode === 0) resolve();
|
||||||
else
|
else reject(Error(`spawnAsync process '${spawedProcess.pid}' exited with code '${exitCode}' signal '${signal}'`));
|
||||||
reject(
|
|
||||||
Error(
|
|
||||||
`spawnAsync process '${spawedProcess.pid}' exited with code '${exitCode}' signal '${signal}'`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
spawedProcess.stderr.on('data', (data) => console.error(data.toString()));
|
spawedProcess.stderr.on('data', (data) => console.error(data.toString()));
|
||||||
spawedProcess.stdin.on('data', (data) => console.log(data.toString()));
|
spawedProcess.stdin.on('data', (data) => console.log(data.toString()));
|
||||||
@@ -59,7 +54,7 @@ const startIpfs = async () => {
|
|||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
// dont use 8080 port because it's too common
|
// dont use 8080 port because it's too common
|
||||||
await spawnAsync(ipfsPath, ['config', '--json', 'Addresses.Gateway', "null"], {
|
await spawnAsync(ipfsPath, ['config', '--json', 'Addresses.Gateway', 'null'], {
|
||||||
env,
|
env,
|
||||||
hideWindows: true,
|
hideWindows: true,
|
||||||
});
|
});
|
||||||
@@ -73,11 +68,7 @@ const startIpfs = async () => {
|
|||||||
await spawnAsync(ipfsPath, ['config', 'Addresses.API', apiAddress], { env, hideWindows: true });
|
await spawnAsync(ipfsPath, ['config', 'Addresses.API', apiAddress], { env, hideWindows: true });
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
const ipfsProcess = spawn(
|
const ipfsProcess = spawn(ipfsPath, ['daemon', '--migrate', '--enable-pubsub-experiment', '--enable-namesys-pubsub'], { env, hideWindows: true });
|
||||||
ipfsPath,
|
|
||||||
['daemon', '--migrate', '--enable-pubsub-experiment', '--enable-namesys-pubsub'],
|
|
||||||
{ env, hideWindows: true }
|
|
||||||
);
|
|
||||||
console.log(`ipfs daemon process started with pid ${ipfsProcess.pid}`);
|
console.log(`ipfs daemon process started with pid ${ipfsProcess.pid}`);
|
||||||
let lastError;
|
let lastError;
|
||||||
ipfsProcess.stderr.on('data', (data) => {
|
ipfsProcess.stderr.on('data', (data) => {
|
||||||
|
|||||||
@@ -1,69 +1,67 @@
|
|||||||
const tcpPortUsed = require('tcp-port-used')
|
const tcpPortUsed = require('tcp-port-used');
|
||||||
const {PlebbitWsServer} = require('@plebbit/plebbit-js/rpc')
|
const { PlebbitWsServer } = require('@plebbit/plebbit-js/rpc');
|
||||||
const path = require('path')
|
const path = require('path');
|
||||||
const envPaths = require('env-paths').default('plebbit', { suffix: false })
|
const envPaths = require('env-paths').default('plebbit', { suffix: false });
|
||||||
const {randomBytes} = require('crypto')
|
const { randomBytes } = require('crypto');
|
||||||
const fs = require('fs-extra')
|
const fs = require('fs-extra');
|
||||||
|
|
||||||
let isDev = true
|
let isDev = true;
|
||||||
try {
|
try {
|
||||||
isDev = require('electron-is-dev')
|
isDev = require('electron-is-dev');
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
// PLEB, always run plebbit rpc on this port so all clients can use it
|
// PLEB, always run plebbit rpc on this port so all clients can use it
|
||||||
const port = 9138
|
const port = 9138;
|
||||||
const defaultPlebbitOptions = {
|
const defaultPlebbitOptions = {
|
||||||
// find the user's OS data path
|
// find the user's OS data path
|
||||||
dataPath: !isDev ? envPaths.data : path.join(__dirname, '..', '.plebbit'),
|
dataPath: !isDev ? envPaths.data : path.join(__dirname, '..', '.plebbit'),
|
||||||
ipfsHttpClientsOptions: ['http://localhost:5001/api/v0'],
|
ipfsHttpClientsOptions: ['http://localhost:5001/api/v0'],
|
||||||
// TODO: having to define pubsubHttpClientsOptions and ipfsHttpClientsOptions is a bug with plebbit-js
|
// TODO: having to define pubsubHttpClientsOptions and ipfsHttpClientsOptions is a bug with plebbit-js
|
||||||
pubsubHttpClientsOptions: ['http://localhost:5001/api/v0'],
|
pubsubHttpClientsOptions: ['http://localhost:5001/api/v0'],
|
||||||
}
|
};
|
||||||
|
|
||||||
// generate plebbit rpc auth key if doesn't exist
|
// generate plebbit rpc auth key if doesn't exist
|
||||||
const plebbitRpcAuthKeyPath = path.join(defaultPlebbitOptions.dataPath, 'auth-key')
|
const plebbitRpcAuthKeyPath = path.join(defaultPlebbitOptions.dataPath, 'auth-key');
|
||||||
let plebbitRpcAuthKey
|
let plebbitRpcAuthKey;
|
||||||
try {
|
try {
|
||||||
plebbitRpcAuthKey = fs.readFileSync(plebbitRpcAuthKeyPath, 'utf8')
|
plebbitRpcAuthKey = fs.readFileSync(plebbitRpcAuthKeyPath, 'utf8');
|
||||||
}
|
} catch (e) {
|
||||||
catch (e) {
|
plebbitRpcAuthKey = randomBytes(32).toString('base64').replace(/[/+=]/g, '').substring(0, 40);
|
||||||
plebbitRpcAuthKey = randomBytes(32).toString('base64').replace(/[/+=]/g, '').substring(0, 40)
|
fs.ensureFileSync(plebbitRpcAuthKeyPath);
|
||||||
fs.ensureFileSync(plebbitRpcAuthKeyPath)
|
fs.writeFileSync(plebbitRpcAuthKeyPath, plebbitRpcAuthKey);
|
||||||
fs.writeFileSync(plebbitRpcAuthKeyPath, plebbitRpcAuthKey)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let pendingStart = false
|
let pendingStart = false;
|
||||||
const start = async () => {
|
const start = async () => {
|
||||||
if (pendingStart) {
|
if (pendingStart) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
pendingStart = true
|
pendingStart = true;
|
||||||
try {
|
try {
|
||||||
const started = await tcpPortUsed.check(port, '127.0.0.1')
|
const started = await tcpPortUsed.check(port, '127.0.0.1');
|
||||||
if (started) {
|
if (started) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
const plebbitWebSocketServer = await PlebbitWsServer({port, plebbitOptions: defaultPlebbitOptions, authKey: plebbitRpcAuthKey})
|
const plebbitWebSocketServer = await PlebbitWsServer({ port, plebbitOptions: defaultPlebbitOptions, authKey: plebbitRpcAuthKey });
|
||||||
|
|
||||||
console.log(`plebbit rpc: listening on ws://localhost:${port} (local connections only)`)
|
console.log(`plebbit rpc: listening on ws://localhost:${port} (local connections only)`);
|
||||||
console.log(`plebbit rpc: listening on ws://localhost:${port}/${plebbitRpcAuthKey} (secret auth key for remote connections)`)
|
console.log(`plebbit rpc: listening on ws://localhost:${port}/${plebbitRpcAuthKey} (secret auth key for remote connections)`);
|
||||||
plebbitWebSocketServer.ws.on('connection', (socket, request) => {
|
plebbitWebSocketServer.ws.on('connection', (socket, request) => {
|
||||||
console.log('plebbit rpc: new connection')
|
console.log('plebbit rpc: new connection');
|
||||||
// debug raw JSON RPC messages in console
|
// debug raw JSON RPC messages in console
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
socket.on('message', (message) => console.log(`plebbit rpc: ${message.toString()}`))
|
socket.on('message', (message) => console.log(`plebbit rpc: ${message.toString()}`));
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.log('failed starting plebbit rpc server', e);
|
||||||
}
|
}
|
||||||
catch (e) {
|
pendingStart = false;
|
||||||
console.log('failed starting plebbit rpc server', e)
|
};
|
||||||
}
|
|
||||||
pendingStart = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// retry starting the plebbit rpc server every 1 second,
|
// retry starting the plebbit rpc server every 1 second,
|
||||||
// in case it was started by another client that shut down and shut down the server with it
|
// in case it was started by another client that shut down and shut down the server with it
|
||||||
start()
|
start();
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
start()
|
start();
|
||||||
}, 1000)
|
}, 1000);
|
||||||
|
|||||||
@@ -4,20 +4,24 @@ import useGeneralStore from '../../hooks/stores/useGeneralStore';
|
|||||||
import Modal from 'react-modal';
|
import Modal from 'react-modal';
|
||||||
import Draggable from 'react-draggable';
|
import Draggable from 'react-draggable';
|
||||||
|
|
||||||
|
|
||||||
const CaptchaModal = () => {
|
const CaptchaModal = () => {
|
||||||
const {
|
const {
|
||||||
challengesArray, setChallengesArray,
|
challengesArray,
|
||||||
|
setChallengesArray,
|
||||||
pendingComment,
|
pendingComment,
|
||||||
selectedStyle,
|
selectedStyle,
|
||||||
setCaptchaResponse,
|
setCaptchaResponse,
|
||||||
isAuthorDelete, setIsAuthorDelete,
|
isAuthorDelete,
|
||||||
isAuthorEdit, setIsAuthorEdit,
|
setIsAuthorDelete,
|
||||||
isCaptchaOpen, setIsCaptchaOpen,
|
isAuthorEdit,
|
||||||
isModEdit, setIsModEdit,
|
setIsAuthorEdit,
|
||||||
|
isCaptchaOpen,
|
||||||
|
setIsCaptchaOpen,
|
||||||
|
isModEdit,
|
||||||
|
setIsModEdit,
|
||||||
resolveCaptchaPromise,
|
resolveCaptchaPromise,
|
||||||
selectedShortCid,
|
selectedShortCid,
|
||||||
} = useGeneralStore(state => state);
|
} = useGeneralStore((state) => state);
|
||||||
|
|
||||||
const [imageSources, setImageSources] = useState([]);
|
const [imageSources, setImageSources] = useState([]);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
@@ -28,7 +32,6 @@ const CaptchaModal = () => {
|
|||||||
const nodeRef = useRef(null);
|
const nodeRef = useRef(null);
|
||||||
const [isPromiseResolved, setIsPromiseResolved] = useState(false);
|
const [isPromiseResolved, setIsPromiseResolved] = useState(false);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isCaptchaOpen) {
|
if (!isCaptchaOpen) {
|
||||||
setIsAuthorDelete(false);
|
setIsAuthorDelete(false);
|
||||||
@@ -37,7 +40,6 @@ const CaptchaModal = () => {
|
|||||||
}
|
}
|
||||||
}, [isCaptchaOpen, setIsAuthorDelete, setIsAuthorEdit, setIsModEdit]);
|
}, [isCaptchaOpen, setIsAuthorDelete, setIsAuthorEdit, setIsModEdit]);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleResize = () => setIsMobile(window.innerWidth <= 480);
|
const handleResize = () => setIsMobile(window.innerWidth <= 480);
|
||||||
window.addEventListener('resize', handleResize);
|
window.addEventListener('resize', handleResize);
|
||||||
@@ -48,7 +50,6 @@ const CaptchaModal = () => {
|
|||||||
};
|
};
|
||||||
}, [setIsMobile]);
|
}, [setIsMobile]);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isCaptchaOpen && challengesArray) {
|
if (isCaptchaOpen && challengesArray) {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
@@ -67,9 +68,8 @@ const CaptchaModal = () => {
|
|||||||
}
|
}
|
||||||
}, [challengesArray, isCaptchaOpen]);
|
}, [challengesArray, isCaptchaOpen]);
|
||||||
|
|
||||||
|
|
||||||
const handleKeyDown = (event) => {
|
const handleKeyDown = (event) => {
|
||||||
if (event.key === "Enter") {
|
if (event.key === 'Enter') {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
submitCaptcha((response) => {
|
submitCaptcha((response) => {
|
||||||
setCaptchaResponse(response);
|
setCaptchaResponse(response);
|
||||||
@@ -78,15 +78,13 @@ const CaptchaModal = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const handleReturnKeyDown = () => {
|
const handleReturnKeyDown = () => {
|
||||||
submitCaptcha((response) => {
|
submitCaptcha((response) => {
|
||||||
setCaptchaResponse(response);
|
setCaptchaResponse(response);
|
||||||
resolveCaptchaPromise(response);
|
resolveCaptchaPromise(response);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const submitCaptcha = (callback) => {
|
const submitCaptcha = (callback) => {
|
||||||
if (!isPromiseResolved) {
|
if (!isPromiseResolved) {
|
||||||
setCaptchaResponse(responseRef.current.value);
|
setCaptchaResponse(responseRef.current.value);
|
||||||
@@ -116,76 +114,76 @@ const CaptchaModal = () => {
|
|||||||
setIsCaptchaOpen(false);
|
setIsCaptchaOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledModal
|
<StyledModal
|
||||||
isOpen={isCaptchaOpen}
|
isOpen={isCaptchaOpen}
|
||||||
onRequestClose={() => {
|
onRequestClose={() => {
|
||||||
handleCloseModal();
|
handleCloseModal();
|
||||||
submitCaptcha();}}
|
submitCaptcha();
|
||||||
contentLabel="Captcha Modal"
|
}}
|
||||||
shouldCloseOnEsc={false}
|
contentLabel='Captcha Modal'
|
||||||
shouldCloseOnOverlayClick={false}
|
shouldCloseOnEsc={false}
|
||||||
selectedStyle={selectedStyle}
|
shouldCloseOnOverlayClick={false}
|
||||||
overlayClassName="hide-modal-overlay">
|
selectedStyle={selectedStyle}
|
||||||
<Draggable handle=".modal-header" nodeRef={nodeRef} disabled={isMobile}>
|
overlayClassName='hide-modal-overlay'
|
||||||
<div className="modal-content" ref={nodeRef}>
|
>
|
||||||
<div className="modal-header">
|
<Draggable handle='.modal-header' nodeRef={nodeRef} disabled={isMobile}>
|
||||||
{isModEdit ? "Challenge for Moderator Action" :
|
<div className='modal-content' ref={nodeRef}>
|
||||||
isAuthorEdit ? "Challenge for Editing Post" :
|
<div className='modal-header'>
|
||||||
isAuthorDelete ? "Challenge for Deleting Post" :
|
{isModEdit
|
||||||
pendingComment.parentCid ?
|
? 'Challenge for Moderator Action'
|
||||||
("Challenges for Reply to c/" + selectedShortCid) :
|
: isAuthorEdit
|
||||||
"Challenges for New Thread"}
|
? 'Challenge for Editing Post'
|
||||||
<button className="icon" onClick={() => handleCloseModal()} title="close" />
|
: isAuthorDelete
|
||||||
|
? 'Challenge for Deleting Post'
|
||||||
|
: pendingComment.parentCid
|
||||||
|
? 'Challenges for Reply to c/' + selectedShortCid
|
||||||
|
: 'Challenges for New Thread'}
|
||||||
|
<button className='icon' onClick={() => handleCloseModal()} title='close' />
|
||||||
</div>
|
</div>
|
||||||
<div id="form">
|
<div id='form'>
|
||||||
{pendingComment.author?.displayName ? (
|
{pendingComment.author?.displayName ? (
|
||||||
<div>
|
<div>
|
||||||
<input id="field" type="text" placeholder={pendingComment.author?.displayName || ''} disabled />
|
<input id='field' type='text' placeholder={pendingComment.author?.displayName || ''} disabled />
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{pendingComment.title ? (
|
{pendingComment.title ? (
|
||||||
<div>
|
<div>
|
||||||
<input id="field" type="text" placeholder={pendingComment.title || ''} disabled />
|
<input id='field' type='text' placeholder={pendingComment.title || ''} disabled />
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{pendingComment.content ? (
|
{pendingComment.content ? (
|
||||||
<div>
|
<div>
|
||||||
<textarea
|
<textarea rows='4' placeholder={pendingComment.content || 'Comment'} wrap='soft' disabled />
|
||||||
rows="4"
|
|
||||||
placeholder={pendingComment.content || "Comment"}
|
|
||||||
wrap="soft"
|
|
||||||
disabled
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{pendingComment.link ? (
|
{pendingComment.link ? (
|
||||||
<div>
|
<div>
|
||||||
<input id="field" type="text" placeholder={pendingComment.link || ''} disabled />
|
<input id='field' type='text' placeholder={pendingComment.link || ''} disabled />
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<div id="captcha-container">
|
<div id='captcha-container'>
|
||||||
<input
|
<input
|
||||||
id="response"
|
id='response'
|
||||||
type="text"
|
type='text'
|
||||||
autoComplete='off'
|
autoComplete='off'
|
||||||
placeholder="TYPE THE CAPTCHA HERE AND PRESS ENTER"
|
placeholder='TYPE THE CAPTCHA HERE AND PRESS ENTER'
|
||||||
ref={responseRef}
|
ref={responseRef}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
autoFocus />
|
autoFocus
|
||||||
|
/>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<img src="" alt="loading..." style={{ visibility: "hidden" }} />
|
<img src='' alt='loading...' style={{ visibility: 'hidden' }} />
|
||||||
) : (
|
) : (
|
||||||
imageSources[currentChallengeIndex] && <img src={imageSources[currentChallengeIndex]} alt="captcha" />
|
imageSources[currentChallengeIndex] && <img src={imageSources[currentChallengeIndex]} alt='captcha' />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span style={{lineHeight: '1.7'}}>
|
<span style={{ lineHeight: '1.7' }}>
|
||||||
Challenge {currentChallengeIndex + 1} of {totalChallenges}
|
Challenge {currentChallengeIndex + 1} of {totalChallenges}
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
id="nav"
|
id='nav'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (currentChallengeIndex + 1 < totalChallenges) {
|
if (currentChallengeIndex + 1 < totalChallenges) {
|
||||||
setCurrentChallengeIndex((currentChallengeIndex + 1) % totalChallenges);
|
setCurrentChallengeIndex((currentChallengeIndex + 1) % totalChallenges);
|
||||||
@@ -194,7 +192,7 @@ const CaptchaModal = () => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{currentChallengeIndex + 1 < totalChallenges ? "Next" : "Submit"}
|
{currentChallengeIndex + 1 < totalChallenges ? 'Next' : 'Submit'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useAccount, useCreateSubplebbit,
|
import {
|
||||||
|
useAccount,
|
||||||
|
useCreateSubplebbit,
|
||||||
// useSubplebbits, useAccountSubplebbits
|
// useSubplebbits, useAccountSubplebbits
|
||||||
} from '@plebbit/plebbit-react-hooks';
|
} from '@plebbit/plebbit-react-hooks';
|
||||||
import { StyledModal } from '../styled/modals/CreateBoardModal.styled';
|
import { StyledModal } from '../styled/modals/CreateBoardModal.styled';
|
||||||
import useGeneralStore from '../../hooks/stores/useGeneralStore';
|
import useGeneralStore from '../../hooks/stores/useGeneralStore';
|
||||||
import Modal from 'react-modal';
|
import Modal from 'react-modal';
|
||||||
@@ -10,7 +12,7 @@ import useError from '../../hooks/useError';
|
|||||||
import useSuccess from '../../hooks/useSuccess';
|
import useSuccess from '../../hooks/useSuccess';
|
||||||
|
|
||||||
const CreateBoardModal = ({ isOpen, closeModal }) => {
|
const CreateBoardModal = ({ isOpen, closeModal }) => {
|
||||||
const { selectedStyle } = useGeneralStore(state => state);
|
const { selectedStyle } = useGeneralStore((state) => state);
|
||||||
|
|
||||||
const account = useAccount();
|
const account = useAccount();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -29,7 +31,6 @@ const CreateBoardModal = ({ isOpen, closeModal }) => {
|
|||||||
const [rules, setRules] = useState([]);
|
const [rules, setRules] = useState([]);
|
||||||
const [editIndex, setEditIndex] = useState(-1);
|
const [editIndex, setEditIndex] = useState(-1);
|
||||||
|
|
||||||
|
|
||||||
const handleAddRule = () => {
|
const handleAddRule = () => {
|
||||||
if (rule.trim() === '') {
|
if (rule.trim() === '') {
|
||||||
setNewErrorMessage('Rule field is empty');
|
setNewErrorMessage('Rule field is empty');
|
||||||
@@ -41,15 +42,14 @@ const CreateBoardModal = ({ isOpen, closeModal }) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(editIndex > -1){
|
if (editIndex > -1) {
|
||||||
setRules(rules.map((r, i) => i === editIndex ? rule : r));
|
setRules(rules.map((r, i) => (i === editIndex ? rule : r)));
|
||||||
setEditIndex(-1);
|
setEditIndex(-1);
|
||||||
} else {
|
} else {
|
||||||
setRules([...rules, rule]);
|
setRules([...rules, rule]);
|
||||||
}
|
}
|
||||||
setRule('');
|
setRule('');
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (ruleInputRef.current) {
|
if (ruleInputRef.current) {
|
||||||
@@ -57,39 +57,34 @@ const CreateBoardModal = ({ isOpen, closeModal }) => {
|
|||||||
}
|
}
|
||||||
}, [rules]);
|
}, [rules]);
|
||||||
|
|
||||||
|
|
||||||
const handleKeyDown = (e) => {
|
const handleKeyDown = (e) => {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
handleAddRule();
|
handleAddRule();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
const handleEditRule = (index) => {
|
const handleEditRule = (index) => {
|
||||||
setRule(rules[index]);
|
setRule(rules[index]);
|
||||||
setEditIndex(index);
|
setEditIndex(index);
|
||||||
ruleInputRef.current.focus();
|
ruleInputRef.current.focus();
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
const handleDeleteRule = (index) => {
|
const handleDeleteRule = (index) => {
|
||||||
if (index === editIndex) {
|
if (index === editIndex) {
|
||||||
setEditIndex(-1);
|
setEditIndex(-1);
|
||||||
}
|
}
|
||||||
setRules(rules.filter((_, i) => i !== index));
|
setRules(rules.filter((_, i) => i !== index));
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
const createSubplebbitOptions = {
|
const createSubplebbitOptions = {
|
||||||
title: title || undefined,
|
title: title || undefined,
|
||||||
description: description || undefined,
|
description: description || undefined,
|
||||||
suggested: {
|
suggested: {
|
||||||
avatarUrl : avatar || undefined,
|
avatarUrl: avatar || undefined,
|
||||||
},
|
},
|
||||||
roles: moderators || undefined,
|
roles: moderators || undefined,
|
||||||
rules: rules || undefined,
|
rules: rules || undefined,
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
const resetFields = () => {
|
const resetFields = () => {
|
||||||
setTitle('');
|
setTitle('');
|
||||||
@@ -98,23 +93,21 @@ const CreateBoardModal = ({ isOpen, closeModal }) => {
|
|||||||
setModerators('');
|
setModerators('');
|
||||||
setRule('');
|
setRule('');
|
||||||
setRules([]);
|
setRules([]);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
const { createdSubplebbit, createSubplebbit } = useCreateSubplebbit(createSubplebbitOptions);
|
const { createdSubplebbit, createSubplebbit } = useCreateSubplebbit(createSubplebbitOptions);
|
||||||
|
|
||||||
|
|
||||||
const handleCreateBoard = async () => {
|
const handleCreateBoard = async () => {
|
||||||
let moderatorAddresses = moderators.trim() ? moderators.split(',').map(addr => addr.trim()) : [];
|
let moderatorAddresses = moderators.trim() ? moderators.split(',').map((addr) => addr.trim()) : [];
|
||||||
let invalidAddresses = moderatorAddresses.filter(addr => !(addr.endsWith('.eth') || (addr.startsWith('12D3KooW') && addr.length === 52)));
|
let invalidAddresses = moderatorAddresses.filter((addr) => !(addr.endsWith('.eth') || (addr.startsWith('12D3KooW') && addr.length === 52)));
|
||||||
|
|
||||||
if (invalidAddresses.length > 0) {
|
if (invalidAddresses.length > 0) {
|
||||||
setNewErrorMessage("Invalid moderator addresses: " + invalidAddresses.join(", "));
|
setNewErrorMessage('Invalid moderator addresses: ' + invalidAddresses.join(', '));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const roles = {};
|
const roles = {};
|
||||||
moderatorAddresses.forEach(addr => {
|
moderatorAddresses.forEach((addr) => {
|
||||||
roles[addr] = { role: 'moderator' };
|
roles[addr] = { role: 'moderator' };
|
||||||
});
|
});
|
||||||
roles[account.author.address] = { role: 'admin' };
|
roles[account.author.address] = { role: 'admin' };
|
||||||
@@ -133,7 +126,7 @@ const CreateBoardModal = ({ isOpen, closeModal }) => {
|
|||||||
|
|
||||||
if (avatar) {
|
if (avatar) {
|
||||||
createSubplebbitOptions.suggested = {
|
createSubplebbitOptions.suggested = {
|
||||||
avatarUrl : avatar,
|
avatarUrl: avatar,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,7 +146,7 @@ const CreateBoardModal = ({ isOpen, closeModal }) => {
|
|||||||
setNewSuccessMessage('Board created successfully, address: ' + createdSubplebbit.address);
|
setNewSuccessMessage('Board created successfully, address: ' + createdSubplebbit.address);
|
||||||
navigate(`/p/${createdSubplebbit.address}`);
|
navigate(`/p/${createdSubplebbit.address}`);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
// remove after testing:
|
// remove after testing:
|
||||||
// const {accountSubplebbits} = useAccountSubplebbits()
|
// const {accountSubplebbits} = useAccountSubplebbits()
|
||||||
@@ -165,114 +158,68 @@ const CreateBoardModal = ({ isOpen, closeModal }) => {
|
|||||||
<StyledModal
|
<StyledModal
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onRequestClose={closeModal}
|
onRequestClose={closeModal}
|
||||||
contentLabel="Create Board"
|
contentLabel='Create Board'
|
||||||
selectedStyle={selectedStyle}
|
selectedStyle={selectedStyle}
|
||||||
style={{ overlay: { backgroundColor: "rgba(0,0,0,.25)" }}}
|
style={{ overlay: { backgroundColor: 'rgba(0,0,0,.25)' } }}
|
||||||
>
|
>
|
||||||
<div className="modal-content" ref={nodeRef}>
|
<div className='modal-content' ref={nodeRef}>
|
||||||
<div className="modal-header">
|
<div className='modal-header'>
|
||||||
Create Board
|
Create Board
|
||||||
<button className="icon" onClick={() => closeModal()} title="close" />
|
<button className='icon' onClick={() => closeModal()} title='close' />
|
||||||
</div>
|
</div>
|
||||||
<ul id="form">
|
<ul id='form'>
|
||||||
<div id="explaination">NOTE: plebbit is P2P, your board will stay online for as long as you leave the plebchan desktop app (full node) connected to the Internet.</div>
|
<div id='explaination'>
|
||||||
<li className='settings-option disc'>
|
NOTE: plebbit is P2P, your board will stay online for as long as you leave the plebchan desktop app (full node) connected to the Internet.
|
||||||
Title
|
|
||||||
</li>
|
|
||||||
<div className='settings-tip'>
|
|
||||||
Optional, useful to describe the board next to its p/address.
|
|
||||||
</div>
|
</div>
|
||||||
|
<li className='settings-option disc'>Title</li>
|
||||||
|
<div className='settings-tip'>Optional, useful to describe the board next to its p/address.</div>
|
||||||
<div className='settings-input'>
|
<div className='settings-input'>
|
||||||
<input
|
<input id='name' type='text' value={title} onChange={(e) => setTitle(e.target.value)} placeholder='Board Title' />
|
||||||
id="name"
|
|
||||||
type="text"
|
|
||||||
value={title}
|
|
||||||
onChange={(e) => setTitle(e.target.value)}
|
|
||||||
placeholder="Board Title"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<li className='settings-option disc'>
|
|
||||||
Description
|
|
||||||
</li>
|
|
||||||
<div className='settings-tip'>
|
|
||||||
Optional, displayed as sticky with the board's avatar if set.
|
|
||||||
</div>
|
</div>
|
||||||
|
<li className='settings-option disc'>Description</li>
|
||||||
|
<div className='settings-tip'>Optional, displayed as sticky with the board's avatar if set.</div>
|
||||||
<div className='settings-input'>
|
<div className='settings-input'>
|
||||||
<textarea
|
<textarea id='description' type='text' value={description} onChange={(e) => setDescription(e.target.value)} placeholder='Add a description' />
|
||||||
id="description"
|
|
||||||
type="text"
|
|
||||||
value={description}
|
|
||||||
onChange={(e) => setDescription(e.target.value)}
|
|
||||||
placeholder="Add a description"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<li className='settings-option disc'>
|
|
||||||
Avatar
|
|
||||||
</li>
|
|
||||||
<div className='settings-tip'>
|
|
||||||
Optional, set an image that represents your board.
|
|
||||||
</div>
|
</div>
|
||||||
|
<li className='settings-option disc'>Avatar</li>
|
||||||
|
<div className='settings-tip'>Optional, set an image that represents your board.</div>
|
||||||
<div className='settings-input'>
|
<div className='settings-input'>
|
||||||
<input
|
<input id='name' type='text' value={avatar} onChange={(e) => setAvatar(e.target.value)} placeholder='https://example.com/image.png' />
|
||||||
id="name"
|
|
||||||
type="text"
|
|
||||||
value={avatar}
|
|
||||||
onChange={(e) => setAvatar(e.target.value)}
|
|
||||||
placeholder="https://example.com/image.png"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<li className='settings-option disc'>
|
|
||||||
Moderators
|
|
||||||
</li>
|
|
||||||
<div className='settings-tip'>
|
|
||||||
Optional, let other users help you moderate your board.
|
|
||||||
</div>
|
</div>
|
||||||
|
<li className='settings-option disc'>Moderators</li>
|
||||||
|
<div className='settings-tip'>Optional, let other users help you moderate your board.</div>
|
||||||
<div className='settings-input'>
|
<div className='settings-input'>
|
||||||
<textarea
|
<textarea id='name' type='text' value={moderators} onChange={(e) => setModerators(e.target.value)} placeholder='username.eth, 12D3KooW..., username2.eth' />
|
||||||
id="name"
|
|
||||||
type="text"
|
|
||||||
value={moderators}
|
|
||||||
onChange={(e) => setModerators(e.target.value)}
|
|
||||||
placeholder='username.eth, 12D3KooW..., username2.eth'
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<li className='settings-option disc'>
|
|
||||||
Rules
|
|
||||||
</li>
|
|
||||||
<div className='settings-tip'>
|
|
||||||
Optional, listed in a sticky on top. Maximum 15 rules.
|
|
||||||
</div>
|
</div>
|
||||||
|
<li className='settings-option disc'>Rules</li>
|
||||||
|
<div className='settings-tip'>Optional, listed in a sticky on top. Maximum 15 rules.</div>
|
||||||
<div className='settings-input'>
|
<div className='settings-input'>
|
||||||
<input
|
<input ref={ruleInputRef} id='rule' type='text' value={rule} onChange={(e) => setRule(e.target.value)} onKeyDown={handleKeyDown} placeholder='Add a rule' />
|
||||||
ref={ruleInputRef}
|
|
||||||
id="rule"
|
|
||||||
type="text"
|
|
||||||
value={rule}
|
|
||||||
onChange={(e) => setRule(e.target.value)}
|
|
||||||
onKeyDown={handleKeyDown}
|
|
||||||
placeholder="Add a rule"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<button style={{display: rules.length > 0 ? 'none' : 'block'}} id="rule-btn" className={rules.length > 0 ? "relative" : ""} onClick={handleAddRule}>
|
<button style={{ display: rules.length > 0 ? 'none' : 'block' }} id='rule-btn' className={rules.length > 0 ? 'relative' : ''} onClick={handleAddRule}>
|
||||||
{editIndex > -1 ? "Update Rule" : "Add Rule"}
|
{editIndex > -1 ? 'Update Rule' : 'Add Rule'}
|
||||||
</button>
|
</button>
|
||||||
{rules.length > 0 && (
|
{rules.length > 0 && (
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>
|
<legend>
|
||||||
<button className={rules.length > 0 ? "relative" : ""} onClick={handleAddRule}>
|
<button className={rules.length > 0 ? 'relative' : ''} onClick={handleAddRule}>
|
||||||
{editIndex > -1 ? "Update Rule" : "Add Rule"}
|
{editIndex > -1 ? 'Update Rule' : 'Add Rule'}
|
||||||
</button>
|
</button>
|
||||||
</legend>
|
</legend>
|
||||||
{rules.map((rule, index) => (
|
{rules.map((rule, index) => (
|
||||||
<div key={index} className="rule-item">
|
<div key={index} className='rule-item'>
|
||||||
<p>{index+1}. {rule}</p>
|
<p>
|
||||||
|
{index + 1}. {rule}
|
||||||
|
</p>
|
||||||
<button onClick={() => handleEditRule(index)}>Edit</button>
|
<button onClick={() => handleEditRule(index)}>Edit</button>
|
||||||
<button onClick={() => handleDeleteRule(index)}>Delete</button>
|
<button onClick={() => handleDeleteRule(index)}>Delete</button>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
)}
|
)}
|
||||||
<button onClick={handleCreateBoard} id="create-board-btn">Create Board</button>
|
<button onClick={handleCreateBoard} id='create-board-btn'>
|
||||||
|
Create Board
|
||||||
|
</button>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</StyledModal>
|
</StyledModal>
|
||||||
|
|||||||
@@ -4,19 +4,14 @@ import useGeneralStore from '../../hooks/stores/useGeneralStore';
|
|||||||
import Modal from 'react-modal';
|
import Modal from 'react-modal';
|
||||||
import Draggable from 'react-draggable';
|
import Draggable from 'react-draggable';
|
||||||
|
|
||||||
|
|
||||||
const EditModal = ({ isOpen, closeModal, originalCommentContent }) => {
|
const EditModal = ({ isOpen, closeModal, originalCommentContent }) => {
|
||||||
const {
|
const { setEditedComment, selectedStyle } = useGeneralStore((state) => state);
|
||||||
setEditedComment,
|
|
||||||
selectedStyle,
|
|
||||||
} = useGeneralStore(state => state);
|
|
||||||
|
|
||||||
const nodeRef = useRef(null);
|
const nodeRef = useRef(null);
|
||||||
const commentRef = useRef();
|
const commentRef = useRef();
|
||||||
|
|
||||||
const [isMobile, setIsMobile] = useState(window.innerWidth <= 480);
|
const [isMobile, setIsMobile] = useState(window.innerWidth <= 480);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleResize = () => setIsMobile(window.innerWidth <= 480);
|
const handleResize = () => setIsMobile(window.innerWidth <= 480);
|
||||||
window.addEventListener('resize', handleResize);
|
window.addEventListener('resize', handleResize);
|
||||||
@@ -26,40 +21,43 @@ const EditModal = ({ isOpen, closeModal, originalCommentContent }) => {
|
|||||||
};
|
};
|
||||||
}, [setIsMobile]);
|
}, [setIsMobile]);
|
||||||
|
|
||||||
|
|
||||||
const handleSaveEdit = () => {
|
const handleSaveEdit = () => {
|
||||||
setEditedComment(commentRef.current.value);
|
setEditedComment(commentRef.current.value);
|
||||||
closeModal();
|
closeModal();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledModal
|
<StyledModal
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onRequestClose={closeModal}
|
onRequestClose={closeModal}
|
||||||
contentLabel="Edit Comment"
|
contentLabel='Edit Comment'
|
||||||
shouldCloseOnEsc={false}
|
shouldCloseOnEsc={false}
|
||||||
shouldCloseOnOverlayClick={isMobile}
|
shouldCloseOnOverlayClick={isMobile}
|
||||||
selectedStyle={selectedStyle}
|
selectedStyle={selectedStyle}
|
||||||
style={isMobile ? ({ overlay: { backgroundColor: "rgba(0,0,0,.25)" }}) : ({ overlay: { backgroundColor: "rgba(0,0,0,0)" }})}>
|
style={isMobile ? { overlay: { backgroundColor: 'rgba(0,0,0,.25)' } } : { overlay: { backgroundColor: 'rgba(0,0,0,0)' } }}
|
||||||
<Draggable handle=".modal-header" nodeRef={nodeRef} disabled={isMobile}>
|
>
|
||||||
<div className="modal-content" ref={nodeRef}>
|
<Draggable handle='.modal-header' nodeRef={nodeRef} disabled={isMobile}>
|
||||||
<div className="modal-header">
|
<div className='modal-content' ref={nodeRef}>
|
||||||
|
<div className='modal-header'>
|
||||||
Edit Comment
|
Edit Comment
|
||||||
<button className="icon" onClick={() => closeModal()} title="close" />
|
<button className='icon' onClick={() => closeModal()} title='close' />
|
||||||
</div>
|
</div>
|
||||||
<div id="form">
|
<div id='form'>
|
||||||
<div className="textarea-wrapper">
|
<div className='textarea-wrapper'>
|
||||||
<textarea className="textarea"
|
<textarea
|
||||||
rows="4"
|
className='textarea'
|
||||||
style={{paddingTop: '0'}}
|
rows='4'
|
||||||
placeholder="Comment"
|
style={{ paddingTop: '0' }}
|
||||||
defaultValue={originalCommentContent}
|
placeholder='Comment'
|
||||||
wrap="soft"
|
defaultValue={originalCommentContent}
|
||||||
ref={commentRef} />
|
wrap='soft'
|
||||||
|
ref={commentRef}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button id="next" onClick={handleSaveEdit}>Save</button>
|
<button id='next' onClick={handleSaveEdit}>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,26 +1,17 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from 'react';
|
||||||
import Modal from "react-modal";
|
import Modal from 'react-modal';
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from 'react-router-dom';
|
||||||
import { useComment, usePublishCommentEdit } from "@plebbit/plebbit-react-hooks";
|
import { useComment, usePublishCommentEdit } from '@plebbit/plebbit-react-hooks';
|
||||||
import { StyledModal } from "../styled/modals/ModerationModal.styled";
|
import { StyledModal } from '../styled/modals/ModerationModal.styled';
|
||||||
import useGeneralStore from "../../hooks/stores/useGeneralStore";
|
import useGeneralStore from '../../hooks/stores/useGeneralStore';
|
||||||
import useError from "../../hooks/useError";
|
import useError from '../../hooks/useError';
|
||||||
import useSuccess from "../../hooks/useSuccess";
|
import useSuccess from '../../hooks/useSuccess';
|
||||||
|
|
||||||
|
|
||||||
const ModerationModal = ({ isOpen, closeModal, deletePost }) => {
|
const ModerationModal = ({ isOpen, closeModal, deletePost }) => {
|
||||||
const {
|
const { selectedAddress, selectedStyle, setCaptchaResponse, setChallengesArray, setIsCaptchaOpen, setIsModEdit, moderatingCommentCid, setResolveCaptchaPromise } =
|
||||||
selectedAddress,
|
useGeneralStore((state) => state);
|
||||||
selectedStyle,
|
|
||||||
setCaptchaResponse,
|
|
||||||
setChallengesArray,
|
|
||||||
setIsCaptchaOpen,
|
|
||||||
setIsModEdit,
|
|
||||||
moderatingCommentCid,
|
|
||||||
setResolveCaptchaPromise,
|
|
||||||
} = useGeneralStore(state => state);
|
|
||||||
|
|
||||||
const comment = useComment({commentCid: moderatingCommentCid});
|
const comment = useComment({ commentCid: moderatingCommentCid });
|
||||||
|
|
||||||
const [pin, setPin] = useState(comment.pinned);
|
const [pin, setPin] = useState(comment.pinned);
|
||||||
const [deleteThread, setDeleteThread] = useState(deletePost);
|
const [deleteThread, setDeleteThread] = useState(deletePost);
|
||||||
@@ -31,56 +22,50 @@ const ModerationModal = ({ isOpen, closeModal, deletePost }) => {
|
|||||||
const [, setNewErrorMessage] = useError();
|
const [, setNewErrorMessage] = useError();
|
||||||
const [, setNewSuccessMessage] = useSuccess();
|
const [, setNewSuccessMessage] = useSuccess();
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setPin(comment.pinned);
|
setPin(comment.pinned);
|
||||||
setClose(comment.locked);
|
setClose(comment.locked);
|
||||||
}, [comment]);
|
}, [comment]);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setDeleteThread(deletePost);
|
setDeleteThread(deletePost);
|
||||||
}, [deletePost]);
|
}, [deletePost]);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
setDeleteThread(deletePost);
|
setDeleteThread(deletePost);
|
||||||
}
|
}
|
||||||
}, [isOpen, deletePost]);
|
}, [isOpen, deletePost]);
|
||||||
|
|
||||||
|
|
||||||
const handleCloseModal = () => {
|
const handleCloseModal = () => {
|
||||||
setDeleteThread(false);
|
setDeleteThread(false);
|
||||||
closeModal();
|
closeModal();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const onChallengeVerification = (challengeVerification) => {
|
const onChallengeVerification = (challengeVerification) => {
|
||||||
if (challengeVerification.challengeSuccess === true) {
|
if (challengeVerification.challengeSuccess === true) {
|
||||||
setNewSuccessMessage('Challenge Success'); console.log('challenge success', challengeVerification);
|
setNewSuccessMessage('Challenge Success');
|
||||||
|
console.log('challenge success', challengeVerification);
|
||||||
} else if (challengeVerification.challengeSuccess === false) {
|
} else if (challengeVerification.challengeSuccess === false) {
|
||||||
setNewErrorMessage(`Challenge Failed, reason: ${challengeVerification.reason}. Errors: ${challengeVerification.errors}`);
|
setNewErrorMessage(`Challenge Failed, reason: ${challengeVerification.reason}. Errors: ${challengeVerification.errors}`);
|
||||||
console.log('challenge failed', challengeVerification);
|
console.log('challenge failed', challengeVerification);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const onChallenge = async (challenges, comment) => {
|
const onChallenge = async (challenges, comment) => {
|
||||||
let challengeAnswers = [];
|
let challengeAnswers = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
challengeAnswers = await getChallengeAnswersFromUser(challenges)
|
challengeAnswers = await getChallengeAnswersFromUser(challenges);
|
||||||
}
|
} catch (error) {
|
||||||
catch (error) {
|
setNewErrorMessage(error.message);
|
||||||
setNewErrorMessage(error.message); console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
if (challengeAnswers) {
|
if (challengeAnswers) {
|
||||||
await comment.publishChallengeAnswers(challengeAnswers)
|
await comment.publishChallengeAnswers(challengeAnswers);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const getChallengeAnswersFromUser = async (challenges) => {
|
const getChallengeAnswersFromUser = async (challenges) => {
|
||||||
setChallengesArray(challenges);
|
setChallengesArray(challenges);
|
||||||
|
|
||||||
@@ -115,19 +100,17 @@ const ModerationModal = ({ isOpen, closeModal, deletePost }) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const [publishCommentEditOptions, setPublishCommentEditOptions] = useState({
|
const [publishCommentEditOptions, setPublishCommentEditOptions] = useState({
|
||||||
commentCid: moderatingCommentCid,
|
commentCid: moderatingCommentCid,
|
||||||
subplebbitAddress: selectedAddress,
|
subplebbitAddress: selectedAddress,
|
||||||
onChallenge,
|
onChallenge,
|
||||||
onChallengeVerification,
|
onChallengeVerification,
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
setNewErrorMessage(error.message); console.log(error);
|
setNewErrorMessage(error.message);
|
||||||
|
console.log(error);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedAddress) {
|
if (selectedAddress) {
|
||||||
setPublishCommentEditOptions((prevOptions) => ({
|
setPublishCommentEditOptions((prevOptions) => ({
|
||||||
@@ -137,10 +120,8 @@ const ModerationModal = ({ isOpen, closeModal, deletePost }) => {
|
|||||||
}
|
}
|
||||||
}, [selectedAddress]);
|
}, [selectedAddress]);
|
||||||
|
|
||||||
|
|
||||||
const { publishCommentEdit } = usePublishCommentEdit(publishCommentEditOptions);
|
const { publishCommentEdit } = usePublishCommentEdit(publishCommentEditOptions);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setPublishCommentEditOptions((prevOptions) => ({
|
setPublishCommentEditOptions((prevOptions) => ({
|
||||||
...prevOptions,
|
...prevOptions,
|
||||||
@@ -148,7 +129,6 @@ const ModerationModal = ({ isOpen, closeModal, deletePost }) => {
|
|||||||
}));
|
}));
|
||||||
}, [moderatingCommentCid]);
|
}, [moderatingCommentCid]);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let isActive = true;
|
let isActive = true;
|
||||||
if (publishCommentEditOptions && triggerPublishCommentEdit) {
|
if (publishCommentEditOptions && triggerPublishCommentEdit) {
|
||||||
@@ -165,80 +145,64 @@ const ModerationModal = ({ isOpen, closeModal, deletePost }) => {
|
|||||||
};
|
};
|
||||||
}, [triggerPublishCommentEdit, publishCommentEdit, publishCommentEditOptions]);
|
}, [triggerPublishCommentEdit, publishCommentEdit, publishCommentEditOptions]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledModal
|
<StyledModal
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onRequestClose={handleCloseModal}
|
onRequestClose={handleCloseModal}
|
||||||
contentLabel="Moderator Tools"
|
contentLabel='Moderator Tools'
|
||||||
style={{ overlay: { backgroundColor: "rgba(0,0,0,.25)" }}}
|
style={{ overlay: { backgroundColor: 'rgba(0,0,0,.25)' } }}
|
||||||
selectedStyle={selectedStyle}
|
selectedStyle={selectedStyle}
|
||||||
>
|
>
|
||||||
<div className="panel">
|
<div className='panel'>
|
||||||
<div className="panel-header">
|
<div className='panel-header'>
|
||||||
Moderator Tools
|
Moderator Tools
|
||||||
<Link to="" onClick={handleCloseModal}>
|
<Link to='' onClick={handleCloseModal}>
|
||||||
<span className="icon" title="close" />
|
<span className='icon' title='close' />
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<ul className="settings-cat">
|
<ul className='settings-cat'>
|
||||||
<li className="settings-cat-lbl">
|
<li className='settings-cat-lbl'>
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" style={{marginRight: "10px"}}
|
<input type='checkbox' style={{ marginRight: '10px' }} checked={pin} onChange={() => setPin(!pin)} />
|
||||||
checked={pin} onChange={() => setPin(!pin)} />
|
|
||||||
Pin post
|
Pin post
|
||||||
</label>
|
</label>
|
||||||
</li>
|
</li>
|
||||||
<li className="settings-tip">
|
<li className='settings-tip'>Pin the post to make it a sticky, showed at the top of the board even as new posts are submitted.</li>
|
||||||
Pin the post to make it a sticky, showed at the top of the board even as new posts are submitted.
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
<ul className="settings-cat">
|
<ul className='settings-cat'>
|
||||||
<li className="settings-cat-lbl">
|
<li className='settings-cat-lbl'>
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" style={{marginRight: "10px"}}
|
<input type='checkbox' style={{ marginRight: '10px' }} checked={deleteThread} onChange={() => setDeleteThread(!deleteThread)} />
|
||||||
checked={deleteThread} onChange={() => setDeleteThread(!deleteThread)} />
|
|
||||||
Delete post
|
Delete post
|
||||||
</label>
|
</label>
|
||||||
</li>
|
</li>
|
||||||
<li className="settings-tip">
|
<li className='settings-tip'>The post will no longer visible to other users, but the person who posted it can still see it in their own account.</li>
|
||||||
The post will no longer visible to other users, but the person who posted it can still see it in their own account.
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
<ul className="settings-cat">
|
<ul className='settings-cat'>
|
||||||
<li className="settings-cat-lbl">
|
<li className='settings-cat-lbl'>
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" style={{marginRight: "10px"}}
|
<input type='checkbox' style={{ marginRight: '10px' }} checked={close} onChange={() => setClose(!close)} />
|
||||||
checked={close} onChange={() => setClose(!close)} />
|
|
||||||
Close post
|
Close post
|
||||||
</label>
|
</label>
|
||||||
</li>
|
|
||||||
<li className="settings-tip">
|
|
||||||
Closing a post allows users to still see the content, but they cannot add any new replies to it.
|
|
||||||
</li>
|
</li>
|
||||||
|
<li className='settings-tip'>Closing a post allows users to still see the content, but they cannot add any new replies to it.</li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul className="settings-cat">
|
<ul className='settings-cat'>
|
||||||
<li className="settings-option disc">
|
<li className='settings-option disc'>Reason</li>
|
||||||
Reason
|
<li className='settings-tip'>Help people become better posters by giving a short reason why their post was removed.</li>
|
||||||
</li>
|
<li className='settings-input' style={{ marginTop: '-10px' }}>
|
||||||
<li className="settings-tip">
|
<textarea value={reason} placeholder='Enter reason here...' onChange={(e) => setReason(e.target.value)} />
|
||||||
Help people become better posters by giving a short reason why their post was removed.
|
|
||||||
</li>
|
|
||||||
<li className="settings-input" style={{marginTop: "-10px"}}>
|
|
||||||
<textarea value={reason} placeholder="Enter reason here..."
|
|
||||||
onChange={e => setReason(e.target.value)}/>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<button
|
<button
|
||||||
className="save-button"
|
className='save-button'
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
setPublishCommentEditOptions(prevOptions => ({
|
setPublishCommentEditOptions((prevOptions) => ({
|
||||||
...prevOptions,
|
...prevOptions,
|
||||||
pinned: pin,
|
pinned: pin,
|
||||||
removed: deleteThread,
|
removed: deleteThread,
|
||||||
locked: close,
|
locked: close,
|
||||||
reason: reason
|
reason: reason,
|
||||||
}));
|
}));
|
||||||
setTriggerPublishCommentEdit(true);
|
setTriggerPublishCommentEdit(true);
|
||||||
setIsModEdit(true);
|
setIsModEdit(true);
|
||||||
@@ -250,8 +214,8 @@ const ModerationModal = ({ isOpen, closeModal, deletePost }) => {
|
|||||||
</div>
|
</div>
|
||||||
</StyledModal>
|
</StyledModal>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
Modal.setAppElement("#root");
|
Modal.setAppElement('#root');
|
||||||
|
|
||||||
export default ModerationModal;
|
export default ModerationModal;
|
||||||
@@ -1,22 +1,18 @@
|
|||||||
import React, {useState, useEffect} from "react";
|
import React, { useState, useEffect } from 'react';
|
||||||
import { StyledModal } from '../styled/modals/ReplyModal.styled';
|
import { StyledModal } from '../styled/modals/ReplyModal.styled';
|
||||||
import useGeneralStore from "../../hooks/stores/useGeneralStore";
|
import useGeneralStore from '../../hooks/stores/useGeneralStore';
|
||||||
import Modal from "react-modal";
|
import Modal from 'react-modal';
|
||||||
import Draggable from "react-draggable";
|
import Draggable from 'react-draggable';
|
||||||
import getDate from "../../utils/getDate";
|
import getDate from '../../utils/getDate';
|
||||||
|
|
||||||
|
|
||||||
const OriginalCommentModal = ({ isOpen, closeModal, comment }) => {
|
const OriginalCommentModal = ({ isOpen, closeModal, comment }) => {
|
||||||
const {
|
const { selectedStyle } = useGeneralStore((state) => state);
|
||||||
selectedStyle,
|
|
||||||
} = useGeneralStore(state => state);
|
|
||||||
|
|
||||||
const [isMobile, setIsMobile] = useState(window.innerWidth <= 480);
|
const [isMobile, setIsMobile] = useState(window.innerWidth <= 480);
|
||||||
const nodeRef = React.useRef(null);
|
const nodeRef = React.useRef(null);
|
||||||
const originalCommentContent = comment.original?.content;
|
const originalCommentContent = comment.original?.content;
|
||||||
const timestamp = getDate(comment.timestamp);
|
const timestamp = getDate(comment.timestamp);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleResize = () => setIsMobile(window.innerWidth <= 480);
|
const handleResize = () => setIsMobile(window.innerWidth <= 480);
|
||||||
window.addEventListener('resize', handleResize);
|
window.addEventListener('resize', handleResize);
|
||||||
@@ -26,31 +22,33 @@ const OriginalCommentModal = ({ isOpen, closeModal, comment }) => {
|
|||||||
};
|
};
|
||||||
}, [setIsMobile]);
|
}, [setIsMobile]);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledModal
|
<StyledModal
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onRequestClose={closeModal}
|
onRequestClose={closeModal}
|
||||||
contentLabel="Original Comment"
|
contentLabel='Original Comment'
|
||||||
shouldCloseOnEsc={false}
|
shouldCloseOnEsc={false}
|
||||||
shouldCloseOnOverlayClick={isMobile}
|
shouldCloseOnOverlayClick={isMobile}
|
||||||
selectedStyle={selectedStyle}
|
selectedStyle={selectedStyle}
|
||||||
style={isMobile ? ({ overlay: { backgroundColor: "rgba(0,0,0,.25)" }}) : ({ overlay: { backgroundColor: "rgba(0,0,0,0)" }})}>
|
style={isMobile ? { overlay: { backgroundColor: 'rgba(0,0,0,.25)' } } : { overlay: { backgroundColor: 'rgba(0,0,0,0)' } }}
|
||||||
<Draggable handle=".modal-header" nodeRef={nodeRef} disabled={isMobile}>
|
>
|
||||||
<div className="modal-content" ref={nodeRef}>
|
<Draggable handle='.modal-header' nodeRef={nodeRef} disabled={isMobile}>
|
||||||
<div className="modal-header">
|
<div className='modal-content' ref={nodeRef}>
|
||||||
|
<div className='modal-header'>
|
||||||
Original at {timestamp}
|
Original at {timestamp}
|
||||||
<button className="icon" onClick={() => closeModal()} title="close" />
|
<button className='icon' onClick={() => closeModal()} title='close' />
|
||||||
</div>
|
</div>
|
||||||
<div id="form">
|
<div id='form'>
|
||||||
<div className="textarea-wrapper">
|
<div className='textarea-wrapper'>
|
||||||
<textarea className="textarea"
|
<textarea
|
||||||
rows="4"
|
className='textarea'
|
||||||
|
rows='4'
|
||||||
style={{ paddingTop: '0' }}
|
style={{ paddingTop: '0' }}
|
||||||
placeholder="Comment"
|
placeholder='Comment'
|
||||||
defaultValue={originalCommentContent}
|
defaultValue={originalCommentContent}
|
||||||
wrap="soft"
|
wrap='soft'
|
||||||
readOnly={true} />
|
readOnly={true}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import styled from "styled-components";
|
import styled from 'styled-components';
|
||||||
|
|
||||||
export const BoardStatsContainer = styled.div`
|
export const BoardStatsContainer = styled.div`
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -96,7 +95,7 @@ export const BoardStatsContainer = styled.div`
|
|||||||
}`;
|
}`;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
`;
|
`;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ToastContainer } from "react-toastify";
|
import { ToastContainer } from 'react-toastify';
|
||||||
import styled from "styled-components";
|
import styled from 'styled-components';
|
||||||
|
|
||||||
export const Toast = styled(ToastContainer)`
|
export const Toast = styled(ToastContainer)`
|
||||||
.Toastify__toast {
|
.Toastify__toast {
|
||||||
|
|||||||
@@ -23,9 +23,6 @@ export const AlertModal = styled.div`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
${({ selectedStyle }) => {
|
${({ selectedStyle }) => {
|
||||||
switch (selectedStyle) {
|
switch (selectedStyle) {
|
||||||
case 'Yotsuba':
|
case 'Yotsuba':
|
||||||
@@ -70,8 +67,8 @@ export const AlertModal = styled.div`
|
|||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
`;
|
`;
|
||||||
@@ -1,15 +1,17 @@
|
|||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import { persist } from 'zustand/middleware';
|
import { persist } from 'zustand/middleware';
|
||||||
|
|
||||||
const useAnonModeStore = create(persist(
|
const useAnonModeStore = create(
|
||||||
(set) => ({
|
persist(
|
||||||
anonymousMode: true,
|
(set) => ({
|
||||||
setAnonymousMode: (mode) => set({ anonymousMode: mode }),
|
anonymousMode: true,
|
||||||
}),
|
setAnonymousMode: (mode) => set({ anonymousMode: mode }),
|
||||||
{
|
}),
|
||||||
name: "anonmode_store",
|
{
|
||||||
getStorage: () => localStorage,
|
name: 'anonmode_store',
|
||||||
}
|
getStorage: () => localStorage,
|
||||||
));
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
export default useAnonModeStore;
|
export default useAnonModeStore;
|
||||||
+2
-2
@@ -21,14 +21,14 @@ root.render(
|
|||||||
<App />
|
<App />
|
||||||
</HashRouter>
|
</HashRouter>
|
||||||
</HelmetProvider>
|
</HelmetProvider>
|
||||||
</React.StrictMode>
|
</React.StrictMode>,
|
||||||
);
|
);
|
||||||
|
|
||||||
// set up PWA https://cra.link/PWA
|
// set up PWA https://cra.link/PWA
|
||||||
serviceWorkerRegistration.register();
|
serviceWorkerRegistration.register();
|
||||||
|
|
||||||
// add back button in android app
|
// add back button in android app
|
||||||
CapacitorApp.addListener('backButton', ({canGoBack}) => {
|
CapacitorApp.addListener('backButton', ({ canGoBack }) => {
|
||||||
if (canGoBack) {
|
if (canGoBack) {
|
||||||
window.history.back();
|
window.history.back();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ registerRoute(
|
|||||||
// match index.html
|
// match index.html
|
||||||
({ url }) => url.pathname === '/',
|
({ url }) => url.pathname === '/',
|
||||||
// serve cached index.html first but revalidate in background
|
// serve cached index.html first but revalidate in background
|
||||||
new StaleWhileRevalidate()
|
new StaleWhileRevalidate(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Precache all of the assets generated by your build process.
|
// Precache all of the assets generated by your build process.
|
||||||
@@ -51,7 +51,7 @@ registerRoute(
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html')
|
createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html'),
|
||||||
);
|
);
|
||||||
|
|
||||||
// An example runtime caching route for requests that aren't handled by the
|
// An example runtime caching route for requests that aren't handled by the
|
||||||
@@ -66,7 +66,7 @@ registerRoute(
|
|||||||
// least-recently used images are removed.
|
// least-recently used images are removed.
|
||||||
new ExpirationPlugin({ maxEntries: 50 }),
|
new ExpirationPlugin({ maxEntries: 50 }),
|
||||||
],
|
],
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
// This allows the web app to trigger skipWaiting via
|
// This allows the web app to trigger skipWaiting via
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const isLocalhost = Boolean(
|
|||||||
// [::1] is the IPv6 localhost address.
|
// [::1] is the IPv6 localhost address.
|
||||||
window.location.hostname === '[::1]' ||
|
window.location.hostname === '[::1]' ||
|
||||||
// 127.0.0.0/8 are considered localhost for IPv4.
|
// 127.0.0.0/8 are considered localhost for IPv4.
|
||||||
window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/)
|
window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/),
|
||||||
);
|
);
|
||||||
|
|
||||||
export function register(config) {
|
export function register(config) {
|
||||||
@@ -39,10 +39,7 @@ export function register(config) {
|
|||||||
// Add some additional logging to localhost, pointing developers to the
|
// Add some additional logging to localhost, pointing developers to the
|
||||||
// service worker/PWA documentation.
|
// service worker/PWA documentation.
|
||||||
navigator.serviceWorker.ready.then(() => {
|
navigator.serviceWorker.ready.then(() => {
|
||||||
console.log(
|
console.log('This web app is being served cache-first by a service ' + 'worker. To learn more, visit https://cra.link/PWA');
|
||||||
'This web app is being served cache-first by a service ' +
|
|
||||||
'worker. To learn more, visit https://cra.link/PWA'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Is not localhost. Just register service worker
|
// Is not localhost. Just register service worker
|
||||||
@@ -67,10 +64,7 @@ function registerValidSW(swUrl, config) {
|
|||||||
// At this point, the updated precached content has been fetched,
|
// At this point, the updated precached content has been fetched,
|
||||||
// but the previous service worker will still serve the older
|
// but the previous service worker will still serve the older
|
||||||
// content until all client tabs are closed.
|
// content until all client tabs are closed.
|
||||||
console.log(
|
console.log('New content is available and will be used when all ' + 'tabs for this page are closed. See https://cra.link/PWA.');
|
||||||
'New content is available and will be used when all ' +
|
|
||||||
'tabs for this page are closed. See https://cra.link/PWA.'
|
|
||||||
);
|
|
||||||
|
|
||||||
// Execute callback
|
// Execute callback
|
||||||
if (config && config.onUpdate) {
|
if (config && config.onUpdate) {
|
||||||
@@ -104,10 +98,7 @@ function checkValidServiceWorker(swUrl, config) {
|
|||||||
.then((response) => {
|
.then((response) => {
|
||||||
// Ensure service worker exists, and that we really are getting a JS file.
|
// Ensure service worker exists, and that we really are getting a JS file.
|
||||||
const contentType = response.headers.get('content-type');
|
const contentType = response.headers.get('content-type');
|
||||||
if (
|
if (response.status === 404 || (contentType != null && contentType.indexOf('javascript') === -1)) {
|
||||||
response.status === 404 ||
|
|
||||||
(contentType != null && contentType.indexOf('javascript') === -1)
|
|
||||||
) {
|
|
||||||
// No service worker found. Probably a different app. Reload the page.
|
// No service worker found. Probably a different app. Reload the page.
|
||||||
navigator.serviceWorker.ready.then((registration) => {
|
navigator.serviceWorker.ready.then((registration) => {
|
||||||
registration.unregister().then(() => {
|
registration.unregister().then(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user