diff --git a/dist/challenge/index.html b/dist/challenge/index.html
index a3b3fe7aff..a6c77b5938 100644
--- a/dist/challenge/index.html
+++ b/dist/challenge/index.html
@@ -15,7 +15,7 @@
src: url('../font/inter/bold.woff2') format('woff2');
}
- html, body { height: 100%; }
+ html, body { height: 100%; -webkit-app-region: drag; }
/*html.dark body { background-color: #171717; color: #a09f92; }*/
body { font-family: 'Inter', Helvetica, Arial, sans-serif; font-size: 14px; line-height: 22px; background-color: #fff; color: #252525; }
diff --git a/dist/embed/iframe.html b/dist/embed/iframe.html
index a8963150fd..d4ca48529c 100644
--- a/dist/embed/iframe.html
+++ b/dist/embed/iframe.html
@@ -82,6 +82,7 @@
let cachedHtml = '';
let height = 0;
let blockId = '';
+ let player;
win.off('message resize');
@@ -139,6 +140,23 @@
insertHtml(html);
};
+ if (processor == Processor.Youtube) {
+ window.onYouTubeIframeAPIReady = () => {
+ player = new YT.Player('player', {
+ events: {
+ onReady,
+ onStateChange,
+ }
+ });
+ };
+
+ const onReady = (event) => {
+ };
+
+ const onStateChange = (event) => {
+ };
+ };
+
loadLibs(libs, () => {
if (!insertBeforeLoad) {
insertHtml(html);
@@ -258,6 +276,10 @@
libs.push('https://cpwebassets.codepen.io/assets/embed/ei.js');
break;
};
+
+ case Processor.Youtube:
+ libs.push('https://www.youtube.com/iframe_api');
+ break;
};
return {
diff --git a/dist/extension/auth/index.html b/dist/extension/auth/index.html
new file mode 100644
index 0000000000..152adfb788
--- /dev/null
+++ b/dist/extension/auth/index.html
@@ -0,0 +1,10 @@
+
+
+
+
+ Anytype Web Clipper
+
+
+
+
+
\ No newline at end of file
diff --git a/dist/workers/graph.js b/dist/workers/graph.js
index 87e6741c3b..94395585e3 100644
--- a/dist/workers/graph.js
+++ b/dist/workers/graph.js
@@ -317,7 +317,6 @@ updateSettings = (param) => {
updateTheme = ({ theme, colors }) => {
data.colors = colors;
-
initTheme(theme);
redraw();
};
@@ -624,7 +623,9 @@ onClick = ({ x, y }) => {
onSelect = ({ x, y, selectRelated }) => {
const d = getNodeByCoords(x, y);
- let related = [];
+
+ let related = [];
+
if (d) {
if (selectRelated) {
related = edgeMap.get(d.id);
@@ -638,6 +639,7 @@ onSetRootId = ({ x, y }) => {
const d = getNodeByCoords(x, y);
if (d) {
this.setRootId({ rootId: d.id });
+ send('setRootId', { node: d.id });
};
};
diff --git a/electron.js b/electron.js
index 04ae6096bd..f4f897c5c4 100644
--- a/electron.js
+++ b/electron.js
@@ -9,6 +9,7 @@ const protocol = 'anytype';
const remote = require('@electron/remote/main');
const { installNativeMessagingHost } = require('./electron/js/lib/installNativeMessagingHost.js');
const binPath = fixPathForAsarUnpack(path.join(__dirname, 'dist', `anytypeHelper${is.windows ? '.exe' : ''}`));
+const Store = require('electron-store');
// Fix notifications app name
if (is.windows) {
@@ -16,6 +17,7 @@ if (is.windows) {
};
storage.setDataPath(app.getPath('userData'));
+Store.initRenderer();
const Api = require('./electron/js/api.js');
const ConfigManager = require('./electron/js/config.js');
diff --git a/electron/js/api.js b/electron/js/api.js
index 308f5452e0..17d9d8d658 100644
--- a/electron/js/api.js
+++ b/electron/js/api.js
@@ -50,8 +50,14 @@ class Api {
WindowManager.sendToAll('pin-check');
};
- setConfig (win, config) {
- ConfigManager.set(config, () => Util.send(win, 'config', ConfigManager.config));
+ setConfig (win, config, callBack) {
+ ConfigManager.set(config, () => {
+ Util.send(win, 'config', ConfigManager.config);
+
+ if (callBack) {
+ callBack();
+ };
+ });
};
setAccount (win, account) {
diff --git a/electron/js/menu.js b/electron/js/menu.js
index 4492b86d45..5848a4e4ac 100644
--- a/electron/js/menu.js
+++ b/electron/js/menu.js
@@ -1,5 +1,6 @@
const { app, shell, Menu, Tray } = require('electron');
const { is } = require('electron-util');
+const fs = require('fs');
const path = require('path');
const ConfigManager = require('./config.js');
const Util = require('./util.js');
@@ -64,16 +65,40 @@ class MenuManager {
Separator,
{
- label: Util.translate('electronMenuDirectory'), submenu: [
+ label: Util.translate('electronMenuOpen'), submenu: [
{ label: Util.translate('electronMenuWorkDirectory'), click: () => shell.openPath(Util.userPath()) },
{ label: Util.translate('electronMenuDataDirectory'), click: () => shell.openPath(Util.dataPath()) },
{ label: Util.translate('electronMenuConfigDirectory'), click: () => shell.openPath(Util.defaultUserDataPath()) },
{ label: Util.translate('electronMenuLogsDirectory'), click: () => shell.openPath(Util.logPath()) },
+ {
+ label: Util.translate('electronMenuCustomCss'),
+ click: () => {
+ const fp = path.join(Util.userPath(), 'custom.css');
+
+ if (!fs.existsSync(fp)) {
+ fs.writeFileSync(fp, '');
+ };
+
+ shell.openPath(fp);
+ },
+ },
]
},
Separator,
+ {
+ label: Util.translate('electronMenuApplyCustomCss'), type: 'checkbox', checked: !config.disableCss,
+ click: () => {
+ config.disableCss = !config.disableCss;
+ Api.setConfig(this.win, { disableCss: config.disableCss }, () => {
+ WindowManager.reloadAll();
+ });
+ },
+ },
+
+ Separator,
+
{ role: 'close', label: Util.translate('electronMenuClose') },
]
},
@@ -150,11 +175,11 @@ class MenuManager {
submenu: [
{
label: `${Util.translate('electronMenuReleaseNotes')} (${app.getVersion()})`,
- click: () => Util.send(this.win, 'popup', 'help', { preventResize: true, data: { document: 'whatsNew' } })
+ click: () => Util.send(this.win, 'popup', 'help', { data: { document: 'whatsNew' } })
},
{
label: Util.translate('electronMenuShortcuts'), accelerator: 'Ctrl+Space',
- click: () => Util.send(this.win, 'popup', 'shortcut', { preventResize: true })
+ click: () => Util.send(this.win, 'popup', 'shortcut', {})
},
Separator,
@@ -235,6 +260,7 @@ class MenuManager {
{ label: Util.translate('electronMenuDebugStat'), click: () => Util.send(this.win, 'commandGlobal', 'debugStat') },
{ label: Util.translate('electronMenuDebugReconcile'), click: () => Util.send(this.win, 'commandGlobal', 'debugReconcile') },
{ label: Util.translate('electronMenuDebugNet'), click: () => Util.send(this.win, 'commandGlobal', 'debugNet') },
+ { label: Util.translate('electronMenuDebugLog'), click: () => Util.send(this.win, 'commandGlobal', 'debugLog') },
Separator,
@@ -319,7 +345,7 @@ class MenuManager {
this.tray = new Tray (this.getTrayIcon());
this.tray.setToolTip('Anytype');
this.tray.setContextMenu(Menu.buildFromTemplate([
- { label: Util.translate('electronMenuOpen'), click: () => this.winShow() },
+ { label: Util.translate('electronMenuOpenApp'), click: () => this.winShow() },
Separator,
diff --git a/electron/js/preload.js b/electron/js/preload.js
index 55aaa97744..77f321592f 100644
--- a/electron/js/preload.js
+++ b/electron/js/preload.js
@@ -5,6 +5,9 @@ const os = require('os');
const path = require('path');
const mime = require('mime-types');
const tmpPath = () => app.getPath('temp');
+const Store = require('electron-store');
+const suffix = app.isPackaged ? '' : 'dev';
+const store = new Store({ name: [ 'localStorage', suffix ].join('-') });
contextBridge.exposeInMainWorld('Electron', {
version: {
@@ -16,6 +19,10 @@ contextBridge.exposeInMainWorld('Electron', {
platform: os.platform(),
arch: process.arch,
+ storeSet: (key, value) => store.set(key, value),
+ storeGet: key => store.get(key),
+ storeDelete: key => store.delete(key),
+
isPackaged: app.isPackaged,
userPath: () => app.getPath('userData'),
tmpPath,
@@ -26,7 +33,11 @@ contextBridge.exposeInMainWorld('Electron', {
fileMime: fp => mime.lookup(fp),
fileExt: fp => path.extname(fp).replace(/^./, ''),
fileSize: fp => fs.statSync(fp).size,
- isDirectory: fp => fs.lstatSync(fp).isDirectory(),
+ isDirectory: fp => {
+ let ret = false;
+ try { ret = fs.lstatSync(fp).isDirectory(); } catch (e) {};
+ return ret;
+ },
defaultPath: () => path.join(app.getPath('appData'), app.getName()),
currentWindow: () => getCurrentWindow(),
diff --git a/electron/js/server.js b/electron/js/server.js
index f52eb77892..c58f291af9 100644
--- a/electron/js/server.js
+++ b/electron/js/server.js
@@ -7,6 +7,7 @@ const { app, dialog, shell } = require('electron');
const Util = require('./util.js');
let maxStdErrChunksBuffer = 10;
+const winShutdownStdinMessage = 'shutdown\n';
class Server {
@@ -114,7 +115,12 @@ class Server {
});
this.stopTriggered = true;
- this.cp.kill(signal);
+ if (process.platform === 'win32') {
+ // it is not possible to handle os signals on windows, so we can't do graceful shutdown on go side
+ this.cp.stdin.write(winShutdownStdinMessage);
+ } else {
+ this.cp.kill(signal);
+ };
} else {
resolve();
};
@@ -131,4 +137,4 @@ class Server {
};
-module.exports = new Server();
\ No newline at end of file
+module.exports = new Server();
diff --git a/electron/js/window.js b/electron/js/window.js
index 902ed90488..9b0f36aef7 100644
--- a/electron/js/window.js
+++ b/electron/js/window.js
@@ -1,4 +1,4 @@
-const { app, BrowserWindow, nativeImage, dialog, screen } = require('electron');
+const { app, BrowserWindow, nativeImage, dialog } = require('electron');
const { is, fixPathForAsarUnpack } = require('electron-util');
const path = require('path');
const windowStateKeeper = require('electron-window-state');
@@ -115,6 +115,7 @@ class WindowManager {
height: state.height,
});
} else {
+ const { screen } = require('electron');
const primaryDisplay = screen.getPrimaryDisplay();
const { width, height } = primaryDisplay.workAreaSize;
@@ -141,10 +142,16 @@ class WindowManager {
};
createChallenge (options) {
+ const { screen } = require('electron');
+ const primaryDisplay = screen.getPrimaryDisplay();
+ const { width } = primaryDisplay.workAreaSize;
+
const win = this.create({}, {
backgroundColor: '',
width: 424,
height: 232,
+ x: Math.floor(width / 2 - 212),
+ y: 50,
titleBarStyle: 'hidden',
});
@@ -152,6 +159,7 @@ class WindowManager {
win.setMenu(null);
is.windows || is.linux ? win.showInactive() : win.show();
+ win.focus();
win.webContents.once('did-finish-load', () => {
win.webContents.postMessage('challenge', options);
diff --git a/extension/auth.tsx b/extension/auth.tsx
new file mode 100644
index 0000000000..7da76dc4ef
--- /dev/null
+++ b/extension/auth.tsx
@@ -0,0 +1,82 @@
+import * as React from 'react';
+import * as hs from 'history';
+import { Router, Route, Switch } from 'react-router-dom';
+import { RouteComponentProps } from 'react-router';
+import { Provider } from 'mobx-react';
+import { configure } from 'mobx';
+import { S, U } from 'Lib';
+
+import Index from './auth/index';
+import Success from './auth/success';
+import Util from './lib/util';
+
+import './scss/auth.scss';
+
+configure({ enforceActions: 'never' });
+
+const Routes = [
+ { path: '/' },
+ { path: '/:page' },
+];
+
+const Components = {
+ index: Index,
+ success: Success,
+};
+
+const memoryHistory = hs.createMemoryHistory;
+const history = memoryHistory();
+
+class RoutePage extends React.Component {
+
+ render () {
+ const { match } = this.props;
+ const params = match.params as any;
+ const page = params.page || 'index';
+ const Component = Components[page];
+
+ return Component ? : null;
+ };
+
+};
+
+class Auth extends React.Component {
+
+ render () {
+ return (
+
+
+
+
+ {Routes.map((item: any, i: number) => (
+
+ ))}
+
+
+
+
+ );
+ };
+
+ componentDidMount () {
+ U.Router.init(history);
+
+ /* @ts-ignore */
+ chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
+ switch (msg.type) {
+ case 'initAuth':
+ const { appKey, gatewayPort, serverPort } = msg;
+
+ Util.init(serverPort, gatewayPort);
+ Util.authorize(appKey);
+
+ sendResponse({});
+ break;
+ };
+ return true;
+ });
+ };
+
+};
+
+export default Auth;
\ No newline at end of file
diff --git a/extension/popup/challenge.tsx b/extension/auth/index.tsx
similarity index 60%
rename from extension/popup/challenge.tsx
rename to extension/auth/index.tsx
index 7ab9f087b7..8c6e98f87b 100644
--- a/extension/popup/challenge.tsx
+++ b/extension/auth/index.tsx
@@ -8,7 +8,7 @@ interface State {
error: string;
};
-const Challenge = observer(class Challenge extends React.Component {
+const Index = observer(class Index extends React.Component {
ref: any = null;
state = {
@@ -19,22 +19,20 @@ const Challenge = observer(class Challenge extends React.Component
+
this.ref = ref}
pinLength={4}
- focusOnMount={true}
onSuccess={this.onSuccess}
- onError={this.onError}
+ onError={() => {}}
/>
@@ -43,29 +41,35 @@ const Challenge = observer(class Challenge extends React.Component {
+ const urlParams = new URLSearchParams(window.location.search);
+ const data = JSON.parse(atob(urlParams.get('data') as string));
+
+ if (!data) {
+ this.setState({ error: 'Invalid data' });
+ return;
+ };
+
+ Util.init(data.serverPort, data.gatewayPort);
+
+ C.AccountLocalLinkSolveChallenge(data.challengeId, this.ref?.getValue().trim(), (message: any) => {
if (message.error.code) {
this.setState({ error: message.error.description });
return;
};
const { appKey } = message;
- const { serverPort, gatewayPort } = S.Extension;
Storage.set('appKey', appKey);
Util.authorize(appKey, () => {
- Util.sendMessage({ type: 'initIframe', appKey, serverPort, gatewayPort }, () => {});
+ Util.sendMessage({ type: 'initIframe', appKey, ...data }, () => {});
Util.sendMessage({ type: 'initMenu' }, () => {});
- U.Router.go('/create', {});
+ U.Router.go('/success', {});
});
});
};
- onError () {
- };
-
});
-export default Challenge;
\ No newline at end of file
+export default Index;
\ No newline at end of file
diff --git a/extension/auth/success.tsx b/extension/auth/success.tsx
new file mode 100644
index 0000000000..ab290a1776
--- /dev/null
+++ b/extension/auth/success.tsx
@@ -0,0 +1,21 @@
+import * as React from 'react';
+import { observer } from 'mobx-react';
+import { I } from 'Lib';
+
+interface State {
+ error: string;
+};
+
+const Success = observer(class Success extends React.Component {
+
+ render () {
+ return (
+
+
Successfully authorized!
+
+ );
+ };
+
+});
+
+export default Success;
\ No newline at end of file
diff --git a/extension/entry.tsx b/extension/entry.tsx
index 9a3c0854b5..924810b3b2 100644
--- a/extension/entry.tsx
+++ b/extension/entry.tsx
@@ -4,6 +4,7 @@ import $ from 'jquery';
import { C, U, J, S } from 'Lib';
import Popup from './popup';
import Iframe from './iframe';
+import Auth from './auth';
import Util from './lib/util';
import './scss/common.scss';
@@ -38,8 +39,8 @@ window.Anytype = {
window.AnytypeGlobalConfig = {
emojiUrl: J.Extension.clipper.emojiUrl,
menuBorderTop: 16,
- menuBorderBottom: 16,
- flagsMw: { request: false },
+ menuBorderBottom: 16,
+ flagsMw: { request: true },
};
let rootId = '';
@@ -52,6 +53,10 @@ if (Util.isPopup()) {
if (Util.isIframe()) {
rootId = `${J.Extension.clipper.prefix}-iframe`;
component = ;
+} else
+if (Util.isAuth()) {
+ rootId = `${J.Extension.clipper.prefix}-auth`;
+ component = ;
};
if (!rootId) {
diff --git a/extension/lib/util.ts b/extension/lib/util.ts
index 56e7d7bfc0..5bf16778bd 100644
--- a/extension/lib/util.ts
+++ b/extension/lib/util.ts
@@ -1,7 +1,8 @@
import { S, U, J, C, dispatcher } from 'Lib';
const INDEX_POPUP = '/popup/index.html';
-const INDEX_IFRAME = '/iframe/index.html'
+const INDEX_IFRAME = '/iframe/index.html';
+const INDEX_AUTH = '/auth/index.html';
class Util {
@@ -20,6 +21,10 @@ class Util {
return this.isExtension() && (location.pathname == INDEX_IFRAME);
};
+ isAuth () {
+ return this.isExtension() && (location.pathname == INDEX_AUTH);
+ };
+
fromPopup (url: string) {
return url.match(INDEX_POPUP);
};
@@ -57,6 +62,13 @@ class Util {
};
C.AccountSelect(message.accountId, '', 0, '', (message: any) => {
+ if (message.error.code) {
+ if (onError) {
+ onError(message.error);
+ };
+ return;
+ };
+
S.Auth.accountSet(message.account);
S.Common.configSet(message.account.config, false);
S.Common.showVaultSet(false);
diff --git a/extension/popup.tsx b/extension/popup.tsx
index 8e78a0767a..c39dd72df0 100644
--- a/extension/popup.tsx
+++ b/extension/popup.tsx
@@ -8,7 +8,6 @@ import { ListMenu } from 'Component';
import { S, U, C, J } from 'Lib';
import Index from './popup/index';
-import Challenge from './popup/challenge';
import Create from './popup/create';
import Success from './popup/success';
@@ -23,7 +22,6 @@ const Routes = [
const Components = {
index: Index,
- challenge: Challenge,
create: Create,
success: Success,
};
diff --git a/extension/popup/index.tsx b/extension/popup/index.tsx
index 56b260fa30..cf2930f017 100644
--- a/extension/popup/index.tsx
+++ b/extension/popup/index.tsx
@@ -81,6 +81,7 @@ const Index = observer(class Index extends React.Component {
if (message.error.code) {
@@ -88,8 +89,14 @@ const Index = observer(class Index extends React.Component= 8"
}
},
"node_modules/@rsdoctor/graph": {
- "version": "0.4.8",
- "resolved": "https://registry.npmjs.org/@rsdoctor/graph/-/graph-0.4.8.tgz",
- "integrity": "sha512-F6lKNjU1pewVXT8PLPgL7WuxB4Aru7unpZjJ4Hh1vbcNUsK6VNUNHsAviOu2ZrbuKuwbxqjd3p/NIjThUzX7NQ==",
+ "version": "0.4.12",
+ "resolved": "https://registry.npmjs.org/@rsdoctor/graph/-/graph-0.4.12.tgz",
+ "integrity": "sha512-xf66kkOF44wi/SWomr8qgJ5jEbX6DFxdTP+9FQNTz/LRRqYebySkU9uvVTLHmjSxDVBQ1dGx+YsZLhFpF6neWA==",
"dev": true,
- "license": "MIT",
"dependencies": {
- "@rsdoctor/types": "0.4.8",
- "@rsdoctor/utils": "0.4.8",
+ "@rsdoctor/types": "0.4.12",
+ "@rsdoctor/utils": "0.4.12",
"lodash": "^4.17.21",
- "socket.io": "4.7.2",
+ "socket.io": "4.8.1",
"source-map": "^0.7.4"
}
},
@@ -1175,23 +1171,21 @@
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
"dev": true,
- "license": "BSD-3-Clause",
"engines": {
"node": ">= 8"
}
},
"node_modules/@rsdoctor/rspack-plugin": {
- "version": "0.4.8",
- "resolved": "https://registry.npmjs.org/@rsdoctor/rspack-plugin/-/rspack-plugin-0.4.8.tgz",
- "integrity": "sha512-IIyZ/EhiU7b3n0m+1frPTBKXnmjpB6rKbumjntzTDkmY7+OEub69WA9eqSxx0nPWQxBHwBzCpIVjSjYyARRaNg==",
+ "version": "0.4.12",
+ "resolved": "https://registry.npmjs.org/@rsdoctor/rspack-plugin/-/rspack-plugin-0.4.12.tgz",
+ "integrity": "sha512-jzU+8sGzXe8TP4jy3+rtsPOTj71J1JDKCeiXqbJFelAWl05Bhfj6nw+ED/23SDb/430EEVW6nSdgEm/iDCHJIA==",
"dev": true,
- "license": "MIT",
"dependencies": {
- "@rsdoctor/core": "0.4.8",
- "@rsdoctor/graph": "0.4.8",
- "@rsdoctor/sdk": "0.4.8",
- "@rsdoctor/types": "0.4.8",
- "@rsdoctor/utils": "0.4.8",
+ "@rsdoctor/core": "0.4.12",
+ "@rsdoctor/graph": "0.4.12",
+ "@rsdoctor/sdk": "0.4.12",
+ "@rsdoctor/types": "0.4.12",
+ "@rsdoctor/utils": "0.4.12",
"lodash": "^4.17.21"
},
"peerDependencies": {
@@ -1199,25 +1193,25 @@
}
},
"node_modules/@rsdoctor/sdk": {
- "version": "0.4.8",
- "resolved": "https://registry.npmjs.org/@rsdoctor/sdk/-/sdk-0.4.8.tgz",
- "integrity": "sha512-tD14drEa9ZZm5FtKL6BfDsBbOvYbIRbF5aEpQSP5ZQsKa6YfOhHA2dpv+zN7qIUXzd16vKBGNzTT906BT7HvEw==",
+ "version": "0.4.12",
+ "resolved": "https://registry.npmjs.org/@rsdoctor/sdk/-/sdk-0.4.12.tgz",
+ "integrity": "sha512-b2ob92cYle2kXFWkxrdVen6/9vNcn8UJW8kFmD4OCfR4AgHiFLZMSWzJ5P41M1Ti1AqY3kVlp2CaEZOIof2SBw==",
"dev": true,
- "license": "MIT",
"dependencies": {
- "@rsdoctor/client": "0.4.8",
- "@rsdoctor/graph": "0.4.8",
- "@rsdoctor/types": "0.4.8",
- "@rsdoctor/utils": "0.4.8",
+ "@rsdoctor/client": "0.4.12",
+ "@rsdoctor/graph": "0.4.12",
+ "@rsdoctor/types": "0.4.12",
+ "@rsdoctor/utils": "0.4.12",
"@types/fs-extra": "^11.0.4",
"body-parser": "1.20.3",
"cors": "2.8.5",
"dayjs": "1.11.13",
"fs-extra": "^11.1.1",
+ "json-cycle": "^1.5.0",
"lodash": "^4.17.21",
"open": "^8.4.2",
"serve-static": "1.16.2",
- "socket.io": "4.7.2",
+ "socket.io": "4.8.1",
"source-map": "^0.7.4",
"tapable": "2.2.1"
}
@@ -1227,7 +1221,6 @@
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.4.tgz",
"integrity": "sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==",
"dev": true,
- "license": "MIT",
"dependencies": {
"@types/jsonfile": "*",
"@types/node": "*"
@@ -1238,7 +1231,6 @@
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
"integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">=8"
}
@@ -1248,7 +1240,6 @@
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz",
"integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==",
"dev": true,
- "license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
@@ -1263,7 +1254,6 @@
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"dev": true,
- "license": "MIT",
"dependencies": {
"universalify": "^2.0.0"
},
@@ -1276,7 +1266,6 @@
"resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz",
"integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==",
"dev": true,
- "license": "MIT",
"dependencies": {
"define-lazy-prop": "^2.0.0",
"is-docker": "^2.1.1",
@@ -1294,17 +1283,15 @@
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
"dev": true,
- "license": "BSD-3-Clause",
"engines": {
"node": ">= 8"
}
},
"node_modules/@rsdoctor/types": {
- "version": "0.4.8",
- "resolved": "https://registry.npmjs.org/@rsdoctor/types/-/types-0.4.8.tgz",
- "integrity": "sha512-yY9OyfRLuOva2GhLccjHy1jDVTfoxeGSXF030DingiorrhFXMi+TJjq98LN4p9ha/awxzHayAUcSwZ3nq1XEXQ==",
+ "version": "0.4.12",
+ "resolved": "https://registry.npmjs.org/@rsdoctor/types/-/types-0.4.12.tgz",
+ "integrity": "sha512-FqdRPbOllpG6dxSU5f6Pe5pLJGLMuw5yEnwBACb818uIFI+099xwZxkmxBqDg13ZEFrfMNQsruZv3HBQ9hYHPQ==",
"dev": true,
- "license": "MIT",
"dependencies": {
"@types/connect": "3.4.38",
"@types/estree": "1.0.5",
@@ -1325,28 +1312,25 @@
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
- "dev": true,
- "license": "MIT"
+ "dev": true
},
"node_modules/@rsdoctor/types/node_modules/source-map": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
"dev": true,
- "license": "BSD-3-Clause",
"engines": {
"node": ">= 8"
}
},
"node_modules/@rsdoctor/utils": {
- "version": "0.4.8",
- "resolved": "https://registry.npmjs.org/@rsdoctor/utils/-/utils-0.4.8.tgz",
- "integrity": "sha512-5m0dvwkdKAT5mONAf49oG1JEr+FXSThxwo36tfIHrUPNm3pnx8bDXFL3E5rNb9LK8Va7GFYe6hjmxTxyoaveFg==",
+ "version": "0.4.12",
+ "resolved": "https://registry.npmjs.org/@rsdoctor/utils/-/utils-0.4.12.tgz",
+ "integrity": "sha512-GQahkLhH65nfRvLK1auydLvbVFsLKcDZIwEf1VoL1lpWhtxSEzVJCuex8764Fs8abBlFM1Of22IOpMMQ2Pvdbw==",
"dev": true,
- "license": "MIT",
"dependencies": {
"@babel/code-frame": "7.25.7",
- "@rsdoctor/types": "0.4.8",
+ "@rsdoctor/types": "0.4.12",
"@types/estree": "1.0.5",
"acorn": "^8.10.0",
"acorn-import-assertions": "1.9.0",
@@ -1370,7 +1354,6 @@
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.25.7.tgz",
"integrity": "sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==",
"dev": true,
- "license": "MIT",
"dependencies": {
"@babel/highlight": "^7.25.7",
"picocolors": "^1.0.0"
@@ -1383,15 +1366,13 @@
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
- "dev": true,
- "license": "MIT"
+ "dev": true
},
"node_modules/@rsdoctor/utils/node_modules/fs-extra": {
"version": "11.2.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz",
"integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==",
"dev": true,
- "license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
@@ -1406,7 +1387,6 @@
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"dev": true,
- "license": "MIT",
"dependencies": {
"universalify": "^2.0.0"
},
@@ -1419,7 +1399,6 @@
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.4.tgz",
"integrity": "sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==",
"dev": true,
- "license": "MIT",
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
}
@@ -2092,8 +2071,7 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
- "dev": true,
- "license": "MIT"
+ "dev": true
},
"node_modules/@szmarczak/http-timer": {
"version": "4.0.6",
@@ -2172,15 +2150,13 @@
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
"integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==",
- "dev": true,
- "license": "MIT"
+ "dev": true
},
"node_modules/@types/cors": {
"version": "2.8.17",
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz",
"integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==",
"dev": true,
- "license": "MIT",
"dependencies": {
"@types/node": "*"
}
@@ -2358,9 +2334,9 @@
}
},
"node_modules/@types/d3-scale-chromatic": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.3.tgz",
- "integrity": "sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw=="
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz",
+ "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ=="
},
"node_modules/@types/d3-selection": {
"version": "3.0.11",
@@ -2416,15 +2392,6 @@
"@types/ms": "*"
}
},
- "node_modules/@types/dompurify": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.2.0.tgz",
- "integrity": "sha512-Fgg31wv9QbLDA0SpTOXO3MaxySc4DKGLi8sna4/Utjo4r3ZRPdCt4UQee8BWr+Q5z21yifghREPJGYaEOEIACg==",
- "deprecated": "This is a stub types definition. dompurify provides its own type definitions, so you do not need this installed.",
- "dependencies": {
- "dompurify": "*"
- }
- },
"node_modules/@types/emoji-mart": {
"version": "3.0.14",
"resolved": "https://registry.npmjs.org/@types/emoji-mart/-/emoji-mart-3.0.14.tgz",
@@ -2546,7 +2513,6 @@
"resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.4.tgz",
"integrity": "sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==",
"dev": true,
- "license": "MIT",
"dependencies": {
"@types/node": "*"
}
@@ -2781,11 +2747,16 @@
"resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-2.2.7.tgz",
"integrity": "sha512-D6QzACV9vNX3r8HQQNTOnpG+Bv1rko+yEA82wKs3O9CQ5+XW7HI7TED17/UE7+5dIxyxZIWTxKbsBeF6uKFCwA==",
"dev": true,
- "license": "MIT",
"dependencies": {
"tapable": "^2.2.0"
}
},
+ "node_modules/@types/trusted-types": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
+ "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
+ "optional": true
+ },
"node_modules/@types/verror": {
"version": "1.10.10",
"resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.10.tgz",
@@ -3252,7 +3223,6 @@
"integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==",
"deprecated": "package has been renamed to acorn-import-attributes",
"dev": true,
- "license": "MIT",
"peerDependencies": {
"acorn": "^8"
}
@@ -3342,7 +3312,6 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
"integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
- "dev": true,
"dependencies": {
"ajv": "^8.0.0"
},
@@ -3359,7 +3328,6 @@
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
- "dev": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@@ -3374,8 +3342,7 @@
"node_modules/ajv-formats/node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
- "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
- "dev": true
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
},
"node_modules/ajv-keywords": {
"version": "3.5.2",
@@ -3921,6 +3888,14 @@
"node": ">= 4.0.0"
}
},
+ "node_modules/atomically": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/atomically/-/atomically-1.7.0.tgz",
+ "integrity": "sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==",
+ "engines": {
+ "node": ">=10.12.0"
+ }
+ },
"node_modules/available-typed-arrays": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
@@ -3937,11 +3912,10 @@
}
},
"node_modules/axios": {
- "version": "1.7.7",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz",
- "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
+ "version": "1.7.9",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
+ "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
"dev": true,
- "license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
@@ -3977,7 +3951,6 @@
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
"dev": true,
- "license": "MIT",
"engines": {
"node": "^4.5.0 || >= 5.9"
}
@@ -4951,6 +4924,71 @@
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
+ "node_modules/conf": {
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/conf/-/conf-10.2.0.tgz",
+ "integrity": "sha512-8fLl9F04EJqjSqH+QjITQfJF8BrOVaYr1jewVgSRAEWePfxT0sku4w2hrGQ60BC/TNLGQ2pgxNlTbWQmMPFvXg==",
+ "dependencies": {
+ "ajv": "^8.6.3",
+ "ajv-formats": "^2.1.1",
+ "atomically": "^1.7.0",
+ "debounce-fn": "^4.0.0",
+ "dot-prop": "^6.0.1",
+ "env-paths": "^2.2.1",
+ "json-schema-typed": "^7.0.3",
+ "onetime": "^5.1.2",
+ "pkg-up": "^3.1.0",
+ "semver": "^7.3.5"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/conf/node_modules/ajv": {
+ "version": "8.17.1",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
+ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "fast-uri": "^3.0.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/conf/node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
+ },
+ "node_modules/conf/node_modules/mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/conf/node_modules/onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "dependencies": {
+ "mimic-fn": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/confbox": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz",
@@ -5015,7 +5053,6 @@
"resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz",
"integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==",
"dev": true,
- "license": "MIT",
"dependencies": {
"debug": "2.6.9",
"finalhandler": "1.1.2",
@@ -5040,7 +5077,6 @@
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dev": true,
- "license": "MIT",
"dependencies": {
"ms": "2.0.0"
}
@@ -5050,7 +5086,6 @@
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">= 0.8"
}
@@ -5060,7 +5095,6 @@
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
"integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
"dev": true,
- "license": "MIT",
"dependencies": {
"debug": "2.6.9",
"encodeurl": "~1.0.2",
@@ -5078,15 +5112,13 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
- "dev": true,
- "license": "MIT"
+ "dev": true
},
"node_modules/connect/node_modules/on-finished": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
"integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==",
"dev": true,
- "license": "MIT",
"dependencies": {
"ee-first": "1.1.1"
},
@@ -5099,7 +5131,6 @@
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
"integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">= 0.6"
}
@@ -5155,7 +5186,6 @@
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
"dev": true,
- "license": "MIT",
"dependencies": {
"object-assign": "^4",
"vary": "^1"
@@ -5890,6 +5920,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/date-fns": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
+ "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/kossnocorp"
+ }
+ },
"node_modules/dayjs": {
"version": "1.11.13",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
@@ -5899,8 +5939,29 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz",
"integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==",
- "dev": true,
- "license": "MIT"
+ "dev": true
+ },
+ "node_modules/debounce-fn": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/debounce-fn/-/debounce-fn-4.0.0.tgz",
+ "integrity": "sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==",
+ "dependencies": {
+ "mimic-fn": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/debounce-fn/node_modules/mimic-fn": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz",
+ "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==",
+ "engines": {
+ "node": ">=8"
+ }
},
"node_modules/debug": {
"version": "4.3.7",
@@ -6016,7 +6077,6 @@
"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz",
"integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==",
"dev": true,
- "license": "MIT",
"dependencies": {
"type-detect": "^4.0.0"
},
@@ -6485,6 +6545,20 @@
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.6.tgz",
"integrity": "sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ=="
},
+ "node_modules/dot-prop": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz",
+ "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==",
+ "dependencies": {
+ "is-obj": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/dotenv": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-9.0.2.tgz",
@@ -6539,11 +6613,10 @@
}
},
"node_modules/electron": {
- "version": "31.7.5",
- "resolved": "https://registry.npmjs.org/electron/-/electron-31.7.5.tgz",
- "integrity": "sha512-8zFzVJdhxTRmoPcRiKkEmPW0bJHAUsTQJwEX2YJ8X0BVFIJLwSvHkSlpCjEExVbNCAk+gHnkIYX+2OyCXrRwHQ==",
+ "version": "33.2.1",
+ "resolved": "https://registry.npmjs.org/electron/-/electron-33.2.1.tgz",
+ "integrity": "sha512-SG/nmSsK9Qg1p6wAW+ZfqU+AV8cmXMTIklUL18NnOKfZLlum4ZsDoVdmmmlL39ZmeCaq27dr7CgslRPahfoVJg==",
"hasInstallScript": true,
- "license": "MIT",
"dependencies": {
"@electron/get": "^2.0.0",
"@types/node": "^20.9.0",
@@ -6734,6 +6807,29 @@
"graceful-fs": "^4.1.6"
}
},
+ "node_modules/electron-store": {
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/electron-store/-/electron-store-8.2.0.tgz",
+ "integrity": "sha512-ukLL5Bevdil6oieAOXz3CMy+OgaItMiVBg701MNlG6W5RaC0AHN7rvlqTCmeb6O7jP0Qa1KKYTE0xV0xbhF4Hw==",
+ "dependencies": {
+ "conf": "^10.2.0",
+ "type-fest": "^2.17.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/electron-store/node_modules/type-fest": {
+ "version": "2.19.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
+ "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
+ "engines": {
+ "node": ">=12.20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/electron-to-chromium": {
"version": "1.5.43",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.43.tgz",
@@ -6867,18 +6963,17 @@
}
},
"node_modules/engine.io": {
- "version": "6.5.5",
- "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz",
- "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==",
+ "version": "6.6.2",
+ "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz",
+ "integrity": "sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==",
"dev": true,
- "license": "MIT",
"dependencies": {
"@types/cookie": "^0.4.1",
"@types/cors": "^2.8.12",
"@types/node": ">=10.0.0",
"accepts": "~1.3.4",
"base64id": "2.0.0",
- "cookie": "~0.4.1",
+ "cookie": "~0.7.2",
"cors": "~2.8.5",
"debug": "~4.3.1",
"engine.io-parser": "~5.2.1",
@@ -6893,17 +6988,15 @@
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/engine.io/node_modules/cookie": {
- "version": "0.4.2",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
- "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">= 0.6"
}
@@ -6913,7 +7006,6 @@
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">=10.0.0"
},
@@ -6935,7 +7027,6 @@
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz",
"integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==",
"dev": true,
- "license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.4",
"tapable": "^2.2.0"
@@ -6957,7 +7048,6 @@
"resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.14.0.tgz",
"integrity": "sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==",
"dev": true,
- "license": "MIT",
"bin": {
"envinfo": "dist/cli.js"
},
@@ -7531,9 +7621,9 @@
"integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw=="
},
"node_modules/express": {
- "version": "4.21.1",
- "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
- "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
+ "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
"dev": true,
"dependencies": {
"accepts": "~1.3.8",
@@ -7555,7 +7645,7 @@
"methods": "~1.1.2",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
- "path-to-regexp": "0.1.10",
+ "path-to-regexp": "0.1.12",
"proxy-addr": "~2.0.7",
"qs": "6.13.0",
"range-parser": "~1.2.1",
@@ -7570,6 +7660,10 @@
},
"engines": {
"node": ">= 0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
"node_modules/express/node_modules/debug": {
@@ -7588,9 +7682,9 @@
"dev": true
},
"node_modules/express/node_modules/path-to-regexp": {
- "version": "0.1.10",
- "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
- "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==",
+ "version": "0.1.12",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
+ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
"dev": true
},
"node_modules/ext-list": {
@@ -7648,8 +7742,7 @@
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "dev": true
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"node_modules/fast-glob": {
"version": "3.3.2",
@@ -7694,8 +7787,7 @@
"node_modules/fast-uri": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz",
- "integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==",
- "dev": true
+ "integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw=="
},
"node_modules/fastq": {
"version": "1.17.1",
@@ -7764,7 +7856,6 @@
"resolved": "https://registry.npmjs.org/filesize/-/filesize-10.1.6.tgz",
"integrity": "sha512-sJslQKU2uM33qH5nqewAwVB2QgR6w1aMNsYUp3aN5rMRyXEwJGmZvaWzeJFNTOXWlHQyBFCWrdj3fV/fsTOX8w==",
"dev": true,
- "license": "BSD-3-Clause",
"engines": {
"node": ">= 10.4.0"
}
@@ -8239,7 +8330,6 @@
"resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz",
"integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">=8"
},
@@ -8698,8 +8788,7 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
"integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
- "dev": true,
- "license": "MIT"
+ "dev": true
},
"node_modules/http-cache-semantics": {
"version": "4.1.1",
@@ -9393,6 +9482,14 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-obj": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
+ "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/is-path-inside": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
@@ -9745,6 +9842,15 @@
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="
},
+ "node_modules/json-cycle": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/json-cycle/-/json-cycle-1.5.0.tgz",
+ "integrity": "sha512-GOehvd5PO2FeZ5T4c+RxobeT5a1PiGpF4u9/3+UvrMU4bhnVqzJY7hm39wg8PDCqkU91fWGH8qjWR4bn+wgq9w==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
"node_modules/json-parse-better-errors": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
@@ -9763,6 +9869,11 @@
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
},
+ "node_modules/json-schema-typed": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-7.0.3.tgz",
+ "integrity": "sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A=="
+ },
"node_modules/json-stable-stringify-without-jsonify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
@@ -9773,8 +9884,7 @@
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-stream-stringify/-/json-stream-stringify-3.0.1.tgz",
"integrity": "sha512-vuxs3G1ocFDiAQ/SX0okcZbtqXwgj1g71qE9+vrjJ2EkjKQlEFDAcUNRxRU8O+GekV4v5cM2qXP0Wyt/EMDBiQ==",
- "dev": true,
- "license": "MIT"
+ "dev": true
},
"node_modules/json-stringify-safe": {
"version": "5.0.1",
@@ -10824,15 +10934,14 @@
}
},
"node_modules/mermaid": {
- "version": "11.4.0",
- "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.4.0.tgz",
- "integrity": "sha512-mxCfEYvADJqOiHfGpJXLs4/fAjHz448rH0pfY5fAoxiz70rQiDSzUUy4dNET2T08i46IVpjohPd6WWbzmRHiPA==",
+ "version": "11.4.1",
+ "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.4.1.tgz",
+ "integrity": "sha512-Mb01JT/x6CKDWaxigwfZYuYmDZ6xtrNwNlidKZwkSrDaY9n90tdrJTV5Umk+wP1fZscGptmKFXHsXMDEVZ+Q6A==",
"dependencies": {
"@braintree/sanitize-url": "^7.0.1",
"@iconify/utils": "^2.1.32",
"@mermaid-js/parser": "^0.3.0",
"@types/d3": "^7.4.3",
- "@types/dompurify": "^3.0.5",
"cytoscape": "^3.29.2",
"cytoscape-cose-bilkent": "^4.1.0",
"cytoscape-fcose": "^2.2.0",
@@ -10840,7 +10949,7 @@
"d3-sankey": "^0.12.3",
"dagre-d3-es": "7.0.11",
"dayjs": "^1.11.10",
- "dompurify": "^3.0.11 <3.1.7",
+ "dompurify": "^3.2.1",
"katex": "^0.16.9",
"khroma": "^2.1.0",
"lodash-es": "^4.17.21",
@@ -10851,6 +10960,14 @@
"uuid": "^9.0.1"
}
},
+ "node_modules/mermaid/node_modules/dompurify": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.2.tgz",
+ "integrity": "sha512-YMM+erhdZ2nkZ4fTNRTSI94mb7VG7uVF5vj5Zde7tImgnhZE3R6YW/IACGIHb2ux+QkEXMhe591N+5jWOmL4Zw==",
+ "optionalDependencies": {
+ "@types/trusted-types": "^2.0.7"
+ }
+ },
"node_modules/methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
@@ -11171,7 +11288,6 @@
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz",
"integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">=10"
}
@@ -15155,6 +15271,14 @@
"node": ">= 4"
}
},
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/package-json-from-dist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
@@ -15349,8 +15473,7 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
"integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==",
- "dev": true,
- "license": "MIT"
+ "dev": true
},
"node_modules/path-data-parser": {
"version": "0.1.0",
@@ -15519,6 +15642,73 @@
"pathe": "^1.1.2"
}
},
+ "node_modules/pkg-up": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz",
+ "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==",
+ "dependencies": {
+ "find-up": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pkg-up/node_modules/find-up": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+ "dependencies": {
+ "locate-path": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/pkg-up/node_modules/locate-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
+ "dependencies": {
+ "p-locate": "^3.0.0",
+ "path-exists": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/pkg-up/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/pkg-up/node_modules/p-locate": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+ "dependencies": {
+ "p-limit": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/pkg-up/node_modules/path-exists": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+ "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/plist": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz",
@@ -16467,7 +16657,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
- "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -17323,7 +17512,6 @@
"resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz",
"integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==",
"dev": true,
- "license": "MIT",
"dependencies": {
"@polka/url": "^1.0.0-next.24",
"mrmime": "^2.0.0",
@@ -17376,17 +17564,16 @@
}
},
"node_modules/socket.io": {
- "version": "4.7.2",
- "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.2.tgz",
- "integrity": "sha512-bvKVS29/I5fl2FGLNHuXlQaUH/BlzX1IN6S+NKLNZpBsPZIDH+90eQmCs2Railn4YUiww4SzUedJ6+uzwFnKLw==",
+ "version": "4.8.1",
+ "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz",
+ "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==",
"dev": true,
- "license": "MIT",
"dependencies": {
"accepts": "~1.3.4",
"base64id": "~2.0.0",
"cors": "~2.8.5",
"debug": "~4.3.2",
- "engine.io": "~6.5.2",
+ "engine.io": "~6.6.0",
"socket.io-adapter": "~2.5.2",
"socket.io-parser": "~4.2.4"
},
@@ -17399,7 +17586,6 @@
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz",
"integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==",
"dev": true,
- "license": "MIT",
"dependencies": {
"debug": "~4.3.4",
"ws": "~8.17.1"
@@ -17410,7 +17596,6 @@
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">=10.0.0"
},
@@ -17432,7 +17617,6 @@
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
"dev": true,
- "license": "MIT",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1"
@@ -18557,7 +18741,6 @@
"resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
"integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">=6"
}
@@ -18669,7 +18852,6 @@
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz",
"integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">=4"
}
@@ -19155,7 +19337,6 @@
"resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.2.tgz",
"integrity": "sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw==",
"dev": true,
- "license": "MIT",
"dependencies": {
"@discoveryjs/json-ext": "0.5.7",
"acorn": "^8.0.4",
diff --git a/package.json b/package.json
index a9f9a98755..51027d3bba 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "anytype",
- "version": "0.43.7",
+ "version": "0.44.0",
"description": "Anytype",
"main": "electron.js",
"scripts": {
@@ -68,7 +68,7 @@
"@typescript-eslint/parser": "^6.18.1",
"cross-env": "^7.0.2",
"css-loader": "^3.6.0",
- "electron": "^31.0.2",
+ "electron": "^33.2.1",
"electron-builder": "^24.13.3",
"eslint": "^8.29.0",
"eslint-plugin-react": "^7.31.11",
@@ -102,11 +102,13 @@
"d3": "^7.0.1",
"d3-force": "^3.0.0",
"d3-force-cluster": "^0.1.2",
+ "date-fns": "^4.1.0",
"diff": "^5.2.0",
"dompurify": "3.1.6",
"electron-dl": "^1.14.0",
"electron-json-storage": "^4.5.0",
"electron-log": "^5.2.0",
+ "electron-store": "^8.2.0",
"electron-updater": "^6.2.1",
"electron-util": "^0.12.3",
"electron-window-state": "^5.0.3",
@@ -124,7 +126,7 @@
"lazy-val": "^1.0.4",
"lodash": "^4.17.20",
"lodash.isequal": "^4.5.0",
- "mermaid": "^11.4.0",
+ "mermaid": "^11.4.1",
"mime-types": "^2.1.35",
"mobx": "^6.6.1",
"mobx-logger": "^0.7.1",
diff --git a/src/img/icon/chat/buttons/clear.svg b/src/img/icon/chat/buttons/clear.svg
index c58fc9f811..6cb0cc2d7f 100644
--- a/src/img/icon/chat/buttons/clear.svg
+++ b/src/img/icon/chat/buttons/clear.svg
@@ -1,4 +1,3 @@
diff --git a/src/img/icon/chat/buttons/emoji.svg b/src/img/icon/chat/buttons/emoji.svg
index 5ac953e852..9bc303e108 100644
--- a/src/img/icon/chat/buttons/emoji.svg
+++ b/src/img/icon/chat/buttons/emoji.svg
@@ -1,4 +1,4 @@
\ No newline at end of file
diff --git a/src/img/icon/chat/buttons/reaction0.svg b/src/img/icon/chat/buttons/reaction0.svg
index 366ea79d50..bdfab28308 100644
--- a/src/img/icon/chat/buttons/reaction0.svg
+++ b/src/img/icon/chat/buttons/reaction0.svg
@@ -1,6 +1,6 @@
\ No newline at end of file
diff --git a/src/img/icon/chat/buttons/reaction1.svg b/src/img/icon/chat/buttons/reaction1.svg
index 07a434de97..5734380531 100644
--- a/src/img/icon/chat/buttons/reaction1.svg
+++ b/src/img/icon/chat/buttons/reaction1.svg
@@ -1,6 +1,6 @@
+
+
\ No newline at end of file
diff --git a/src/img/icon/chat/buttons/remove.svg b/src/img/icon/chat/buttons/remove.svg
index 013eb95104..edb3bf2f8c 100644
--- a/src/img/icon/chat/buttons/remove.svg
+++ b/src/img/icon/chat/buttons/remove.svg
@@ -1,3 +1,3 @@
\ No newline at end of file
+
+
diff --git a/src/img/icon/menu/action/date0.svg b/src/img/icon/menu/action/date0.svg
new file mode 100644
index 0000000000..ff38c6cfeb
--- /dev/null
+++ b/src/img/icon/menu/action/date0.svg
@@ -0,0 +1,11 @@
+
\ No newline at end of file
diff --git a/src/img/icon/menu/action/graph0.svg b/src/img/icon/menu/action/graph0.svg
index 9fb75dcd18..735af9899d 100644
--- a/src/img/icon/menu/action/graph0.svg
+++ b/src/img/icon/menu/action/graph0.svg
@@ -1,3 +1,7 @@
-
\ No newline at end of file
+
diff --git a/src/img/icon/menu/widget/compact.svg b/src/img/icon/menu/widget/compact.svg
index e6068b67ac..9d35669576 100644
--- a/src/img/icon/menu/widget/compact.svg
+++ b/src/img/icon/menu/widget/compact.svg
@@ -1,15 +1,11 @@
-
-
- {picture ?
: ''}
-
+
+ {picture ? (
+
+
+
+ ) : ''}
);
};
renderImage () {
- const { object } = this.props;
-
- this.previewItem = { type: I.FileType.Image, object };
+ const { object, scrollToBottom } = this.props;
+ const { isLoaded } = this.state;
if (!this.src) {
if (object.isTmp && object.file) {
- U.File.loadPreviewBase64(object.file, { type: 'jpg', quality: 99, maxWidth: J.Size.image }, (image: string, param: any) => {
+ U.File.loadPreviewBase64(object.file, { type: 'jpg', quality: 99, maxWidth: J.Size.image }, (image: string) => {
this.src = image;
- this.previewItem.src = image;
$(this.node).find('#image').attr({ 'src': image });
});
this.src = './img/space.svg';
@@ -193,25 +205,29 @@ const ChatAttachment = observer(class ChatAttachment extends React.Component this.setState({ isLoaded: true });
+ img.src = this.src;
+ };
- return (
+ return isLoaded ? (
e.preventDefault()}
+ style={{ aspectRatio: `${object.widthInPixels} / ${object.heightInPixels}` }}
/>
- );
+ ) : ;
};
renderVideo () {
const { object } = this.props;
const src = S.Common.fileUrl(object.id);
- this.previewItem = { type: I.FileType.Video, src, object };
-
return (
;
};
+ componentDidUpdate (prevProps: Readonly, prevState: Readonly): void {
+ const { scrollToBottom } = this.props;
+
+ if (!prevState.isLoaded && this.state.isLoaded && scrollToBottom) {
+ scrollToBottom();
+ };
+ };
+
onOpen () {
const { object } = this.props;
- if (!object.isTmp) {
- U.Object.openPopup(object);
+ switch (object.layout) {
+ case I.ObjectLayout.Bookmark: {
+ this.onOpenBookmark();
+ break;
+ };
+
+ case I.ObjectLayout.Video:
+ case I.ObjectLayout.Image: {
+ this.onPreview();
+ break;
+ };
+
+ case I.ObjectLayout.File:
+ case I.ObjectLayout.Pdf:
+ case I.ObjectLayout.Audio: {
+ Action.openFile(object.id, analytics.route.chat);
+ break;
+ };
+
+ default: {
+ if (!object.isTmp) {
+ U.Object.openPopup(object);
+ };
+ break;
+ };
};
};
- onOpenBookmark () {
- const { object } = this.props;
- const { source } = object;
+ onContextMenu (e: any) {
+ e.stopPropagation();
+
+ const { object, subId } = this.props;
+
+ S.Menu.open('dataviewContext', {
+ recalcRect: () => {
+ const { x, y } = keyboard.mouse.page;
+ return { width: 0, height: 0, x: x + 4, y: y };
+ },
+ data: {
+ objectIds: [ object.id ],
+ subId,
+ allowedLinkTo: true,
+ allowedOpen: true,
+ }
+ });
+ };
- Action.openUrl(source);
+ onOpenBookmark () {
+ Action.openUrl(this.props.object.source);
};
onPreview () {
const { onPreview } = this.props;
-
- if (!this.previewItem) {
- return;
- };
+ const item = this.getPreviewItem();
if (onPreview) {
- onPreview(this.previewItem);
+ onPreview(item);
} else {
- S.Popup.open('preview', { data: { gallery: [ this.previewItem ] } });
+ S.Popup.open('preview', { data: { gallery: [ item ] } });
};
};
@@ -267,7 +327,24 @@ const ChatAttachment = observer(class ChatAttachment extends React.Component {
@@ -30,14 +31,18 @@ const ChatForm = observer(class ChatForm extends React.Component {
node = null;
refEditable = null;
refButtons = null;
+ refCounter = null;
+ isLoading = [];
marks: I.Mark[] = [];
range: I.TextRange = { from: 0, to: 0 };
timeoutFilter = 0;
editingId: string = '';
replyingId: string = '';
swiper = null;
+ speedLimit = { last: 0, counter: 0 }
state = {
attachments: [],
+ charCounter: 0,
};
constructor (props: Props) {
@@ -76,7 +81,7 @@ const ChatForm = observer(class ChatForm extends React.Component {
render () {
const { rootId, readonly, getReplyContent } = this.props;
- const { attachments } = this.state;
+ const { attachments, charCounter } = this.state;
const { space } = S.Common;
const value = this.getTextValue();
@@ -115,7 +120,7 @@ const ChatForm = observer(class ChatForm extends React.Component {
icon = ;
};
- if (reply.isMultiple) {
+ if (reply.isMultiple && !reply.attachment) {
icon = ;
};
onClear = this.onReplyClear;
@@ -149,6 +154,7 @@ const ChatForm = observer(class ChatForm extends React.Component {
this.refEditable = ref}
id="messageBox"
+ classNameWrap="customScrollbar"
maxLength={J.Constant.limit.chat.text}
placeholder={translate('blockChatPlaceholder')}
onSelect={this.onSelect}
@@ -201,6 +207,8 @@ const ChatForm = observer(class ChatForm extends React.Component {
removeBookmark={this.removeBookmark}
/>
+ this.refCounter = ref} className="charCounter">{charCounter} / {J.Constant.limit.chat.text}
+
@@ -226,6 +234,7 @@ const ChatForm = observer(class ChatForm extends React.Component {
this.marks = marks;
this.updateMarkup(text, length, length);
+ this.updateCounter(text);
if (attachments.length) {
this.setAttachments(attachments);
@@ -245,17 +254,7 @@ const ChatForm = observer(class ChatForm extends React.Component {
componentWillUnmount () {
this._isMounted = false;
window.clearTimeout(this.timeoutFilter);
-
- const { rootId } = this.props;
- const { attachments } = this.state;
-
keyboard.disableSelection(false);
-
- Storage.setChat(rootId, {
- text: this.getTextValue(),
- marks: this.marks,
- attachments,
- });
};
checkSendButton () {
@@ -285,8 +284,17 @@ const ChatForm = observer(class ChatForm extends React.Component {
};
onBlurInput () {
+ const { rootId } = this.props;
+ const { attachments } = this.state;
+
keyboard.disableSelection(false);
this.refEditable?.placeholderCheck();
+
+ Storage.setChat(rootId, {
+ text: this.getTextValue(),
+ marks: this.marks,
+ attachments,
+ });
};
onKeyDownInput (e: any) {
@@ -370,7 +378,7 @@ const ChatForm = observer(class ChatForm extends React.Component {
};
onKeyUpInput (e: any) {
- this.range = this.refEditable.getRange();
+ this.range = this.refEditable.getRange() || { from: 0, to: 0 };
const { attachments } = this.state;
const { to } = this.range;
@@ -442,6 +450,7 @@ const ChatForm = observer(class ChatForm extends React.Component {
this.checkSendButton();
this.updateButtons();
this.removeBookmarks();
+ this.updateCounter();
};
onInput () {
@@ -455,25 +464,39 @@ const ChatForm = observer(class ChatForm extends React.Component {
e.preventDefault();
const { from, to } = this.range;
+ const limit = J.Constant.limit.chat.text;
+ const current = this.getTextValue();
const cb = e.clipboardData || e.originalEvent.clipboardData;
- const text = U.Common.normalizeLineEndings(String(cb.getData('text/plain') || ''));
const electron = U.Common.getElectron();
const list = U.Common.getDataTransferFiles((e.clipboardData || e.originalEvent.clipboardData).items).map((it: File) => this.getObjectFromFile(it)).filter(it => {
return !electron.isDirectory(it.path);
});
- const value = U.Common.stringInsert(this.getTextValue(), text, from, to);
+
+ let text = U.Common.normalizeLineEndings(String(cb.getData('text/plain') || ''));
+ let value = U.Common.stringInsert(current, text, from, to);
+ if (value.length >= limit) {
+ const excess = value.length - limit;
+ const keep = text.length - excess;
+
+ text = text.substring(0, keep);
+ value = U.Common.stringInsert(current, text, from, to);
+ };
this.range = { from: to, to: to + text.length };
- this.refEditable.setValue(value);
+ this.refEditable.setValue(Mark.toHtml(value, this.marks));
this.refEditable.setRange(this.range);
this.refEditable.placeholderCheck();
+ this.renderMarkup();
if (list.length) {
- this.addAttachments(list);
+ U.Common.saveClipboardFiles(list, {}, data => {
+ this.addAttachments(data.files);
+ });
};
this.checkUrls();
this.onInput();
+ this.updateCounter();
};
checkUrls () {
@@ -485,17 +508,16 @@ const ChatForm = observer(class ChatForm extends React.Component {
this.removeBookmarks();
- window.setTimeout(() => {
- for (const url of urls) {
- const { from, to, isLocal, value } = url;
- const param = isLocal ? `file://${value}` : value;
+ for (const url of urls) {
+ const { from, to, isLocal, value } = url;
+ const param = isLocal ? `file://${value}` : value;
- this.marks = Mark.adjust(this.marks, from - 1, value.length + 1);
- this.marks.push({ type: I.MarkType.Link, range: { from, to }, param});
- this.addBookmark(param, true);
- };
- this.updateMarkup(text, this.range.to + 1, this.range.to + 1);
- }, 150);
+ this.marks = Mark.adjust(this.marks, from - 1, value.length + 1);
+ this.marks.push({ type: I.MarkType.Link, range: { from, to }, param});
+ this.addBookmark(param, true);
+ };
+
+ this.updateMarkup(text, this.range.to + 1, this.range.to + 1);
};
canDrop (e: any): boolean {
@@ -568,7 +590,11 @@ const ChatForm = observer(class ChatForm extends React.Component {
this.addAttachments([ item ]);
};
+ this.isLoading.push(url);
+
C.LinkPreview(url, (message: any) => {
+ this.isLoading = this.isLoading.filter(it => it != url);
+
if (message.error.code) {
add({ title: url, url });
} else {
@@ -618,6 +644,8 @@ const ChatForm = observer(class ChatForm extends React.Component {
const clear = () => {
this.onEditClear();
this.onReplyClear();
+ this.clearCounter();
+ this.checkSpeedLimit();
loader.removeClass('active');
};
@@ -716,6 +744,7 @@ const ChatForm = observer(class ChatForm extends React.Component {
this.editingId = message.id;
this.replyingId = '';
this.updateMarkup(text, l, l);
+ this.updateCounter();
this.setAttachments(attachments, () => {
this.refEditable.setRange(this.range);
@@ -727,6 +756,7 @@ const ChatForm = observer(class ChatForm extends React.Component {
this.marks = [];
this.updateMarkup('', 0, 0);
this.setState({ attachments: [] }, () => this.refEditable.setRange(this.range));
+ this.clearCounter();
this.refButtons.setButtons();
};
@@ -743,6 +773,7 @@ const ChatForm = observer(class ChatForm extends React.Component {
onReplyClear () {
this.replyingId = '';
this.forceUpdate();
+ this.props.scrollToBottom();
};
onDelete (id: string) {
@@ -977,8 +1008,8 @@ const ChatForm = observer(class ChatForm extends React.Component {
};
};
- canSend () {
- return this.editingId || this.getTextValue() || this.state.attachments.length || this.marks.length;
+ canSend (): boolean {
+ return !this.isLoading.length && Boolean(this.editingId || this.getTextValue() || this.state.attachments.length || this.marks.length);
};
hasSelection (): boolean {
@@ -992,6 +1023,13 @@ const ChatForm = observer(class ChatForm extends React.Component {
if (list.length > limit[type]) {
list = list.slice(0, limit[type]);
+
+ if (type == 'attachments') {
+ Preview.toastShow({
+ icon: 'notice',
+ text: U.Common.sprintf(translate('toastChatAttachmentsLimitReached'), limit[type], U.Common.plural(limit[type], translate('pluralFile')).toLowerCase())
+ });
+ };
};
return list;
@@ -1000,6 +1038,7 @@ const ChatForm = observer(class ChatForm extends React.Component {
updateMarkup (value: string, from: number, to: number) {
this.range = { from, to };
this.refEditable.setValue(Mark.toHtml(value, this.marks));
+
this.refEditable.setRange({ from, to });
this.refEditable.placeholderCheck();
this.renderMarkup();
@@ -1008,7 +1047,7 @@ const ChatForm = observer(class ChatForm extends React.Component {
renderMarkup () {
const { rootId, renderLinks, renderMentions, renderObjects, renderEmoji } = this.props;
- const node = this.refEditable.node;
+ const node = this.refEditable.getNode();
const value = this.refEditable.getTextValue();
renderMentions(rootId, node, this.marks, () => value);
@@ -1017,6 +1056,56 @@ const ChatForm = observer(class ChatForm extends React.Component {
renderEmoji(node);
};
+ updateCounter (v?: string) {
+ const value = v || this.getTextValue();
+ const l = value.length;
+
+ this.setState({ charCounter: l });
+ $(this.refCounter).toggleClass('show', l >= J.Constant.limit.chat.text - 50);
+ };
+
+ clearCounter () {
+ this.setState({ charCounter: 0 });
+ $(this.refCounter).removeClass('show');
+ };
+
+ checkSpeedLimit () {
+ const { last, counter } = this.speedLimit;
+ const now = U.Date.now();
+
+ if (now - last >= 5 ) {
+ this.speedLimit = {
+ last: now,
+ counter: 1
+ };
+ return;
+ };
+
+ this.speedLimit = {
+ last: now,
+ counter: counter + 1,
+ };
+
+ if (counter >= 5) {
+ this.speedLimit = {
+ last: now,
+ counter: 1
+ };
+
+ S.Popup.open('confirm', {
+ data: {
+ icon: 'warningInverted',
+ bgColor: 'red',
+ title: translate('popupConfirmSpeedLimitTitle'),
+ text: translate('popupConfirmSpeedLimitText'),
+ textConfirm: translate('commonOkay'),
+ colorConfirm: 'blank',
+ canCancel: false,
+ }
+ });
+ };
+ };
+
});
export default ChatForm;
diff --git a/src/ts/component/block/chat/message/index.tsx b/src/ts/component/block/chat/message/index.tsx
index 177e842a92..29dcc98923 100644
--- a/src/ts/component/block/chat/message/index.tsx
+++ b/src/ts/component/block/chat/message/index.tsx
@@ -27,7 +27,7 @@ const ChatMessage = observer(class ChatMessage extends React.Component
- U.Object.openConfig(author)} />
+ U.Object.openConfig(author)}
+ />
-
U.Object.openConfig(author)}>
{U.Date.date('H:i', createdAt)}
@@ -151,6 +154,8 @@ const ChatMessage = observer(class ChatMessage extends React.Component
this.attachmentRefs[item.id] = ref}
key={i}
object={item}
+ subId={subId}
+ scrollToBottom={scrollToBottom}
onRemove={() => this.onAttachmentRemove(item.id)}
onPreview={(preview) => this.onPreview(preview)}
showAsFile={!attachmentsLayout}
@@ -170,10 +175,6 @@ const ChatMessage = observer(class ChatMessage extends React.Component
) : ''}
-
- onThread(id)}>
- {!isThread ?
0 replies
: ''}
-
{!readonly ? (
@@ -253,14 +254,22 @@ const ChatMessage = observer(class ChatMessage extends React.Component
node.addClass('hover'),
- onClose: () => node.removeClass('hover'),
+ onOpen: () => {
+ node.addClass('hover');
+ container.addClass('over');
+ },
+ onClose: () => {
+ node.removeClass('hover');
+ container.removeClass('over');
+ },
data: {
noHead: true,
noUpload: true,
diff --git a/src/ts/component/block/dataview.tsx b/src/ts/component/block/dataview.tsx
index 993b6a25c3..ec5253f6a9 100644
--- a/src/ts/component/block/dataview.tsx
+++ b/src/ts/component/block/dataview.tsx
@@ -632,6 +632,8 @@ const BlockDataview = observer(class BlockDataview extends React.Component {
- refGraph.setRootId(object.id);
+ $(window).trigger('updateGraphRoot', { id: object.id });
});
};
};
- if ([ I.ViewType.Calendar ].includes(view.type)) {
+ if (isViewGraph || isViewCalendar) {
U.Object.openConfig(object);
} else {
if (U.Object.isNoteLayout(object.layout)) {
@@ -1437,9 +1441,7 @@ const BlockDataview = observer(class BlockDataview extends React.Component {
const canEdit = this.canCellEdit(relation, record);
if (!canEdit) {
- if (Relation.isUrl(relation.format) && value) {
- Action.openUrl(Relation.checkUrlScheme(relation.format, value));
- return;
+ if (value) {
+ if (Relation.isUrl(relation.format)) {
+ Action.openUrl(Relation.checkUrlScheme(relation.format, value));
+ return;
+ };
+
+ if (Relation.isDate(relation.format)) {
+ U.Object.openDateByTimestamp(relation.relationKey, value, 'config');
+ return;
+ };
};
if (relation.format == I.RelationType.Checkbox) {
@@ -275,6 +282,7 @@ const Cell = observer(class Cell extends React.Component {
blockId: block.id,
value,
relation: observable.box(relation),
+ relationKey: relation.relationKey,
record,
placeholder,
canEdit,
@@ -292,6 +300,7 @@ const Cell = observer(class Cell extends React.Component {
case I.RelationType.Date: {
param.data = Object.assign(param.data, {
value: param.data.value || U.Date.now(),
+ noKeyboard: true,
});
menuId = 'dataviewCalendar';
diff --git a/src/ts/component/block/dataview/controls.tsx b/src/ts/component/block/dataview/controls.tsx
index 88330feaab..042c8c269f 100644
--- a/src/ts/component/block/dataview/controls.tsx
+++ b/src/ts/component/block/dataview/controls.tsx
@@ -121,10 +121,9 @@ const Controls = observer(class Controls extends React.Component {
id="dataviewControls"
className={cn.join(' ')}
>
+ {head}
- {head}
-
{
let add = false;
- if (sideLeft.hasClass('small')) {
- sideLeft.removeClass('small');
+ if (node.hasClass('small')) {
+ node.removeClass('small');
};
const width = sideLeft.outerWidth() + sideRight.outerWidth();
@@ -571,7 +570,7 @@ const Controls = observer(class Controls extends React.Component
{
};
if (add) {
- sideLeft.addClass('small');
+ node.addClass('small');
} else {
S.Menu.closeAll([ 'dataviewViewList' ]);
};
diff --git a/src/ts/component/block/dataview/view/board.tsx b/src/ts/component/block/dataview/view/board.tsx
index 7c07154d36..39465b4b8f 100644
--- a/src/ts/component/block/dataview/view/board.tsx
+++ b/src/ts/component/block/dataview/view/board.tsx
@@ -252,6 +252,13 @@ const ViewBoard = observer(class ViewBoard extends React.Component {
+ C.ObjectCreate(details, flags, templateId, type?.uniqueKey, S.Common.space, (message: any) => {
if (message.error.code) {
return;
};
diff --git a/src/ts/component/block/dataview/view/calendar/item.tsx b/src/ts/component/block/dataview/view/calendar/item.tsx
index e42b334c00..0e5f6bf742 100644
--- a/src/ts/component/block/dataview/view/calendar/item.tsx
+++ b/src/ts/component/block/dataview/view/calendar/item.tsx
@@ -174,9 +174,10 @@ const Item = observer(class Item extends React.Component {
};
onOpenDate () {
- const { d, m, y } = this.props;
+ const { d, m, y, getView } = this.props;
+ const view = getView();
- U.Object.openDateByTimestamp(U.Date.timestamp(y, m, d, 12, 0, 0), 'config');
+ U.Object.openDateByTimestamp(view.groupRelationKey, U.Date.timestamp(y, m, d, 12, 0, 0), 'config');
};
canCreate (): boolean {
diff --git a/src/ts/component/block/dataview/view/grid.tsx b/src/ts/component/block/dataview/view/grid.tsx
index 68b1a43607..07586e12ee 100644
--- a/src/ts/component/block/dataview/view/grid.tsx
+++ b/src/ts/component/block/dataview/view/grid.tsx
@@ -278,6 +278,7 @@ const ViewGrid = observer(class ViewGrid extends React.Component {
node = null;
menuContext = null;
- refSelect = null;
state = {
- isEditing: false,
result: null,
};
constructor (props: Props) {
super(props);
- this.onClick = this.onClick.bind(this);
this.onOpen = this.onOpen.bind(this);
- this.onClose = this.onClose.bind(this);
this.onOver = this.onOver.bind(this);
this.onChange = this.onChange.bind(this);
this.onMouseEnter = this.onMouseEnter.bind(this);
this.onMouseLeave = this.onMouseLeave.bind(this);
+ this.onSelect = this.onSelect.bind(this);
};
render () {
- const { relationKey, rootId, block, getView } = this.props;
- const { isEditing, result } = this.state;
+ const { relationKey, rootId, block } = this.props;
+ const { result } = this.state;
const relation = S.Record.getRelationByKey(relationKey);
- const view = getView();
- if (!relation || !view) {
+ if (!relation) {
return ;
};
- // Subscriptions
- const viewRelation = view.getRelation(relationKey);
- if (!viewRelation || (viewRelation.formulaType == I.FormulaType.None)) {
- return ;
- };
-
- const cn = [ 'cellFoot', `cell-key-${relationKey}` ];
- const sections = U.Menu.getFormulaSections(relationKey);
- const option = Relation.formulaByType(relation.format).find(it => it.id == String(viewRelation.formulaType));
+ const cn = [ 'cellFoot' ];
+ const formulaType = this.getFormulaType();
+ const option: any = this.getOption() || {};
+ const name = option.short || option.name || '';
const subId = S.Record.getSubId(rootId, block.id);
const records = S.Record.getRecords(subId, [ relationKey ], true);
@@ -67,36 +56,15 @@ const FootCell = observer(class FootCell extends React.Component {
ref={ref => this.node = ref}
id={Relation.cellId('foot', relationKey, '')}
className={cn.join(' ')}
- onClick={this.onClick}
onMouseEnter={this.onMouseEnter}
onMouseLeave={this.onMouseLeave}
>
-
+
- {isEditing || (result === null) ? (
-
@@ -116,7 +84,15 @@ const FootCell = observer(class FootCell extends React.Component
{
calculate () {
const { rootId, block, relationKey, getView, isInline } = this.props;
const view = getView();
+ if (!view) {
+ return;
+ };
+
const viewRelation = view.getRelation(relationKey);
+ if (!viewRelation) {
+ return;
+ };
+
const subId = isInline ? [ rootId, block.id, 'total' ].join('-') : S.Record.getSubId(rootId, block.id);
const result = Dataview.getFormulaResult(subId, viewRelation);
@@ -126,14 +102,53 @@ const FootCell = observer(class FootCell extends React.Component {
};
};
- setEditing (v: boolean): void {
- this.setState({ isEditing: v });
+ getOption (): any {
+ const { relationKey, getView } = this.props;
+ const view = getView();
+
+ if (!view) {
+ return null;
+ };
+
+ const formulaType = this.getFormulaType();
+ const relation = S.Record.getRelationByKey(relationKey);
+
+ if (!relation) {
+ return null;
+ };
+
+ return Relation.formulaByType(relationKey, relation.format).find(it => it.id == String(formulaType));
};
- onClick (e: any) {
- this.setState({ isEditing: true }, () => {
- window.setTimeout(() => this.refSelect.show(e), 10);
+ onSelect (e: any) {
+ const { relationKey, getView } = this.props;
+ const id = Relation.cellId('foot', relationKey, '');
+ const options = U.Menu.getFormulaSections(relationKey);
+ const formulaType = this.getFormulaType();
+
+ if (formulaType == I.FormulaType.None) {
+ return;
+ };
+
+ S.Menu.closeAll([], () => {
+ S.Menu.open('select', {
+ element: `#${id}`,
+ horizontal: I.MenuDirection.Center,
+ onOpen: this.onOpen,
+ subIds: [ 'select2' ],
+ data: {
+ options: U.Menu.prepareForSelect(options),
+ noScroll: true,
+ noVirtualisation: true,
+ onOver: this.onOver,
+ onSelect: (e: any, item: any) => {
+ this.onChange(item.id);
+ },
+ }
+ });
});
+
+ Preview.tooltipHide();
};
onOpen (context: any): void {
@@ -147,10 +162,6 @@ const FootCell = observer(class FootCell extends React.Component {
analytics.event('ClickGridFormula', { format: relation.format, objectType: object.type });
};
- onClose () {
- $(`.cellKeyHover`).removeClass('cellKeyHover');
- };
-
onOver (e: any, item: any) {
if (!this.menuContext) {
return;
@@ -163,7 +174,7 @@ const FootCell = observer(class FootCell extends React.Component {
const { rootId, relationKey } = this.props;
const relation = S.Record.getRelationByKey(relationKey);
- const options = Relation.formulaByType(relation.format).filter(it => it.section == item.id);
+ const options = Relation.formulaByType(relationKey, relation.format).filter(it => it.section == item.id);
S.Menu.closeAll([ 'select2' ], () => {
S.Menu.open('select2', {
@@ -179,7 +190,6 @@ const FootCell = observer(class FootCell extends React.Component {
onSelect: (e: any, item: any) => {
this.onChange(item.id);
this.menuContext.close();
- this.setEditing(false);
},
}
});
@@ -202,15 +212,49 @@ const FootCell = observer(class FootCell extends React.Component {
};
onMouseEnter (): void {
- const { block, relationKey } = this.props;
+ if (keyboard.isDragging) {
+ return;
+ };
+
+ const formulaType = this.getFormulaType();
+
+ if (formulaType == I.FormulaType.None) {
+ return;
+ };
- if (!keyboard.isDragging) {
- $(`#block-${block.id} .cell-key-${relationKey}`).addClass('cellKeyHover');
+ const node = $(this.node);
+
+ node.addClass('hover');
+
+ const { result } = this.state;
+ if ((result === null) || S.Menu.isOpen()) {
+ return;
+ };
+
+ const option: any = this.getOption() || {};
+ const name = option.short || option.name || '';
+
+ const t = Preview.tooltipCaption(name, result);
+ if (t) {
+ Preview.tooltipShow({ text: t, element: node, typeY: I.MenuDirection.Top });
};
};
onMouseLeave () {
- $('.cellKeyHover').removeClass('cellKeyHover');
+ $(this.node).removeClass('hover');
+ Preview.tooltipHide();
+ };
+
+ getFormulaType (): I.FormulaType {
+ const { relationKey, getView } = this.props;
+ const view = getView();
+
+ if (!view) {
+ return I.FormulaType.None;
+ };
+
+ const viewRelation = view.getRelation(relationKey);
+ return viewRelation ? viewRelation.formulaType : I.FormulaType.None;
};
});
diff --git a/src/ts/component/block/dataview/view/grid/head/cell.tsx b/src/ts/component/block/dataview/view/grid/head/cell.tsx
index dc8cd8a1aa..077c68342e 100644
--- a/src/ts/component/block/dataview/view/grid/head/cell.tsx
+++ b/src/ts/component/block/dataview/view/grid/head/cell.tsx
@@ -58,13 +58,15 @@ const HeadCell = observer(class HeadCell extends React.Component {
onMouseEnter (): void {
const { block, relationKey } = this.props;
- if (!keyboard.isDragging) {
+ if (!keyboard.isDragging && !keyboard.isResizing) {
$(`#block-${block.id} .cell-key-${relationKey}`).addClass('cellKeyHover');
};
};
onMouseLeave () {
- $('.cellKeyHover').removeClass('cellKeyHover');
+ if (!keyboard.isDragging && !keyboard.isResizing) {
+ $('.cellKeyHover').removeClass('cellKeyHover');
+ };
};
onEdit (e: any) {
diff --git a/src/ts/component/block/div.tsx b/src/ts/component/block/div.tsx
index f6043a7dce..de3ada1873 100644
--- a/src/ts/component/block/div.tsx
+++ b/src/ts/component/block/div.tsx
@@ -1,80 +1,59 @@
-import * as React from 'react';
+import React, { forwardRef, KeyboardEvent } from 'react';
import { I, focus } from 'Lib';
import { observer } from 'mobx-react';
-const BlockDiv = observer(class BlockDiv extends React.Component {
+const BlockDiv = observer(forwardRef<{}, I.BlockComponent>((props, ref) => {
- _isMounted = false;
+ const { block, onKeyDown, onKeyUp } = props;
+ const { id, content } = block;
+ const { style } = content;
+ const cn = [ 'wrap', 'focusable', `c${id}` ];
- constructor (props: I.BlockComponent) {
- super(props);
-
- this.onKeyDown = this.onKeyDown.bind(this);
- this.onKeyUp = this.onKeyUp.bind(this);
- this.onFocus = this.onFocus.bind(this);
- };
-
- render () {
- const { block } = this.props;
- const { id, content } = block;
- const { style } = content;
-
- const cn = [ 'wrap', 'focusable', 'c' + id ];
- let inner: any = null;
-
- switch (content.style) {
- case I.DivStyle.Line:
- inner = (
-
- );
- break;
-
- case I.DivStyle.Dot:
- inner = (
-
- );
- break;
+ const onKeyDownHandler = (e: KeyboardEvent) => {
+ if (onKeyDown) {
+ onKeyDown(e, '', [], { from: 0, to: 0 }, props);
};
-
- return (
-
- {inner}
-
- );
};
- componentDidMount () {
- this._isMounted = true;
+ const onKeyUpHandler = (e: KeyboardEvent) => {
+ if (onKeyUp) {
+ onKeyUp(e, '', [], { from: 0, to: 0 }, props);
+ };
};
-
- componentWillUnmount () {
- this._isMounted = false;
+
+ const onFocus = () => {
+ focus.set(block.id, { from: 0, to: 0 });
};
-
- onKeyDown (e: any) {
- const { onKeyDown } = this.props;
- if (onKeyDown) {
- onKeyDown(e, '', [], { from: 0, to: 0 }, this.props);
+ let inner: any = null;
+ switch (style) {
+ case I.DivStyle.Line: {
+ inner = ;
+ break;
};
- };
-
- onKeyUp (e: any) {
- const { onKeyUp } = this.props;
- if (onKeyUp) {
- onKeyUp(e, '', [], { from: 0, to: 0 }, this.props);
+ case I.DivStyle.Dot: {
+ inner = (
+
+ {Array(3).fill(null).map((_, i) =>
)}
+
+ );
+ break;
};
};
- onFocus () {
- focus.set(this.props.block.id, { from: 0, to: 0 });
- };
-
-});
+ return (
+
+ {inner}
+
+ );
+
+}));
export default BlockDiv;
\ No newline at end of file
diff --git a/src/ts/component/block/featured.tsx b/src/ts/component/block/featured.tsx
index 3f9a3309f7..c3ff1d04c7 100644
--- a/src/ts/component/block/featured.tsx
+++ b/src/ts/component/block/featured.tsx
@@ -83,10 +83,7 @@ const BlockFeatured = observer(class BlockFeatured extends React.Component {
- e.persist();
- this.onRelation(e, relationKey);
- }}
+ onClick={e => this.onRelation(e, relationKey)}
>
this.cellRefs.set(id, ref)}
@@ -648,6 +645,7 @@ const BlockFeatured = observer(class BlockFeatured extends React.Component {
+const ContentIcon = forwardRef(({ icon = '' }, ref) => {
- render () {
- const { icon } = this.props;
- return ;
- };
-
-};
+ return ;
+
+});
export default ContentIcon;
\ No newline at end of file
diff --git a/src/ts/component/block/help/index.tsx b/src/ts/component/block/help/index.tsx
index a2bdd5c389..5da288078c 100644
--- a/src/ts/component/block/help/index.tsx
+++ b/src/ts/component/block/help/index.tsx
@@ -1,4 +1,4 @@
-import * as React from 'react';
+import React, { forwardRef } from 'react';
import { I, U } from 'Lib';
import ContentIcon from './icon';
import ContentText from './text';
@@ -11,74 +11,70 @@ interface Props {
align?: I.BlockHAlign;
};
-class Block extends React.Component {
+const Block = forwardRef((props, ref) => {
- public static defaultProps = {
- type: I.BlockType.Text,
- style: I.TextStyle.Paragraph,
- align: I.BlockHAlign.Left,
- };
-
- render () {
- const { type, style, align } = this.props;
- const cn = [ 'block', U.Data.blockClass({ type: type, content: { style: style } }), 'align' + align ];
+ const {
+ type = I.BlockType.Text,
+ style = I.TextStyle.Paragraph,
+ align = I.BlockHAlign.Left,
+ } = props;
+ const cn = [ 'block', U.Data.blockClass({ type: type, content: { style: style } }), `align${align}` ];
- let content = null;
+ let content = null;
- switch (type) {
- case I.BlockType.IconPage: {
- content = ;
- break;
- };
-
- case I.BlockType.Text: {
- content = ;
- break;
- };
-
- case I.BlockType.Link: {
- content = ;
- break;
- };
+ switch (type) {
+ case I.BlockType.IconPage: {
+ content = ;
+ break;
+ };
+
+ case I.BlockType.Text: {
+ content = ;
+ break;
+ };
+
+ case I.BlockType.Link: {
+ content = ;
+ break;
+ };
- case I.BlockType.Div: {
- let inner: any = null;
- switch (style) {
- case I.DivStyle.Line:
- inner = (
-
- );
- break;
+ case I.BlockType.Div: {
+ let inner: any = null;
+ switch (style) {
+ case I.DivStyle.Line:
+ inner = (
+
+ );
+ break;
- case I.DivStyle.Dot:
- inner = (
-
- );
- break;
- };
-
- content = {inner} ;
- break;
+ case I.DivStyle.Dot:
+ inner = (
+
+ );
+ break;
};
+
+ content = {inner} ;
+ break;
};
-
- return (
-
-
-
-
- {content}
-
+ };
+
+ return (
+
+ );
+
+});
export default Block;
\ No newline at end of file
diff --git a/src/ts/component/block/help/link.tsx b/src/ts/component/block/help/link.tsx
index 3141cf5927..246016e80f 100644
--- a/src/ts/component/block/help/link.tsx
+++ b/src/ts/component/block/help/link.tsx
@@ -1,4 +1,4 @@
-import * as React from 'react';
+import React, { forwardRef } from 'react';
import { IconObject } from 'Component';
import { U } from 'Lib';
@@ -8,29 +8,17 @@ interface Props {
contentId?: string;
};
-class ContentLink extends React.Component {
+const ContentLink = forwardRef(({ icon = '', name = '', contentId = '' }, ref) => {
- constructor (props: Props) {
- super(props);
-
- this.onClick = this.onClick.bind(this);
- };
+ return (
+ <>
+
+ U.Router.go(`/help/${contentId}`, {})}>
+ {name}
+
+ >
+ );
- render () {
- const { icon, name } = this.props;
-
- return (
-
-
- {name}
-
- );
- };
-
- onClick (e: any) {
- U.Router.go(`/help/${this.props.contentId}`, {});
- };
-
-};
+});
export default ContentLink;
\ No newline at end of file
diff --git a/src/ts/component/block/help/text.tsx b/src/ts/component/block/help/text.tsx
index 3dc1579655..978ba203ec 100644
--- a/src/ts/component/block/help/text.tsx
+++ b/src/ts/component/block/help/text.tsx
@@ -1,4 +1,4 @@
-import * as React from 'react';
+import React, { forwardRef } from 'react';
import { Marker, IconObject } from 'Component';
import { I, U } from 'Lib';
@@ -10,62 +10,59 @@ interface Props {
icon?: string;
};
-class ContentText extends React.Component {
+const ContentText = forwardRef(({
+ text = ' ',
+ style = I.TextStyle.Paragraph,
+ checked = false,
+ color = 'default',
+ icon = '',
+}, ref) => {
- public static defaultProps = {
- text: ' ',
- color: 'default',
- };
-
- render () {
- const { text, style, checked, color, icon } = this.props;
-
- let marker = null;
- let additional = null;
+ let marker = null;
+ let additional = null;
- switch (style) {
- case I.TextStyle.Quote: {
- additional = ;
- break;
- };
+ switch (style) {
+ case I.TextStyle.Quote: {
+ additional = ;
+ break;
+ };
- case I.TextStyle.Callout: {
- additional = ;
- break;
- };
-
- case I.TextStyle.Bulleted: {
- marker = { type: I.MarkerType.Bulleted, className: 'bullet', active: false };
- break;
- };
-
- case I.TextStyle.Numbered: {
- marker = { type: I.MarkerType.Numbered, className: 'number', active: false };
- break;
- };
-
- case I.TextStyle.Toggle: {
- marker = { type: I.MarkerType.Toggle, className: 'toggle', active: false };
- break;
- };
-
- case I.TextStyle.Checkbox: {
- marker = { type: I.MarkerType.Checkbox, className: 'check', active: checked };
- break;
- };
+ case I.TextStyle.Callout: {
+ additional = ;
+ break;
+ };
+
+ case I.TextStyle.Bulleted: {
+ marker = { type: I.MarkerType.Bulleted, className: 'bullet', active: false };
+ break;
+ };
+
+ case I.TextStyle.Numbered: {
+ marker = { type: I.MarkerType.Numbered, className: 'number', active: false };
+ break;
+ };
+
+ case I.TextStyle.Toggle: {
+ marker = { type: I.MarkerType.Toggle, className: 'toggle', active: false };
+ break;
+ };
+
+ case I.TextStyle.Checkbox: {
+ marker = { type: I.MarkerType.Checkbox, className: 'check', active: checked };
+ break;
};
-
- return (
-
-
- {marker ? : ''}
-
- {additional}
-
-
- );
};
-};
+ return (
+
+
+ {marker ? : ''}
+
+ {additional}
+
+
+ );
+
+});
export default ContentText;
\ No newline at end of file
diff --git a/src/ts/component/block/iconPage.tsx b/src/ts/component/block/iconPage.tsx
index 73209eff7d..f4574f48c1 100644
--- a/src/ts/component/block/iconPage.tsx
+++ b/src/ts/component/block/iconPage.tsx
@@ -1,23 +1,22 @@
-import * as React from 'react';
+import React, { forwardRef } from 'react';
import { observer } from 'mobx-react';
import { IconObject } from 'Component';
import { I, S } from 'Lib';
-const BlockIconPage = observer(class BlockIconPage extends React.Component {
-
- render (): any {
- const { rootId, readonly } = this.props;
+const BlockIconPage = observer(forwardRef<{}, I.BlockComponent>(({
+ rootId = '',
+ readonly = false,
+}, ref) => {
- return (
- S.Detail.get(rootId, rootId, [])}
- size={96}
- />
- );
- };
+ return (
+ S.Detail.get(rootId, rootId, [])}
+ size={96}
+ />
+ );
-});
+}));
export default BlockIconPage;
\ No newline at end of file
diff --git a/src/ts/component/block/iconUser.tsx b/src/ts/component/block/iconUser.tsx
index 4afca07976..13c49b0190 100644
--- a/src/ts/component/block/iconUser.tsx
+++ b/src/ts/component/block/iconUser.tsx
@@ -1,59 +1,47 @@
-import * as React from 'react';
+import React, { forwardRef, useState, useImperativeHandle } from 'react';
import { observer } from 'mobx-react';
import { IconObject, Loader } from 'Component';
import { I, S, U } from 'Lib';
-interface State {
- isLoading: boolean;
+interface BlockIconUserRefProps {
+ setLoading: (v: boolean) => void;
};
-const BlockIconUser = observer(class BlockIconUser extends React.Component {
+const BlockIconUser = observer(forwardRef(({
+ rootId = '',
+ readonly = false,
+}, ref) => {
- state = {
- isLoading: false,
- };
-
- constructor (props: I.BlockComponent) {
- super(props);
-
- this.onSelect = this.onSelect.bind(this);
- this.onUpload = this.onUpload.bind(this);
- };
+ const [ isLoading, setIsLoading ] = useState(false);
- render (): any {
- const { isLoading } = this.state;
- const { rootId, readonly } = this.props;
-
- return (
-
- {isLoading ? : ''}
- S.Detail.get(rootId, rootId, [])}
- className={readonly ? 'isReadonly' : ''}
- canEdit={!readonly}
- onSelect={this.onSelect}
- onUpload={this.onUpload}
- size={128}
- />
-
- );
+ const onSelect = () => {
+ setIsLoading(true);
+ U.Object.setIcon(rootId, '', '', () => setIsLoading(false));
};
- onSelect () {
- this.setLoading(true);
- U.Object.setIcon(this.props.rootId, '', '', () => this.setLoading(false));
+ const onUpload = (objectId: string) => {
+ setIsLoading(true);
+ U.Object.setIcon(rootId, '', objectId, () => setIsLoading(false));
};
- onUpload (objectId: string) {
- this.setLoading(true);
- U.Object.setIcon(this.props.rootId, '', objectId, () => this.setLoading(false));
- };
-
- setLoading (v: boolean) {
- this.setState({ isLoading: v });
- };
-
-});
+ useImperativeHandle(ref, () => ({
+ setLoading: (v: boolean) => setIsLoading(v),
+ }));
+
+ return (
+
+ {isLoading ? : ''}
+ S.Detail.get(rootId, rootId, [])}
+ className={readonly ? 'isReadonly' : ''}
+ canEdit={!readonly}
+ onSelect={onSelect}
+ onUpload={onUpload}
+ size={128}
+ />
+
+ );
+}));
export default BlockIconUser;
\ No newline at end of file
diff --git a/src/ts/component/block/index.tsx b/src/ts/component/block/index.tsx
index 14187a2cbe..9c83aa7f00 100644
--- a/src/ts/component/block/index.tsx
+++ b/src/ts/component/block/index.tsx
@@ -468,7 +468,7 @@ const Block = observer(class Block extends React.Component {
onDragStart (e: any) {
e.stopPropagation();
- if (!this._isMounted) {
+ if (!this._isMounted || keyboard.isResizing) {
return;
};
@@ -484,7 +484,7 @@ const Block = observer(class Block extends React.Component {
keyboard.disableSelection(true);
if (selection) {
- if (selection.isSelecting) {
+ if (selection.isSelecting()) {
selection.setIsSelecting(false);
};
@@ -512,15 +512,14 @@ const Block = observer(class Block extends React.Component {
return;
};
+ const offset = element.offset();
+
selection.set(I.SelectType.Block, this.ids);
this.menuOpen({
horizontal: I.MenuDirection.Right,
offsetX: element.outerWidth(),
- recalcRect: () => {
- const offset = element.offset();
- return { x: offset.left, y: keyboard.mouse.page.y, width: element.width(), height: 0 };
- },
+ rect: { x: offset.left, y: keyboard.mouse.page.y, width: element.width(), height: 0 },
});
};
@@ -533,8 +532,7 @@ const Block = observer(class Block extends React.Component {
isContextMenuDisabled ||
readonly ||
(block.isText() && (focused == block.id)) ||
- block.isTable() ||
- block.isDataview()
+ !block.canContextMenu()
) {
return;
};
@@ -559,7 +557,7 @@ const Block = observer(class Block extends React.Component {
};
this.menuOpen({
- recalcRect: () => ({ x: keyboard.mouse.page.x, y: keyboard.mouse.page.y, width: 0, height: 0 })
+ rect: { x: keyboard.mouse.page.x, y: keyboard.mouse.page.y, width: 0, height: 0 },
});
});
};
@@ -1048,13 +1046,13 @@ const Block = observer(class Block extends React.Component {
node = $(node);
const items = node.find(Mark.getTag(I.MarkType.Emoji));
- const { block } = this.props;
- const size = U.Data.emojiParam(block.content.style);
-
if (!items.length) {
return;
};
+ const { block } = this.props;
+ const size = U.Data.emojiParam(block.content.style);
+
items.each((i: number, item: any) => {
item = $(item);
diff --git a/src/ts/component/block/media/file.tsx b/src/ts/component/block/media/file.tsx
index 0f9c638093..8ba48e99d5 100644
--- a/src/ts/component/block/media/file.tsx
+++ b/src/ts/component/block/media/file.tsx
@@ -1,136 +1,107 @@
-import * as React from 'react';
+import React, { forwardRef, KeyboardEvent } from 'react';
import { InputWithFile, Loader, IconObject, Error, ObjectName, Icon } from 'Component';
import { I, S, U, focus, translate, Action, analytics } from 'Lib';
import { observer } from 'mobx-react';
-const BlockFile = observer(class BlockFile extends React.Component {
+const BlockFile = observer(forwardRef<{}, I.BlockComponent>((props, ref) => {
- _isMounted = false;
+ const { rootId, block, readonly, onKeyDown, onKeyUp } = props;
+ const { id, content } = block;
+ const { state, style, targetObjectId } = content;
+ const object = S.Detail.get(rootId, targetObjectId, []);
- constructor (props: I.BlockComponent) {
- super(props);
-
- this.onKeyDown = this.onKeyDown.bind(this);
- this.onKeyUp = this.onKeyUp.bind(this);
- this.onFocus = this.onFocus.bind(this);
- this.onClick = this.onClick.bind(this);
- this.onChangeUrl = this.onChangeUrl.bind(this);
- this.onChangeFile = this.onChangeFile.bind(this);
- };
-
- render () {
- const { rootId, block, readonly } = this.props;
- const { id, content } = block;
- const { state, style, targetObjectId } = content;
- const object = S.Detail.get(rootId, targetObjectId, []);
-
- let element = null;
- if (object.isDeleted) {
- element = (
-
-
- {translate('commonDeletedObject')}
-
- );
- } else {
- switch (state) {
- default:
- case I.FileState.Error:
- case I.FileState.Empty: {
- element = (
-
- {state == I.FileState.Error ? : ''}
-
-
- );
- break;
- };
-
- case I.FileState.Uploading: {
- element = ;
- break;
- };
-
- case I.FileState.Done: {
- element = (
-
-
-
- {U.File.size(object.sizeInBytes)}
-
- );
- break;
- };
- };
- };
-
- return (
-
- {element}
-
- );
- };
-
- componentDidMount () {
- this._isMounted = true;
- };
-
- componentWillUnmount () {
- this._isMounted = false;
- };
-
- onKeyDown (e: any) {
- const { onKeyDown } = this.props;
-
+ const onKeyDownHandler = (e: KeyboardEvent) => {
if (onKeyDown) {
- onKeyDown(e, '', [], { from: 0, to: 0 }, this.props);
+ onKeyDown(e, '', [], { from: 0, to: 0 }, props);
};
};
- onKeyUp (e: any) {
- const { onKeyUp } = this.props;
-
+ const onKeyUpHandler = (e: KeyboardEvent) => {
if (onKeyUp) {
- onKeyUp(e, '', [], { from: 0, to: 0 }, this.props);
+ onKeyUp(e, '', [], { from: 0, to: 0 }, props);
};
};
- onFocus () {
- focus.set(this.props.block.id, { from: 0, to: 0 });
+ const onFocus = () => {
+ focus.set(block.id, { from: 0, to: 0 });
};
- onChangeUrl (e: any, url: string) {
- const { rootId, block } = this.props;
+ const onChangeUrl = (e: any, url: string) => {
Action.upload(I.FileType.File, rootId, block.id, url, '');
};
- onChangeFile (e: any, path: string) {
- const { rootId, block } = this.props;
+ const onChangeFile = (e: any, path: string) => {
Action.upload(I.FileType.File, rootId, block.id, '', path);
};
- onClick (e: any) {
+ const onClick = (e: any) => {
if (!e.button) {
- Action.openFile(this.props.block.getTargetObjectId(), analytics.route.block);
+ Action.openFile(block.getTargetObjectId(), analytics.route.block);
+ };
+ };
+
+ let element = null;
+ if (object.isDeleted) {
+ element = (
+
+
+ {translate('commonDeletedObject')}
+
+ );
+ } else {
+ switch (state) {
+ default:
+ case I.FileState.Error:
+ case I.FileState.Empty: {
+ element = (
+ <>
+ {state == I.FileState.Error ? : ''}
+
+ >
+ );
+ break;
+ };
+
+ case I.FileState.Uploading: {
+ element = ;
+ break;
+ };
+
+ case I.FileState.Done: {
+ element = (
+
+
+
+ {U.File.size(object.sizeInBytes)}
+
+ );
+ break;
+ };
};
};
-});
+ return (
+
+ {element}
+
+ );
+
+}));
export default BlockFile;
\ No newline at end of file
diff --git a/src/ts/component/block/table/cell.tsx b/src/ts/component/block/table/cell.tsx
index f9344424f7..0b8698e8e4 100644
--- a/src/ts/component/block/table/cell.tsx
+++ b/src/ts/component/block/table/cell.tsx
@@ -1,4 +1,4 @@
-import * as React from 'react';
+import React, { forwardRef } from 'react';
import { observer } from 'mobx-react';
import { I, U, J, keyboard } from 'Lib';
import { Icon, Block } from 'Component';
@@ -10,164 +10,152 @@ interface Props extends I.BlockComponentTable {
column: I.Block;
};
-const BlockTableCell = observer(class BlockTableCell extends React.Component {
+const BlockTableCell = observer(forwardRef<{}, Props>((props, ref) => {
- constructor (props: Props) {
- super(props);
+ const {
+ readonly, block, rowIdx, columnIdx, row, column, onHandleRow, onHandleColumn, onOptions, onCellFocus, onCellBlur, onCellClick, onCellEnter,
+ onCellLeave, onCellKeyDown, onCellKeyUp, onResizeStart, onDragStartColumn, onDragStartRow, onEnterHandle, onLeaveHandle, onCellUpdate
+ } = props;
- this.onMouseDown = this.onMouseDown.bind(this);
- };
+ const { isHeader } = row.content;
+ const cn = [ 'cell', 'column' + column.id ];
+ const cellId = [ row.id, column.id ].join('-');
+ const inner = ;
+ const cnm = [ 'menu' ];
- render () {
- const {
- readonly, block, rowIdx, columnIdx, row, column, onHandleRow, onHandleColumn, onOptions, onCellFocus, onCellBlur, onCellClick, onCellEnter,
- onCellLeave, onCellKeyDown, onCellKeyUp, onResizeStart, onDragStartColumn, onDragStartRow, onEnterHandle, onLeaveHandle, onCellUpdate
- } = this.props;
+ if (block) {
+ cn.push('align-v' + block.vAlign);
- if (!row || !column) {
- return null;
+ if (block.bgColor) {
+ cnm.push(`bgColor bgColor-${block.bgColor}`);
};
+ };
+
+ const Handle = (item: any) => {
+ const cn = [ 'handle' ];
- const { isHeader } = row.content;
- const cn = [ 'cell', 'column' + column.id ];
- const cellId = [ row.id, column.id ].join('-');
- const inner = ;
- const cnm = [ 'menu' ];
+ let onDragStart = null;
+ let onClick = null;
+ let canDrag = true;
- if (block) {
- cn.push('align-v' + block.vAlign);
+ switch (item.type) {
+ case I.BlockType.TableColumn:
+ cn.push('handleColumn canDrag');
- if (block.bgColor) {
- cnm.push(`bgColor bgColor-${block.bgColor}`);
- };
+ onDragStart = e => onDragStartColumn(e, column.id);
+ onClick = e => onHandleColumn(e, item.type, row.id, column.id, cellId);
+ break;
+
+ case I.BlockType.TableRow:
+ cn.push('handleRow');
+ canDrag = !isHeader;
+
+ if (canDrag) {
+ onDragStart = e => onDragStartRow(e, row.id);
+ };
+ onClick = e => onHandleRow(e, item.type, row.id, column.id, cellId);
+ break;
};
- const Handle = (item: any) => {
- const cn = [ 'handle' ];
-
- let onDragStart = null;
- let onClick = null;
- let canDrag = true;
-
- switch (item.type) {
- case I.BlockType.TableColumn:
- cn.push('handleColumn canDrag');
-
- onDragStart = e => onDragStartColumn(e, column.id);
- onClick = e => onHandleColumn(e, item.type, row.id, column.id, cellId);
- break;
-
- case I.BlockType.TableRow:
- cn.push('handleRow');
- canDrag = !isHeader;
-
- if (canDrag) {
- onDragStart = e => onDragStartRow(e, row.id);
- };
- onClick = e => onHandleRow(e, item.type, row.id, column.id, cellId);
- break;
- };
-
- if (canDrag) {
- cn.push('canDrag');
- };
-
- return (
- onEnterHandle(e, item.type, row.id, column.id)}
- onMouseLeave={e => onLeaveHandle(e)}
- onClick={onClick}
- onDragStart={onDragStart}
- onContextMenu={onClick}
- >
-
-
- );
+ if (canDrag) {
+ cn.push('canDrag');
+ };
+
+ return (
+ onEnterHandle(e, item.type, row.id, column.id)}
+ onMouseLeave={e => onLeaveHandle(e)}
+ onClick={onClick}
+ onDragStart={onDragStart}
+ onContextMenu={onClick}
+ >
+
+
+ );
+ };
+
+ const EmptyBlock = () => {
+ const cn = [ 'block', 'blockText', 'noPlus', 'align0' ];
+ const cv = [ 'value' ];
+
+ if (readonly) {
+ cn.push('isReadonly');
+ cv.push('isReadonly');
};
- const EmptyBlock = () => {
- const cn = [ 'block', 'blockText', 'noPlus', 'align0' ];
- const cv = [ 'value' ];
-
- if (readonly) {
- cn.push('isReadonly');
- cv.push('isReadonly');
- };
-
- return (
-
-
-
-
-
-
-
- onCellFocus(e, row.id, column.id, cellId)}
- onBlur={e => onCellBlur(e, row.id, column.id, cellId)}
- onDragStart={e => e.preventDefault()}
- />
-
+ return (
+
+
+
+
+
+
+
+ onCellFocus(e, row.id, column.id, cellId)}
+ onBlur={e => onCellBlur(e, row.id, column.id, cellId)}
+ onDragStart={e => e.preventDefault()}
+ />
- );
- };
-
- return (
- onCellClick(e, row.id, column.id, cellId)}
- onMouseEnter={e => onCellEnter(e, row.id, column.id, cellId)}
- onMouseLeave={e => onCellLeave(e, row.id, column.id, cellId)}
- onMouseDown={this.onMouseDown}
- {...U.Common.dataProps({ 'column-id': column.id })}
- >
- {!rowIdx ? : ''}
- {!columnIdx ? : ''}
-
- {block ? (
- {
- onCellKeyDown(e, row.id, column.id, cellId, text, marks, range, props);
- }}
- onKeyUp={(e: any, text: string, marks: I.Mark[], range: I.TextRange, props: any) => {
- onCellKeyUp(e, row.id, column.id, cellId, text, marks, range, props);
- }}
- onUpdate={() => onCellUpdate(cellId)}
- onFocus={e => onCellFocus(e, row.id, column.id, cellId)}
- onBlur={e => onCellBlur(e, row.id, column.id, cellId)}
- getWrapperWidth={() => J.Size.editor}
- />
- ) : (
-
- )}
-
- {!readonly ? onResizeStart(e, column.id)} /> : ''}
- onOptions(e, I.BlockType.Text, row.id, column.id, cellId)} />
);
};
- onMouseDown (e: any) {
+ const onMouseDown = () => {
keyboard.disableSelection(true);
$(window).off('mousedown.table-cell').on('mousedown.table-cell', () => keyboard.disableSelection(false));
};
-});
+ return (
+ onCellClick(e, row.id, column.id, cellId)}
+ onMouseEnter={e => onCellEnter(e, row.id, column.id, cellId)}
+ onMouseLeave={e => onCellLeave(e, row.id, column.id, cellId)}
+ onMouseDown={onMouseDown}
+ {...U.Common.dataProps({ 'column-id': column.id })}
+ >
+ {!rowIdx ? : ''}
+ {!columnIdx ? : ''}
+
+ {block ? (
+ {
+ onCellKeyDown(e, row.id, column.id, cellId, text, marks, range, props);
+ }}
+ onKeyUp={(e: any, text: string, marks: I.Mark[], range: I.TextRange, props: any) => {
+ onCellKeyUp(e, row.id, column.id, cellId, text, marks, range, props);
+ }}
+ onUpdate={() => onCellUpdate(cellId)}
+ onFocus={e => onCellFocus(e, row.id, column.id, cellId)}
+ onBlur={e => onCellBlur(e, row.id, column.id, cellId)}
+ getWrapperWidth={() => J.Size.editor}
+ />
+ ) : (
+
+ )}
+
+ {!readonly ? onResizeStart(e, column.id)} /> : ''}
+ onOptions(e, I.BlockType.Text, row.id, column.id, cellId)} />
+
+ );
+
+}));
export default BlockTableCell;
\ No newline at end of file
diff --git a/src/ts/component/block/table/row.tsx b/src/ts/component/block/table/row.tsx
index b6e949787a..2299c97c2b 100644
--- a/src/ts/component/block/table/row.tsx
+++ b/src/ts/component/block/table/row.tsx
@@ -1,4 +1,4 @@
-import * as React from 'react';
+import React, { forwardRef, useEffect } from 'react';
import { I, S } from 'Lib';
import { observer } from 'mobx-react';
import Cell from './cell';
@@ -7,49 +7,45 @@ interface Props extends I.BlockComponentTable {
onRowUpdate: (rowId: string) => void;
};
-const BlockTableRow = observer(class BlockTableRow extends React.Component {
+const BlockTableRow = observer(forwardRef<{}, Props>((props, ref) => {
- render () {
- const { rootId, block, index, getData } = this.props;
- const { columns } = getData();
- const childrenIds = S.Block.getChildrenIds(rootId, block.id);
- const children = S.Block.getChildren(rootId, block.id);
- const length = childrenIds.length;
- const cn = [ 'row' ];
+ const { rootId, block, index, getData, onRowUpdate } = props;
+ const { columns } = getData();
+ const childrenIds = S.Block.getChildrenIds(rootId, block.id);
+ const children = S.Block.getChildren(rootId, block.id);
+ const length = childrenIds.length;
+ const cn = [ 'row' ];
- if (block.content.isHeader) {
- cn.push('isHeader');
- };
-
- return (
-
- {columns.map((column: any, i: number) => {
- const child = children.find(it => it.id == [ block.id, column.id ].join('-'));
- return (
- |
- );
- })}
-
- );
+ if (block.content.isHeader) {
+ cn.push('isHeader');
};
- componentDidUpdate () {
- const { onRowUpdate, block } = this.props;
-
+ useEffect(() => {
if (onRowUpdate) {
onRowUpdate(block.id);
};
- };
-
-});
+ });
+
+ return (
+
+ {columns.map((column: any, i: number) => {
+ const child = children.find(it => it.id == [ block.id, column.id ].join('-'));
+ return (
+ |
+ );
+ })}
+
+ );
+
+}));
export default BlockTableRow;
\ No newline at end of file
diff --git a/src/ts/component/block/tableOfContents.tsx b/src/ts/component/block/tableOfContents.tsx
index a642cf6421..d81cdb4944 100644
--- a/src/ts/component/block/tableOfContents.tsx
+++ b/src/ts/component/block/tableOfContents.tsx
@@ -1,81 +1,29 @@
-import * as React from 'react';
+import React, { forwardRef, KeyboardEvent } from 'react';
import { I, S, U, J, focus, translate } from 'Lib';
import { observer } from 'mobx-react';
-const BlockTableOfContents = observer(class BlockTableOfContents extends React.Component {
+const BlockTableOfContents = observer(forwardRef<{}, I.BlockComponent>((props, ref) => {
- _isMounted = false;
-
- constructor (props: I.BlockComponent) {
- super(props);
-
- this.onKeyDown = this.onKeyDown.bind(this);
- this.onKeyUp = this.onKeyUp.bind(this);
- this.onFocus = this.onFocus.bind(this);
- };
-
- render () {
- const { block } = this.props;
- const cn = [ 'wrap', 'focusable', 'c' + block.id ];
- const tree = this.getTree();
-
- const Item = (item: any) => {
- return (
- this.onClick(e, item.id)}
- style={{ paddingLeft: item.depth * 24 }}
- >
- {item.text}
-
- );
- };
-
- return (
-
- {!tree.length ? (
- {translate('blockTableOfContentsAdd')}
- ) : (
-
- {tree.map((item: any, i: number) => (
-
- ))}
-
- )}
-
- );
- };
-
- componentDidMount () {
- this._isMounted = true;
- };
-
- componentWillUnmount () {
- this._isMounted = false;
- };
-
- onKeyDown (e: any) {
- const { onKeyDown } = this.props;
+ const { rootId, block, isPopup, onKeyDown, onKeyUp } = props;
+ const cn = [ 'wrap', 'focusable', `c${block.id}` ];
+ const onKeyDownHandler = (e: KeyboardEvent) => {
if (onKeyDown) {
- onKeyDown(e, '', [], { from: 0, to: 0 }, this.props);
+ onKeyDown(e, '', [], { from: 0, to: 0 }, props);
};
};
- onKeyUp (e: any) {
- const { onKeyUp } = this.props;
-
+ const onKeyUpHandler = (e: KeyboardEvent) => {
if (onKeyUp) {
- onKeyUp(e, '', [], { from: 0, to: 0 }, this.props);
+ onKeyUp(e, '', [], { from: 0, to: 0 }, props);
};
};
- onFocus () {
- focus.set(this.props.block.id, { from: 0, to: 0 });
+ const onFocus = () => {
+ focus.set(block.id, { from: 0, to: 0 });
};
- getTree () {
- const { rootId } = this.props;
+ const getTree = () => {
const blocks = S.Block.unwrapTree([ S.Block.wrapTree(rootId, rootId) ]).filter(it => it.isTextHeader());
const list: any[] = [];
@@ -107,12 +55,12 @@ const BlockTableOfContents = observer(class BlockTableOfContents extends React.C
text: String(block.content.text || translate('defaultNamePage')),
});
});
+
return list;
};
- onClick (e: any, id: string) {
- const { isPopup } = this.props;
- const node = $('.focusable.c' + id);
+ const onClick = (e: any, id: string) => {
+ const node = $(`.focusable.c${id}`);
if (!node.length) {
return;
@@ -126,7 +74,39 @@ const BlockTableOfContents = observer(class BlockTableOfContents extends React.C
container.scrollTop(y);
};
-
-});
+
+ const Item = (item: any) => (
+ onClick(e, item.id)}
+ style={{ paddingLeft: item.depth * 24 }}
+ >
+ {item.text}
+
+ );
+
+ const tree = getTree();
+
+ return (
+
+ {!tree.length ? (
+ {translate('blockTableOfContentsAdd')}
+ ) : (
+ <>
+ {tree.map((item: any, i: number) => (
+
+ ))}
+ >
+ )}
+
+ );
+
+}));
export default BlockTableOfContents;
\ No newline at end of file
diff --git a/src/ts/component/block/text.tsx b/src/ts/component/block/text.tsx
index df112ca0a4..5e5619e03d 100644
--- a/src/ts/component/block/text.tsx
+++ b/src/ts/component/block/text.tsx
@@ -63,7 +63,9 @@ const BlockText = observer(class BlockText extends React.Component {
const { text, marks, style, checked, color, iconEmoji, iconImage } = content;
const { theme } = S.Common;
const root = S.Block.getLeaf(rootId, rootId);
- const cv: string[] = [ 'value', 'focusable', 'c' + id ];
+ const cn = [ 'flex' ];
+ const cv = [ 'value', 'focusable', 'c' + id ];
+ const checkRtl = keyboard.isRtl || U.Common.checkRtl(text);
let marker: any = null;
let placeholder = translate('placeholderBlock');
@@ -74,6 +76,10 @@ const BlockText = observer(class BlockText extends React.Component {
cv.push('textColor textColor-' + color);
};
+ if (checkRtl) {
+ cn.push('isRtl');
+ };
+
// Subscriptions
for (const mark of marks) {
if ([ I.MarkType.Mention, I.MarkType.Object ].includes(mark.type)) {
@@ -168,7 +174,7 @@ const BlockText = observer(class BlockText extends React.Component {
return (
this.node = node}
- className="flex"
+ className={cn.join(' ')}
>
{marker ? : ''}
@@ -194,6 +200,7 @@ const BlockText = observer(class BlockText extends React.Component {
onMouseUp={this.onMouseUp}
onInput={this.onInput}
onDragStart={e => e.preventDefault()}
+ onCompositionEnd={this.onKeyUp}
/>
);
@@ -305,15 +312,14 @@ const BlockText = observer(class BlockText extends React.Component {
const tag = Mark.getTag(I.MarkType.Latex);
const code = Mark.getTag(I.MarkType.Code);
const value = this.refEditable.getHtmlValue();
- const reg = /(^|[^\d<]+)?\$((?:[^$<]|\.)*?)\$([^\d]|$)/gi;
- const regCode = new RegExp(`^${code}`, 'i');
+ const reg = /(^|[^\d<\$]+)?\$((?:[^$<]|\.)*?)\$([^\d>\$]+|$)/gi;
+ const regCode = new RegExp(`^${code}|${code}$`, 'i');
if (!/\$((?:[^$<]|\.)*?)\$/.test(value)) {
return;
};
const match = value.matchAll(reg);
-
const render = (s: string) => {
s = U.Common.fromHtmlSpecialChars(s);
@@ -340,7 +346,17 @@ const BlockText = observer(class BlockText extends React.Component {
const m3 = String(m[3] || '');
// Skip inline code marks
- if (regCode.test(m1)) {
+ if (regCode.test(m1) || regCode.test(m3)) {
+ return;
+ };
+
+ // Skip Brazilian Real
+ if (/R$/.test(m1) || /R$/.test(m2)) {
+ return;
+ };
+
+ // Escaped $ sign
+ if (/\\$/.test(m1) || /\\$/.test(m2)) {
return;
};
@@ -697,6 +713,8 @@ const BlockText = observer(class BlockText extends React.Component {
const oneSymbolBefore = range ? value[range.from - 1] : '';
const twoSymbolBefore = range ? value[range.from - 2] : '';
+ keyboard.setRtl(U.Common.checkRtl(value));
+
if (range) {
isAllowedMenu = isAllowedMenu && (!range.from || (range.from == 1) || [ ' ', '\n', '(', '[', '"', '\'' ].includes(twoSymbolBefore));
};
@@ -1090,7 +1108,7 @@ const BlockText = observer(class BlockText extends React.Component {
};
onSelect () {
- if (keyboard.isContextDisabled) {
+ if (keyboard.isContextDisabled || keyboard.isComposition) {
return;
};
diff --git a/src/ts/component/block/type.tsx b/src/ts/component/block/type.tsx
index 247b216726..f4422e63a8 100644
--- a/src/ts/component/block/type.tsx
+++ b/src/ts/component/block/type.tsx
@@ -180,7 +180,6 @@ const BlockType = observer(class BlockType extends React.Component it.id) },
- { relationKey: 'recommendedLayout', condition: I.FilterCondition.In, value: U.Object.getPageLayouts() },
+ { relationKey: 'recommendedLayout', condition: I.FilterCondition.In, value: U.Object.getPageLayouts().concat(U.Object.getSetLayouts()) },
{ relationKey: 'uniqueKey', condition: I.FilterCondition.NotEqual, value: J.Constant.typeKey.template }
],
onClick: (item: any) => {
diff --git a/src/ts/component/drag/box.tsx b/src/ts/component/drag/box.tsx
index 237ecf4c46..57c6b68dbc 100644
--- a/src/ts/component/drag/box.tsx
+++ b/src/ts/component/drag/box.tsx
@@ -1,4 +1,4 @@
-import * as React from 'react';
+import React, { FC, useRef } from 'react';
import { U } from 'Lib';
interface Props {
@@ -6,61 +6,29 @@ interface Props {
onDragEnd(oldIndex: number, newIndex: number): void;
};
-class DragBox extends React.Component {
-
- _isMounted = false;
- node: any = null;
- cache: any = {};
- ox = 0;
- oy = 0;
- oldIndex = -1;
- newIndex = -1;
-
- constructor (props: Props) {
- super(props);
-
- this.onDragStart = this.onDragStart.bind(this);
- };
-
- render () {
- const children = React.Children.map(this.props.children, (child: any) => {
- return React.cloneElement(child, {
- onDragStart: this.onDragStart
- });
- });
+const DragBox: FC = ({ children: initialChildren, onDragEnd }) => {
- return (
- this.node = node}
- className="dragbox"
- >
- {children}
-
- );
- };
-
- componentDidMount () {
- this._isMounted = true;
- };
-
- componentWillUnmount () {
- this._isMounted = false;
- };
+ const nodeRef = useRef(null);
+ const cache = useRef({});
+ const ox = useRef(0);
+ const oy = useRef(0);
+ const oldIndex = useRef(-1);
+ const newIndex = useRef(-1);
- onDragStart (e: any) {
+ const onDragStart = (e: any) => {
e.preventDefault();
e.stopPropagation();
- if (!this._isMounted) {
+ if (!nodeRef.current) {
return;
};
const win = $(window);
- const node = $(this.node);
+ const node = $(nodeRef.current);
const items = node.find('.isDraggable');
const element = $(e.currentTarget);
const clone = element.clone();
- const offset = node.offset();
+ const { left, top } = node.offset();
items.each((i: number, item: any) => {
item = $(item);
@@ -71,7 +39,7 @@ class DragBox extends React.Component {
};
const p = item.position();
- this.cache[id] = {
+ cache.current[id] = {
x: p.left,
y: p.top,
width: item.outerWidth(),
@@ -79,58 +47,57 @@ class DragBox extends React.Component {
};
});
- this.ox = offset.left;
- this.oy = offset.top;
- this.oldIndex = element.data('index');
+ ox.current = left;
+ oy.current = top;
+ oldIndex.current = element.data('index');
node.append(clone);
clone.addClass('isClone');
element.addClass('isDragging');
win.off('mousemove.dragbox mouseup.dragbox');
- win.on('mousemove.dragbox', e => this.onDragMove(e));
- win.on('mouseup.dragbox', e => this.onDragEnd(e));
+ win.on('mousemove.dragbox', e => onDragMove(e));
+ win.on('mouseup.dragbox', e => onDragEndHandler(e));
};
- onDragMove (e: any) {
- if (!this._isMounted) {
+ const onDragMove = (e: any) => {
+ if (!nodeRef.current) {
return;
};
- const node = $(this.node);
+ const node = $(nodeRef.current);
const items = node.find('.isDraggable');
const clone = node.find('.isDraggable.isClone');
const width = clone.outerWidth();
const height = clone.outerHeight();
- const x = e.pageX - this.ox - width / 2;
- const y = e.pageY - this.oy - height / 2;
+ const x = e.pageX - ox.current - width / 2;
+ const y = e.pageY - oy.current - height / 2;
const center = x + width / 2;
- this.newIndex = -1;
+ newIndex.current = -1;
node.find('.isDraggable.isOver').removeClass('isOver left right');
clone.css({ transform: `translate3d(${x}px,${y}px,0px)` });
for (let i = 0; i < items.length; ++i) {
const el = $(items.get(i));
- const rect = this.cache[el.data('id')];
+ const rect = cache.current[el.data('id')];
if (rect && U.Common.rectsCollide({ x: center, y, width: 2, height }, rect)) {
const isLeft = center <= rect.x + rect.width / 2;
- this.newIndex = i;
+ newIndex.current = i;
el.addClass('isOver ' + (isLeft ? 'left' : 'right'));
break;
};
};
};
- onDragEnd (e: any) {
- if (!this._isMounted) {
+ const onDragEndHandler = (e: any) => {
+ if (!nodeRef.current) {
return;
};
- const node = $(this.node);
- const { onDragEnd } = this.props;
+ const node = $(nodeRef.current);
node.find('.isDraggable.isClone').remove();
node.find('.isDraggable.isDragging').removeClass('isDragging');
@@ -138,15 +105,25 @@ class DragBox extends React.Component {
$(window).off('mousemove.dragbox mouseup.dragbox');
- if (this.newIndex >= 0) {
- onDragEnd(this.oldIndex, this.newIndex);
+ if (newIndex.current >= 0) {
+ onDragEnd(oldIndex.current, newIndex.current);
};
- this.cache = {};
- this.oldIndex = -1;
- this.newIndex = -1;
+ cache.current = {};
+ oldIndex.current = -1;
+ newIndex.current = -1;
};
-
+
+ const children = React.Children.map(initialChildren, (child: any) => React.cloneElement(child, { onDragStart }));
+
+ return (
+
+ {children}
+
+ );
};
export default DragBox;
\ No newline at end of file
diff --git a/src/ts/component/drag/layer.tsx b/src/ts/component/drag/layer.tsx
index e76b9f67fc..409674ac91 100644
--- a/src/ts/component/drag/layer.tsx
+++ b/src/ts/component/drag/layer.tsx
@@ -1,93 +1,22 @@
-import * as React from 'react';
+import React, { forwardRef, useRef, useImperativeHandle } from 'react';
import * as ReactDOM from 'react-dom';
import $ from 'jquery';
+import { observer } from 'mobx-react';
import { I, M, S, U, J, keyboard } from 'Lib';
-interface State {
- rootId: string;
- type: I.DropType;
- width: number;
- ids: string[];
-};
-
-class DragLayer extends React.Component {
-
- _isMounted = false;
- node: any = null;
- state = {
- rootId: '',
- type: I.DropType.None,
- width: 0,
- ids: [] as string[],
- };
-
- constructor (props: any) {
- super(props);
-
- this.show = this.show.bind(this);
- this.hide = this.hide.bind(this);
- };
+const DragLayer = observer(forwardRef((_, ref: any) => {
- render () {
- const { width } = this.state;
-
- return (
- this.node = node}
- id="dragLayer"
- className="dragLayer"
- style={{ width }}
- >
-
-
- );
- };
-
- componentDidMount () {
- this._isMounted = true;
- };
-
- componentDidUpdate () {
- if (!this._isMounted) {
- return;
- };
+ const nodeRef = useRef(null);
- const node = $(this.node);
-
- node.find('.block').attr({ id: '' });
- node.find('.selectionTarget').attr({ id: '' });
-
- this.renderContent();
- };
-
- componentWillUnmount () {
- this._isMounted = false;
- };
-
- show (rootId: string, type: I.DropType, ids: string[], component: any, x: number, y: number) {
- if (!this._isMounted) {
- return;
- };
-
+ const show = (rootId: string, type: I.DropType, ids: string[], component: any) => {
const comp = $(ReactDOM.findDOMNode(component));
const rect = (comp.get(0) as Element).getBoundingClientRect();
-
- this.setState({ rootId, type, width: rect.width - J.Size.blockMenu, ids });
- };
-
- hide () {
- if (this._isMounted) {
- this.setState({ rootId: '', type: I.DropType.None, ids: [], width: 0 });
- };
- };
-
- renderContent () {
- const { rootId, type, ids } = this.state;
- const node = $(this.node);
+ const width = rect.width - J.Size.blockMenu;
+ const node = $(nodeRef.current);
const inner = node.find('#inner').html('');
const container = U.Common.getPageContainer(keyboard.isPopup());
const wrap = $('');
-
+
switch (type) {
case I.DropType.Block: {
wrap.addClass('blocks');
@@ -157,8 +86,31 @@ class DragLayer extends React.Component {
};
inner.append(wrap);
+
+ node.css({ width });
+ node.find('.block').attr({ id: '' });
+ node.find('.selectionTarget').attr({ id: '' });
};
-
-};
+
+ const hide = () => {
+ $(nodeRef.current).find('#inner').html('');
+ };
+
+ useImperativeHandle(ref, () => ({
+ show,
+ hide,
+ }));
+
+ return (
+
+ );
+
+}));
export default DragLayer;
\ No newline at end of file
diff --git a/src/ts/component/drag/provider.tsx b/src/ts/component/drag/provider.tsx
index 269ab591ec..8d0910fb70 100644
--- a/src/ts/component/drag/provider.tsx
+++ b/src/ts/component/drag/provider.tsx
@@ -1,4 +1,4 @@
-import * as React from 'react';
+import React, { forwardRef, useRef, useImperativeHandle } from 'react';
import $ from 'jquery';
import raf from 'raf';
import { observer } from 'mobx-react';
@@ -10,64 +10,41 @@ interface Props {
children?: React.ReactNode;
};
-const OFFSET = 100;
-
-const DragProvider = observer(class DragProvider extends React.Component {
-
- node: any = null;
- refLayer: any = null;
- position: I.BlockPosition = I.BlockPosition.None;
- hoverData: any = null;
- canDrop = false;
- init = false;
- top = 0;
- frame = 0;
-
- objects: any = null;
- objectData: Map = new Map();
-
- origin: any = null;
-
- constructor (props: Props) {
- super(props);
-
- this.onDragOver = this.onDragOver.bind(this);
- this.onDragStart = this.onDragStart.bind(this);
- this.onDragEnd = this.onDragEnd.bind(this);
- this.onDropCommon = this.onDropCommon.bind(this);
- this.onDrop = this.onDrop.bind(this);
- };
+interface DragProviderRefProps {
+ onDragStart: (e: any, dropType: I.DropType, ids: string[], component: any) => void;
+ onScroll: () => void;
+};
- render () {
- const { children } = this.props;
-
- return (
- this.node = node}
- id="dragProvider"
- className="dragProvider"
- onDragOver={this.onDragOver}
- onDrop={this.onDropCommon}
- >
- this.refLayer = ref} />
- {children}
-
- );
- };
+const OFFSET = 100;
- initData () {
- if (this.init) {
+const DragProvider = observer(forwardRef((props, ref: any) => {
+
+ const { children } = props;
+ const nodeRef = useRef(null);
+ const layerRef = useRef(null);
+ const isInitialised = useRef(false);
+ const position = useRef(I.BlockPosition.None);
+ const hoverData = useRef(null);
+ const canDrop = useRef(false);
+ const top = useRef(0);
+ const frame = useRef(0);
+ const objects = useRef(null);
+ const objectData = useRef(new Map());
+ const origin = useRef(null);
+
+ const initData = () => {
+ if (isInitialised.current) {
return;
};
const isPopup = keyboard.isPopup();
const container = $(isPopup ? '#popupPage-innerWrap' : '#root');
- this.clearState();
- this.init = true;
- this.objects = container.find('.dropTarget.isDroppable');
+ clearState();
+ isInitialised.current = true;
+ objects.current = container.find('.dropTarget.isDroppable');
- this.objects.each((i: number, el: any) => {
+ objects.current.each((i: number, el: any) => {
const item = $(el);
const data = {
id: item.attr('data-id'),
@@ -98,7 +75,7 @@ const DragProvider = observer(class DragProvider extends React.Component
};
};
- this.objectData.set(data.cacheKey, {
+ objectData.current.set(data.cacheKey, {
...data,
obj: item,
index: i,
@@ -114,11 +91,11 @@ const DragProvider = observer(class DragProvider extends React.Component
});
};
- onDropCommon (e: any) {
+ const onDropCommon = (e: any) => {
e.preventDefault();
if (keyboard.isCommonDropDisabled) {
- this.clearState();
+ clearState();
return;
};
@@ -135,17 +112,16 @@ const DragProvider = observer(class DragProvider extends React.Component
const isFileDrop = dataTransfer.files && dataTransfer.files.length;
const last = S.Block.getFirstBlock(rootId, -1, it => it && it.canCreateBlock());
- let position = this.position;
let data: any = null;
let targetId = '';
let target: any = null;
- if (this.hoverData && (this.position != I.BlockPosition.None)) {
- data = this.hoverData;
+ if (hoverData.current && (position.current != I.BlockPosition.None)) {
+ data = hoverData.current;
} else
if (last && isFileDrop) {
- data = this.objectData.get([ I.DropType.Block, last.id ].join('-'));
- position = I.BlockPosition.Bottom;
+ data = objectData.current.get([ I.DropType.Block, last.id ].join('-'));
+ position.current = I.BlockPosition.Bottom;
};
if (data) {
@@ -156,7 +132,7 @@ const DragProvider = observer(class DragProvider extends React.Component
// Last drop zone
if (targetId == 'blockLast') {
targetId = '';
- position = I.BlockPosition.Bottom;
+ position.current = I.BlockPosition.Bottom;
};
// String items drop
@@ -165,7 +141,7 @@ const DragProvider = observer(class DragProvider extends React.Component
C.BlockPaste(rootId, targetId, { from: 0, to: 0 }, [], false, { html }, '');
});
- this.clearState();
+ clearState();
return;
};
@@ -180,43 +156,43 @@ const DragProvider = observer(class DragProvider extends React.Component
console.log('[DragProvider].onDrop paths', paths);
- C.FileDrop(rootId, targetId, position, paths, () => {
- if (target && target.isTextToggle() && (position == I.BlockPosition.InnerFirst)) {
+ C.FileDrop(rootId, targetId, position.current, paths, () => {
+ if (target && target.isTextToggle() && (position.current == I.BlockPosition.InnerFirst)) {
S.Block.toggle(rootId, targetId, true);
};
});
} else
- if (data && this.canDrop && (position != I.BlockPosition.None)) {
- this.onDrop(e, data.dropType, targetId, position);
+ if (data && canDrop && (position.current != I.BlockPosition.None)) {
+ onDrop(e, data.dropType, targetId, position.current);
};
- this.clearState();
+ clearState();
};
- onDragStart (e: any, dropType: I.DropType, ids: string[], component: any) {
+ const onDragStart = (e: any, dropType: I.DropType, ids: string[], component: any) => {
const rootId = keyboard.getRootId();
const isPopup = keyboard.isPopup();
const selection = S.Common.getRef('selectionProvider');
const win = $(window);
- const node = $(this.node);
+ const node = $(nodeRef.current);
const container = U.Common.getScrollContainer(isPopup);
const sidebar = $('#sidebar');
const layer = $('#dragLayer');
const body = $('body');
const dataTransfer = { rootId, dropType, ids, withAlt: e.altKey };
- this.origin = component;
+ origin.current = component;
e.stopPropagation();
focus.clear(true);
console.log('[DragProvider].onDragStart', dropType, ids);
- this.top = container.scrollTop();
- this.refLayer.show(rootId, dropType, ids, component);
- this.setClass(ids);
- this.initData();
- this.unbind();
+ top.current = container.scrollTop();
+ layerRef.current.show(rootId, dropType, ids, component);
+ setClass(ids);
+ initData();
+ unbind();
e.dataTransfer.setDragImage(layer.get(0), 0, 0);
e.dataTransfer.setData('text/plain', JSON.stringify(dataTransfer));
@@ -229,11 +205,11 @@ const DragProvider = observer(class DragProvider extends React.Component
keyboard.disableSelection(true);
Preview.hideAll();
- win.on('drag.drag', e => this.onDrag(e));
- win.on('dragend.drag', e => this.onDragEnd(e));
+ win.on('drag.drag', e => onDrag(e));
+ win.on('dragend.drag', e => onDragEnd(e));
- container.off('scroll.drag').on('scroll.drag', throttle(() => this.onScroll(), 20));
- sidebar.off('scroll.drag').on('scroll.drag', throttle(() => this.onScroll(), 20));
+ container.off('scroll.drag').on('scroll.drag', throttle(() => onScroll(), 20));
+ sidebar.off('scroll.drag').on('scroll.drag', throttle(() => onScroll(), 20));
$('.colResize.active').removeClass('active');
scrollOnMove.onMouseDown(e, isPopup);
@@ -244,7 +220,7 @@ const DragProvider = observer(class DragProvider extends React.Component
selection?.hide();
};
- onDragOver (e: any) {
+ const onDragOver = (e: any) => {
if (keyboard.isCommonDropDisabled) {
return;
};
@@ -252,25 +228,25 @@ const DragProvider = observer(class DragProvider extends React.Component
e.preventDefault();
e.stopPropagation();
- this.initData();
- this.checkNodes(e, e.pageX, e.pageY);
+ initData();
+ checkNodes(e, e.pageX, e.pageY);
};
- onDrag (e: any) {
+ const onDrag = (e: any) => {
scrollOnMove.onMouseMove(e.clientX, e.clientY);
};
- onDragEnd (e: any) {
+ const onDragEnd = (e: any) => {
const isPopup = keyboard.isPopup();
- const node = $(this.node);
+ const node = $(nodeRef.current);
const container = U.Common.getScrollContainer(isPopup);
const sidebar = $('#sidebar');
const body = $('body');
- this.refLayer.hide();
- this.clearState();
- this.clearStyle();
- this.unbind();
+ layerRef.current.hide();
+ clearState();
+ clearStyle();
+ unbind();
keyboard.setDragging(false);
keyboard.disableSelection(false);
@@ -285,21 +261,20 @@ const DragProvider = observer(class DragProvider extends React.Component
scrollOnMove.onMouseUp(e);
};
- onDrop (e: any, targetType: string, targetId: string, position: I.BlockPosition) {
+ const onDrop = (e: any, targetType: string, targetId: string, position: I.BlockPosition) => {
const selection = S.Common.getRef('selectionProvider');
let data: any = {};
- try { data = JSON.parse(e.dataTransfer.getData('text/plain')) || {}; } catch (e) { /**/ };
+ try { data = JSON.parse(e.dataTransfer.getData('text/plain')) || {}; } catch (e) {};
const { rootId, dropType, withAlt } = data;
const ids = data.ids || [];
+ const contextId = rootId;
if (!ids.length) {
return;
};
- const contextId = rootId;
-
let targetContextId = keyboard.getRootId();
let isToggle = false;
@@ -345,7 +320,7 @@ const DragProvider = observer(class DragProvider extends React.Component
case I.DropType.Block: {
// Drop into column is targeting last block
- if (this.hoverData.isTargetCol) {
+ if (hoverData.current.isTargetCol) {
const childrenIds = S.Block.getChildrenIds(targetContextId, targetId);
targetId = childrenIds.length ? childrenIds[childrenIds.length - 1] : '';
@@ -419,12 +394,12 @@ const DragProvider = observer(class DragProvider extends React.Component
switch (position) {
case I.BlockPosition.Top:
case I.BlockPosition.Bottom: {
- if (!this.origin) {
+ if (!origin.current) {
break;
};
// Sort
- const { onRecordDrop } = this.origin;
+ const { onRecordDrop } = origin.current;
if (onRecordDrop) {
onRecordDrop(targetId, ids);
@@ -490,16 +465,16 @@ const DragProvider = observer(class DragProvider extends React.Component
console.log('[DragProvider].onDrop from:', contextId, 'to: ', targetContextId);
};
- onScroll () {
+ const onScroll = () => {
if (keyboard.isDragging) {
- for (const [ key, value ] of this.objectData) {
+ for (const [ key, value ] of objectData.current) {
const { left, top } = value.obj.offset();
- this.objectData.set(key, { ...value, x: left, y: top });
+ objectData.current.set(key, { ...value, x: left, y: top });
};
};
};
- checkNodes (e: any, ex: number, ey: number) {
+ const checkNodes = (e: any, ex: number, ey: number) => {
const dataTransfer = e.dataTransfer || e.originalEvent.dataTransfer;
const isItemDrag = U.Common.getDataTransferItems(dataTransfer.items).length ? true : false;
const isFileDrag = dataTransfer.types.includes('Files');
@@ -512,12 +487,12 @@ const DragProvider = observer(class DragProvider extends React.Component
break;
};
};
- } catch (e) { /**/ };
+ } catch (e) { };
- this.setHoverData(null);
- this.setPosition(I.BlockPosition.None);
+ setHoverData(null);
+ setPosition(I.BlockPosition.None);
- for (const [ key, value ] of this.objectData) {
+ for (const [ key, value ] of objectData.current) {
const { y, height, dropType } = value;
let { x, width } = value;
@@ -527,11 +502,12 @@ const DragProvider = observer(class DragProvider extends React.Component
};
if ((ex >= x) && (ex <= x + width) && (ey >= y) && (ey <= y + height)) {
- this.setHoverData(value);
+ setHoverData(value);
break;
};
};
+ const hd = hoverData.current;
const dropType = String(data.droptype) || '';
const rootId = String(data.rootid) || '';
const ids = data.ids || [];
@@ -552,24 +528,24 @@ const DragProvider = observer(class DragProvider extends React.Component
let col1 = 0;
let col2 = 0;
- if (this.hoverData) {
- this.canDrop = true;
+ if (hd) {
+ canDrop.current = true;
if (!isFileDrag && (dropType == I.DropType.Block)) {
- this.canDrop = this.checkParentIds(ids, this.hoverData.id);
+ canDrop.current = checkParentIds(ids, hd.id);
};
const initVars = () => {
- x = this.hoverData.x;
- y = this.hoverData.y;
- width = this.hoverData.width;
- height = this.hoverData.height;
- isTargetTop = this.hoverData.isTargetTop;
- isTargetBot = this.hoverData.isTargetBot;
- isTargetCol = this.hoverData.isTargetCol;
- isEmptyToggle = this.hoverData.isEmptyToggle;
-
- obj = $(this.hoverData.obj);
+ x = hd.x;
+ y = hd.y;
+ width = hd.width;
+ height = hd.height;
+ isTargetTop = hd.isTargetTop;
+ isTargetBot = hd.isTargetBot;
+ isTargetCol = hd.isTargetCol;
+ isEmptyToggle = hd.isEmptyToggle;
+
+ obj = $(hd.obj);
type = obj.attr('data-type');
style = Number(obj.attr('data-style')) || 0;
canDropMiddle = Number(obj.attr('data-drop-middle')) || 0;
@@ -582,47 +558,47 @@ const DragProvider = observer(class DragProvider extends React.Component
initVars();
if (ex <= col1) {
- this.setPosition(I.BlockPosition.Left);
+ setPosition(I.BlockPosition.Left);
} else
if ((ex > col1) && (ex <= col2)) {
if (ey <= y + height * 0.3) {
- this.setPosition(I.BlockPosition.Top);
+ setPosition(I.BlockPosition.Top);
} else
if (ey >= y + height * 0.7) {
- this.setPosition(I.BlockPosition.Bottom);
+ setPosition(I.BlockPosition.Bottom);
} else {
- this.setPosition(I.BlockPosition.InnerFirst);
+ setPosition(I.BlockPosition.InnerFirst);
};
} else
if (ex > col2) {
- this.setPosition(I.BlockPosition.Right);
+ setPosition(I.BlockPosition.Right);
};
- if (this.position == I.BlockPosition.Bottom) {
- const targetBot = this.objectData.get(this.hoverData.cacheKey + '-bot');
+ if (position.current == I.BlockPosition.Bottom) {
+ const targetBot = objectData.current.get(hd + '-bot');
if (targetBot) {
- this.setHoverData(targetBot);
+ setHoverData(targetBot);
initVars();
};
};
// canDropMiddle flag for restricted objects
- if ((this.position == I.BlockPosition.InnerFirst) && !canDropMiddle) {
- this.recalcPositionY(ey, y, height);
+ if ((position.current == I.BlockPosition.InnerFirst) && !canDropMiddle) {
+ recalcPositionY(ey, y, height);
};
// Recalc position if dataTransfer items are dragged
- if (isItemDrag && (this.position != I.BlockPosition.None)) {
- this.recalcPositionY(ey, y, height);
+ if (isItemDrag && (position.current != I.BlockPosition.None)) {
+ recalcPositionY(ey, y, height);
};
// You can drop vertically on Layout.Row
if ((type == I.BlockType.Layout) && (style == I.LayoutStyle.Row)) {
if (isTargetTop) {
- this.setPosition(I.BlockPosition.Top);
+ setPosition(I.BlockPosition.Top);
};
if (isTargetBot) {
- this.setPosition(I.BlockPosition.Bottom);
+ setPosition(I.BlockPosition.Bottom);
};
};
@@ -636,92 +612,82 @@ const DragProvider = observer(class DragProvider extends React.Component
I.TextStyle.Callout,
I.TextStyle.Quote,
].includes(style) &&
- (this.position == I.BlockPosition.Bottom)
+ (position.current == I.BlockPosition.Bottom)
) {
- this.setPosition(I.BlockPosition.None);
+ setPosition(I.BlockPosition.None);
};
- if (this.position != I.BlockPosition.None) {
+ if (position.current != I.BlockPosition.None) {
// You can only drop inside of menu items
- if (this.hoverData.dropType == I.DropType.Menu) {
- this.setPosition(I.BlockPosition.InnerFirst);
+ if (hd.dropType == I.DropType.Menu) {
+ setPosition(I.BlockPosition.InnerFirst);
- if (rootId == this.hoverData.targetContextId) {
- this.setPosition(I.BlockPosition.None);
+ if (rootId == hd.targetContextId) {
+ setPosition(I.BlockPosition.None);
};
};
- if (isTargetTop || (this.hoverData.id == 'blockLast')) {
- this.setPosition(I.BlockPosition.Top);
+ if (isTargetTop || (hd.id == 'blockLast')) {
+ setPosition(I.BlockPosition.Top);
};
if (isTargetBot || isTargetCol) {
- this.setPosition(I.BlockPosition.Bottom);
+ setPosition(I.BlockPosition.Bottom);
};
if (isEmptyToggle) {
- this.setPosition(I.BlockPosition.InnerFirst);
+ setPosition(I.BlockPosition.InnerFirst);
};
};
- if ((dropType == I.DropType.Record) && (this.hoverData.dropType == I.DropType.Record) && !canDropMiddle) {
- isReversed ? this.recalcPositionX(ex, x, width) : this.recalcPositionY(ey, y, height);
+ if ((dropType == I.DropType.Record) && (hd.dropType == I.DropType.Record) && !canDropMiddle) {
+ isReversed ? recalcPositionX(ex, x, width) : recalcPositionY(ey, y, height);
};
- if (this.hoverData.dropType == I.DropType.Widget) {
- this.recalcPositionY(ey, y, height);
+ if (hd.dropType == I.DropType.Widget) {
+ recalcPositionY(ey, y, height);
if (isTargetTop) {
- this.setPosition(I.BlockPosition.Top);
+ setPosition(I.BlockPosition.Top);
};
if (isTargetBot) {
- this.setPosition(I.BlockPosition.Bottom);
+ setPosition(I.BlockPosition.Bottom);
};
};
};
- if (this.frame) {
- raf.cancel(this.frame);
+ if (frame.current) {
+ raf.cancel(frame.current);
};
- this.frame = raf(() => {
- this.clearStyle();
- if ((this.position != I.BlockPosition.None) && this.canDrop && this.hoverData) {
- obj.addClass('isOver ' + this.getDirectionClass(this.position));
+ frame.current = raf(() => {
+ clearStyle();
+ if ((position.current != I.BlockPosition.None) && canDrop.current && hd) {
+ obj.addClass(`isOver ${getDirectionClass(position.current)}`);
};
});
};
- recalcPositionY = (ey: number, y: number, height: number) => {
- if (ey <= y + height * 0.5) {
- this.setPosition(I.BlockPosition.Top);
- } else
- if (ey >= y + height * 0.5) {
- this.setPosition(I.BlockPosition.Bottom);
- };
+ const recalcPositionY = (ey: number, y: number, height: number) => {
+ setPosition(ey <= y + height * 0.5 ? I.BlockPosition.Top : I.BlockPosition.Bottom);
};
- recalcPositionX = (ex: number, x: number, width: number) => {
- if (ex <= x + width * 0.5) {
- this.setPosition(I.BlockPosition.Top);
- } else
- if (ex >= x + width * 0.5) {
- this.setPosition(I.BlockPosition.Bottom);
- };
+ const recalcPositionX = (ex: number, x: number, width: number) => {
+ setPosition(ex <= x + width * 0.5 ? I.BlockPosition.Top : I.BlockPosition.Bottom);
};
- setClass (ids: string[]) {
+ const setClass = (ids: string[]) => {
$('.block.isDragging').removeClass('isDragging');
for (const id of ids) {
$('#block-' + id).addClass('isDragging');
};
};
- checkParentIds (ids: string[], id: string): boolean {
+ const checkParentIds = (ids: string[], id: string): boolean => {
const parentIds: string[] = [];
- this.getParentIds(id, parentIds);
+ getParentIds(id, parentIds);
for (const dropId of ids) {
if ((dropId == id) || (parentIds.length && parentIds.includes(dropId))) {
@@ -731,7 +697,7 @@ const DragProvider = observer(class DragProvider extends React.Component
return true;
};
- getParentIds (blockId: string, parentIds: string[]) {
+ const getParentIds = (blockId: string, parentIds: string[]) => {
const rootId = keyboard.getRootId();
const item = S.Block.getMapElement(rootId, blockId);
@@ -740,10 +706,10 @@ const DragProvider = observer(class DragProvider extends React.Component
};
parentIds.push(item.parentId);
- this.getParentIds(item.parentId, parentIds);
+ getParentIds(item.parentId, parentIds);
};
- getDirectionClass (dir: I.BlockPosition) {
+ const getDirectionClass = (dir: I.BlockPosition) => {
let c = '';
switch (dir) {
case I.BlockPosition.None: c = ''; break;
@@ -757,35 +723,53 @@ const DragProvider = observer(class DragProvider extends React.Component
return c;
};
- clearStyle () {
+ const clearStyle = () => {
$('.dropTarget.isOver').removeClass('isOver top bottom left right middle');
};
- clearState () {
- if (this.hoverData) {
- this.setHoverData(null);
+ const clearState = () => {
+ if (hoverData.current) {
+ setHoverData(null);
};
- this.clearStyle();
- this.setPosition(I.BlockPosition.None);
+ clearStyle();
+ setPosition(I.BlockPosition.None);
- this.init = false;
- this.objects = null;
- this.objectData.clear();
+ isInitialised.current = false;
+ objects.current = null;
+ objectData.current.clear();
};
- setHoverData (v: any) {
- this.hoverData = v;
+ const setHoverData = (v: any) => {
+ hoverData.current = v;
};
- setPosition (v: I.BlockPosition) {
- this.position = v;
+ const setPosition = (v: I.BlockPosition) => {
+ position.current = v;
};
- unbind () {
+ const unbind = () => {
$(window).off('drag.drag dragend.drag');
};
-});
-
-export default DragProvider;
+ useImperativeHandle(ref, () => ({
+ onDragStart,
+ onScroll,
+ }));
+
+ return (
+
+
+ {children}
+
+ );
+
+}));
+
+export default DragProvider;
\ No newline at end of file
diff --git a/src/ts/component/drag/target.tsx b/src/ts/component/drag/target.tsx
index 6403b09c22..d15e327800 100644
--- a/src/ts/component/drag/target.tsx
+++ b/src/ts/component/drag/target.tsx
@@ -1,4 +1,4 @@
-import * as React from 'react';
+import React, { FC } from 'react';
import { I, U } from 'Lib';
interface Props {
@@ -20,75 +20,62 @@ interface Props {
onContextMenu?(e: any): void;
};
-class DropTarget extends React.Component {
+const DropTarget: FC = ({
+ id = '',
+ rootId = '',
+ cacheKey = '',
+ targetContextId = '',
+ dropType = I.DropType.None,
+ type,
+ style = 0,
+ className = '',
+ canDropMiddle = false,
+ isTargetTop = false,
+ isTargetBottom = false,
+ isTargetColumn = false,
+ isReversed = false,
+ children,
+ onClick,
+ onContextMenu,
+}) => {
- public static defaultProps: Props = {
- id: '',
- rootId: '',
- dropType: I.DropType.None,
- };
+ const key = [ dropType, cacheKey || id ];
+ const cn = [ 'dropTarget', 'isDroppable', `root-${rootId}`, `drop-target-${id}`, className ];
- constructor (props: Props) {
- super(props);
-
- this.onClick = this.onClick.bind(this);
+ if (isTargetTop) {
+ cn.push('targetTop');
+ key.push('top');
};
-
- render () {
- const {
- id, rootId, cacheKey, targetContextId, dropType, type, style, children, className, canDropMiddle, isTargetTop, isTargetBottom, isTargetColumn,
- isReversed, onContextMenu,
- } = this.props;
- const key = [ dropType, cacheKey || id ];
- const cn = [ 'dropTarget', 'isDroppable', 'root-' + rootId, 'drop-target-' + id ];
-
- if (className) {
- cn.push(className);
- };
- if (isTargetTop) {
- cn.push('targetTop');
- key.push('top');
- };
- if (isTargetBottom) {
- cn.push('targetBot');
- key.push('bot');
- };
- if (isTargetColumn) {
- cn.push('targetCol');
- key.push('col');
- };
-
- return (
-
- {children}
-
- );
+ if (isTargetBottom) {
+ cn.push('targetBot');
+ key.push('bot');
};
-
- onClick (e: any) {
- const { onClick } = this.props;
-
- if (onClick) {
- onClick(e);
- };
+ if (isTargetColumn) {
+ cn.push('targetCol');
+ key.push('col');
};
-
+
+ return (
+
+ {children}
+
+ );
};
export default DropTarget;
\ No newline at end of file
diff --git a/src/ts/component/editor/page.tsx b/src/ts/component/editor/page.tsx
index 907ff170ad..1203606422 100644
--- a/src/ts/component/editor/page.tsx
+++ b/src/ts/component/editor/page.tsx
@@ -3,9 +3,8 @@ import $ from 'jquery';
import raf from 'raf';
import { observer } from 'mobx-react';
import { throttle } from 'lodash';
-import { Icon, Loader, Deleted, DropTarget } from 'Component';
+import { Icon, Loader, Deleted, DropTarget, EditorControls } from 'Component';
import { I, C, S, U, J, Key, Preview, Mark, focus, keyboard, Storage, Action, translate, analytics, Renderer, sidebar } from 'Lib';
-import Controls from 'Component/page/elements/head/controls';
import PageHeadEditor from 'Component/page/elements/head/editor';
import Children from 'Component/page/elements/children';
@@ -98,7 +97,7 @@ const EditorPage = observer(class EditorPage extends React.Component
- this.refControls = ref}
key="editorControls"
{...this.props}
@@ -188,8 +187,6 @@ const EditorPage = observer(class EditorPage extends React.Component it.isFocusable());
+ } else {
+ block = S.Block.getLeaf(rootId, J.Constant.blockId.title);
+ };
+
if (block && block.getLength()) {
block = null;
};
@@ -376,7 +382,7 @@ const EditorPage = observer(class EditorPage extends React.Component `${it}.${ns}`).join(' '));
container.off(`scroll.${ns}`);
- Renderer.remove('commandEditor');
+ Renderer.remove(`commandEditor`);
};
rebind () {
@@ -416,7 +422,7 @@ const EditorPage = observer(class EditorPage extends React.Component this.resizePage());
container.on(`scroll.${ns}`, e => this.onScroll());
- Renderer.on('commandEditor', (e: any, cmd: string, arg: any) => this.onCommand(cmd, arg));
+ Renderer.on(`commandEditor`, (e: any, cmd: string, arg: any) => this.onCommand(cmd, arg));
};
onMouseMove (e: any) {
@@ -453,7 +459,7 @@ const EditorPage = observer(class EditorPage extends React.Component {
- if (!menuOpen) {
- selection.clear();
- };
-
- ret = true;
- });
-
+ if (idsWithChildren.length) {
// Mark-up
let type = null;
@@ -656,18 +655,28 @@ const EditorPage = observer(class EditorPage extends React.Component {
- C.BlockTextListSetMark(rootId, ids, { type: newType, param, range: { from: 0, to: 0 } }, () => {
- analytics.event('ChangeTextStyle', { type: newType, count: ids.length });
+ C.BlockTextListSetMark(rootId, idsWithChildren, { type: newType, param, range: { from: 0, to: 0 } }, () => {
+ analytics.event('ChangeTextStyle', { type: newType, count: idsWithChildren.length });
});
- }
- }
+ },
+ },
});
} else {
- C.BlockTextListSetMark(rootId, ids, { type, param, range: { from: 0, to: 0 } }, () => {
- analytics.event('ChangeTextStyle', { type, count: ids.length });
+ C.BlockTextListSetMark(rootId, idsWithChildren, { type, param, range: { from: 0, to: 0 } }, () => {
+ analytics.event('ChangeTextStyle', { type, count: idsWithChildren.length });
});
};
};
+ };
+
+ if (ids.length) {
+ keyboard.shortcut('escape', e, () => {
+ if (!menuOpen) {
+ selection.clear();
+ };
+
+ ret = true;
+ });
// Duplicate
keyboard.shortcut(`${cmd}+d`, e, () => {
@@ -1429,12 +1438,13 @@ const EditorPage = observer(class EditorPage extends React.Component {
-
- render () {
- return ;
- };
+const FooterAuthDisclaimer = forwardRef<{}, I.FooterComponent>(() => {
-};
+ return (
+
+ );
-export default FooterAuthDisclaimer;
+});
+
+export default FooterAuthDisclaimer;
\ No newline at end of file
diff --git a/src/ts/component/footer/auth/index.tsx b/src/ts/component/footer/auth/index.tsx
index ece7c03a4f..78314f7efa 100644
--- a/src/ts/component/footer/auth/index.tsx
+++ b/src/ts/component/footer/auth/index.tsx
@@ -1,12 +1,12 @@
-import * as React from 'react';
+import React, { forwardRef } from 'react';
import { I, U } from 'Lib';
-class FooterAuthIndex extends React.Component {
-
- render () {
- return {U.Date.date('Y', U.Date.now())}, Anytype ;
- };
+const FooterAuthIndex = forwardRef<{}, I.FooterComponent>(() => {
-};
+ return (
+ {U.Date.date('Y', U.Date.now())}, Anytype
+ );
+
+});
export default FooterAuthIndex;
\ No newline at end of file
diff --git a/src/ts/component/footer/index.tsx b/src/ts/component/footer/index.tsx
index e501e9d63c..48b4872ec3 100644
--- a/src/ts/component/footer/index.tsx
+++ b/src/ts/component/footer/index.tsx
@@ -1,5 +1,5 @@
-import * as React from 'react';
-import { I, S, sidebar } from 'Lib';
+import React, { forwardRef, useRef } from 'react';
+import { I, S } from 'Lib';
import FooterAuthIndex from './auth';
import FooterAuthDisclaimer from './auth/disclaimer';
@@ -16,42 +16,14 @@ const Components = {
mainObject: FooterMainObject,
};
-class Footer extends React.Component {
-
- refChild: any = null;
-
- constructor (props: Props) {
- super(props);
-
- this.onHelp = this.onHelp.bind(this);
- };
+const Footer = forwardRef<{}, Props>((props, ref) => {
- render () {
- const { component, className } = this.props;
- const Component = Components[component] || null;
- const cn = [ 'footer', component, className ];
-
- return (
-
- );
- };
+ const childRef = useRef(null);
+ const { component, className = '' } = props;
+ const Component = Components[component] || null;
+ const cn = [ 'footer', component, className ];
- componentDidMount () {
- sidebar.resizePage(null, false);
- };
-
- componentDidUpdate () {
- sidebar.resizePage(null, false);
- this.refChild.forceUpdate();
- };
-
- onHelp () {
+ const onHelp = () => {
S.Menu.open('help', {
element: '#footer #button-help',
classNameWrap: 'fixed',
@@ -61,6 +33,18 @@ class Footer extends React.Component {
});
};
-};
+ return (
+
+ );
+
+});
-export default Footer;
+export default Footer;
\ No newline at end of file
diff --git a/src/ts/component/footer/main/object.tsx b/src/ts/component/footer/main/object.tsx
index 88da5db8b2..454d6bfd33 100644
--- a/src/ts/component/footer/main/object.tsx
+++ b/src/ts/component/footer/main/object.tsx
@@ -1,61 +1,61 @@
-import * as React from 'react';
+import React, { forwardRef } from 'react';
import { observer } from 'mobx-react';
import { PieChart } from 'react-minimal-pie-chart';
import { Icon } from 'Component';
import { I, J, S, Preview, translate } from 'Lib';
-const FooterMainEdit = observer(class FooterMainEdit extends React.Component {
-
- render () {
- const { onHelp } = this.props;
- const { show } = S.Progress;
- const theme = S.Common.getThemeClass();
- const current = S.Progress.getCurrent();
- const total = S.Progress.getTotal();
- const percent = Math.round((current / total) * 100);
- const color = J.Theme[theme].progress;
+const FooterMainObject = observer(forwardRef<{}, I.FooterComponent>((props, ref) => {
- return (
-
- {total ? (
-
- ) : ''}
-
-
- );
- };
+ const { onHelp } = props;
+ const { show } = S.Progress;
+ const theme = S.Common.getThemeClass();
+ const skipState = [ I.ProgressState.Done, I.ProgressState.Canceled ];
+ const skipType = [ I.ProgressType.Migrate ];
+ const list = S.Progress.getList(it => !skipType.includes(it.type) && !skipState.includes(it.state));
+ const percent = S.Progress.getPercent(list);
+ const color = J.Theme[theme].progress;
- onTooltipShow (e: any, text: string, caption?: string) {
+ const onTooltipShow = (e: any, text: string, caption?: string) => {
const t = Preview.tooltipCaption(text, caption);
if (t) {
Preview.tooltipShow({ text: t, element: $(e.currentTarget), typeY: I.MenuDirection.Top });
};
};
-});
+ return (
+
+ {percent ? (
+
+ ) : ''}
+
+
+
+ );
+
+}));
-export default FooterMainEdit;
\ No newline at end of file
+export default FooterMainObject;
\ No newline at end of file
diff --git a/src/ts/component/form/drag/horizontal.tsx b/src/ts/component/form/drag/horizontal.tsx
index 0c555cb04b..1a7a2ec691 100644
--- a/src/ts/component/form/drag/horizontal.tsx
+++ b/src/ts/component/form/drag/horizontal.tsx
@@ -1,10 +1,10 @@
-import * as React from 'react';
+import React, { forwardRef, useRef, useImperativeHandle, useEffect } from 'react';
import $ from 'jquery';
interface Props {
id?: string;
- className: string;
- value: number;
+ className?: string;
+ value?: number;
snaps?: number[];
strictSnap?: boolean;
onStart?(e: any, v: number): void;
@@ -12,172 +12,160 @@ interface Props {
onEnd?(e: any, v: number): void;
};
-const SNAP = 0.025;
+interface DragHorizontalRefProps {
+ getValue(): number;
+ setValue(v: number): void;
+ resize(): void;
+};
-class DragHorizontal extends React.Component {
+const SNAP = 0.025;
- public static defaultProps = {
- value: 0,
- min: 0,
- max: 1,
- className: '',
- };
-
- value = null;
- ox = 0;
- nw = 0;
- iw = 0;
- node: any = null;
- back: any = null;
- fill: any = null;
- icon: any = null;
-
- constructor (props: Props) {
- super(props);
-
- this.start = this.start.bind(this);
+const DragHorizontal = forwardRef(({
+ id = '',
+ className = '',
+ value: initalValue = 0,
+ snaps = [],
+ strictSnap = false,
+ onStart,
+ onMove,
+ onEnd,
+}, ref) => {
+ let value = initalValue;
+
+ const nodeRef = useRef(null);
+ const iconRef = useRef(null);
+ const backRef = useRef(null);
+ const fillRef = useRef(null);
+ const cn = [ 'input-drag-horizontal', className ];
+
+ const checkValue = (v: number): number => {
+ v = Number(v) || 0;
+ v = Math.max(0, v);
+ v = Math.min(1, v);
+ return v;
};
-
- render () {
- const { id, className } = this.props;
- const cn = [ 'input-drag-horizontal' ];
- if (className) {
- cn.push(className);
- };
-
- return (
- this.node = node}
- id={id}
- className={cn.join(' ')}
- onMouseDown={this.start}
- >
-
-
-
-
- );
- };
-
- componentDidMount () {
- const node = $(this.node);
-
- this.back = node.find('#back');
- this.fill = node.find('#fill');
- this.icon = node.find('#icon');
- this.setValue(this.props.value);
+ const maxWidth = (): number => {
+ return $(nodeRef.current).width() - $(iconRef.current).width();
};
- setValue (v: number) {
- this.move(this.checkValue(v) * this.maxWidth());
+ const setValue = (v: number) => {
+ move(checkValue(v) * maxWidth());
};
-
- getValue () {
- return this.checkValue(this.value);
+
+ const getValue = () => {
+ return checkValue(value);
};
- resize () {
- this.setValue(this.value);
+ const resize = () => {
+ setValue(value);
};
- start (e: any) {
+ const start = (e: any) => {
e.preventDefault();
e.stopPropagation();
- const { onStart, onMove, onEnd } = this.props;
const win = $(window);
- const node = $(this.node);
- const iw = this.icon.width();
+ const node = $(nodeRef.current);
+ const icon = $(iconRef.current);
+ const iw = icon.width();
const ox = node.offset().left;
- this.move(e.pageX - ox - iw / 2);
+ move(e.pageX - ox - iw / 2);
node.addClass('isDragging');
if (onStart) {
- onStart(e, this.value);
+ onStart(e, value);
};
win.off('mousemove.drag touchmove.drag').on('mousemove.drag touchmove.drag', (e: any) => {
- this.move(e.pageX - ox - iw / 2);
+ move(e.pageX - ox - iw / 2);
if (onMove) {
- onMove(e, this.value);
+ onMove(e, value);
};
});
win.off('mouseup.drag touchend.drag').on('mouseup.drag touchend.drag', (e: any) => {
- this.end(e);
+ end(e);
if (onEnd) {
- onEnd(e, this.value);
+ onEnd(e, value);
};
});
};
-
- move (x: number) {
- const { strictSnap } = this.props;
- const snaps = this.props.snaps || [];
- const node = $(this.node);
+
+ const move = (x: number) => {
+ const node = $(nodeRef.current);
+ const icon = $(iconRef.current);
+ const back = $(backRef.current);
+ const fill = $(fillRef.current);
const nw = node.width();
- const iw = this.icon.width() / 2;
- const ib = parseInt(this.icon.css('border-width'));
- const mw = this.maxWidth();
+ const iw = icon.width() / 2;
+ const ib = parseInt(icon.css('border-width'));
+ const mw = maxWidth();
x = Math.max(0, x);
x = Math.min(mw, x);
- this.value = this.checkValue(x / mw);
+ value = checkValue(x / mw);
// Snap
- if (strictSnap && snaps.length && (this.value < snaps[0] / 2)) {
- this.value = 0;
+ if (strictSnap && snaps.length && (value < snaps[0] / 2)) {
+ value = 0;
} else {
const step = 1 / snaps.length;
for (const snap of snaps) {
const d = strictSnap ? step / 2 : SNAP;
- if ((this.value >= snap - d) && (this.value < snap + d)) {
- this.value = snap;
+ if ((value >= snap - d) && (value < snap + d)) {
+ value = snap;
break;
};
};
};
- x = this.value * mw;
+ x = value * mw;
const w = Math.min(nw, x + iw);
- this.icon.css({ left: x });
- this.back.css({ left: (w + iw + ib), width: (nw - w - iw - ib) });
- this.fill.css({ width: (w - ib) });
+ icon.css({ left: x });
+ back.css({ left: (w + iw + ib), width: (nw - w - iw - ib) });
+ fill.css({ width: (w - ib) });
};
-
- end (e) {
+
+ const end = (e) => {
e.preventDefault();
e.stopPropagation();
- const win = $(window);
- const node = $(this.node);
-
- win.off('mousemove.drag touchmove.drag mouseup.drag touchend.drag');
- node.removeClass('isDragging');
+ $(window).off('mousemove.drag touchmove.drag mouseup.drag touchend.drag');
+ $(nodeRef.current).removeClass('isDragging');
};
- maxWidth () {
- const node = $(this.node);
- return node.width() - this.icon.width();
- };
-
- checkValue (v: number): number {
- v = Number(v) || 0;
- v = Math.max(0, v);
- v = Math.min(1, v);
- return v;
- };
-
-};
+ useEffect(() => setValue(initalValue), []);
+
+ useImperativeHandle(ref, () => ({
+ getValue,
+ setValue,
+ resize,
+ }));
+
+ return (
+ e.stopPropagation()}
+ >
+
+
+
+
+ );
+
+});
export default DragHorizontal;
\ No newline at end of file
diff --git a/src/ts/component/form/drag/vertical.tsx b/src/ts/component/form/drag/vertical.tsx
index c27220ab73..23f1e88161 100644
--- a/src/ts/component/form/drag/vertical.tsx
+++ b/src/ts/component/form/drag/vertical.tsx
@@ -1,4 +1,5 @@
-import React, { useRef, useImperativeHandle, forwardRef, ChangeEvent, MouseEvent } from 'react';
+import React, { useRef, useImperativeHandle, forwardRef, ChangeEvent, MouseEvent, useEffect } from 'react';
+import $ from 'jquery';
import { Input } from 'Component';
interface Props {
@@ -13,31 +14,53 @@ interface Props {
onMouseEnter? (e: MouseEvent): void;
};
-const DragVertical = forwardRef(({
+interface DragVerticalRefProps {
+ getValue: () => number;
+ setValue: (v: number) => void;
+};
+
+const DragVertical = forwardRef(({
id,
className = '',
- value,
+ value: initialValue = 0,
min = 0,
max = 1,
step = 0.01,
onChange,
onMouseLeave,
onMouseEnter,
-}, forwardedRef) => {
+}, ref) => {
const inputRef = useRef(null);
+ const trackRef = useRef(null);
const divRef = useRef(null);
- useImperativeHandle(forwardedRef, () => divRef.current);
+ const setHeight = (v: number) => {
+ $(trackRef.current).css({ height: `${Math.round(v * 72)}px` });
+ };
const handleChange = (e: ChangeEvent, value: string) => {
+ const v = 1 - Number(value) || 0;
+
e.preventDefault();
e.stopPropagation();
+ setHeight(v);
+
if (onChange) {
- onChange(e, 1 - Number(value) || 0);
+ onChange(e, v);
};
};
+ useImperativeHandle(ref, () => ({
+ getValue: () => inputRef.current?.getValue(),
+ setValue: (v: number) => inputRef.current?.setValue(v),
+ }));
+
+ useEffect(() => {
+ setHeight(initialValue);
+ inputRef.current.setValue(initialValue);
+ }, []);
+
return (
(({
type="range"
className="vertical-range"
ref={inputRef}
- value={String(value)}
+ value={String(initialValue)}
min={min}
max={max}
step={step}
@@ -61,7 +84,10 @@ const DragVertical = forwardRef (({
}}
/>
-
+
);
});
diff --git a/src/ts/component/form/editable.tsx b/src/ts/component/form/editable.tsx
index c8c029cf70..51b88efff0 100644
--- a/src/ts/component/form/editable.tsx
+++ b/src/ts/component/form/editable.tsx
@@ -1,5 +1,4 @@
-import * as React from 'react';
-import raf from 'raf';
+import React, { forwardRef, useRef, useImperativeHandle } from 'react';
import { getRange, setRange } from 'selection-ranges';
import { I, U, keyboard, Mark } from 'Lib';
@@ -26,182 +25,118 @@ interface Props {
onCompositionEnd?: (e: any) => void;
};
-class Editable extends React.Component {
-
- _isMounted = false;
- node: any = null;
- refPlaceholder = null;
- refEditable = null;
-
- constructor (props: Props) {
- super(props);
-
- this.onInput = this.onInput.bind(this);
- this.onKeyDown = this.onKeyDown.bind(this);
- this.onKeyUp = this.onKeyUp.bind(this);
- this.onFocus = this.onFocus.bind(this);
- this.onBlur = this.onBlur.bind(this);
- this.onPaste = this.onPaste.bind(this);
- this.onCompositionStart = this.onCompositionStart.bind(this);
- this.onCompositionEnd = this.onCompositionEnd.bind(this);
- };
-
- render () {
- const { id, classNameWrap, classNameEditor, classNamePlaceholder, readonly, placeholder, spellcheck, onSelect, onMouseDown, onMouseUp, onDragStart } = this.props;
- const cnw = [ 'editableWrap' ];
- const cne = [ 'editable' ];
- const cnp = [ 'placeholder' ];
-
- if (classNameWrap) {
- cnw.push(classNameWrap);
- };
-
- if (classNameEditor) {
- cne.push(classNameEditor);
- };
-
- if (classNamePlaceholder) {
- cnp.push(classNamePlaceholder);
- };
-
- let editor = null;
- if (readonly) {
- cne.push('isReadonly');
-
- editor = (
- this.refEditable = ref}
- className={cne.join(' ')}
- contentEditable={true}
- suppressContentEditableWarning={true}
- spellCheck={false}
- onMouseUp={onSelect}
- />
- );
- } else {
- editor = (
- this.refEditable = ref}
- className={cne.join(' ')}
- contentEditable={true}
- suppressContentEditableWarning={true}
- spellCheck={spellcheck}
- onKeyDown={this.onKeyDown}
- onKeyUp={this.onKeyUp}
- onFocus={this.onFocus}
- onBlur={this.onBlur}
- onSelect={onSelect}
- onPaste={this.onPaste}
- onMouseUp={onMouseUp}
- onInput={this.onInput}
- onDragStart={onDragStart}
- onCompositionStart={this.onCompositionStart}
- onCompositionEnd={this.onCompositionEnd}
- />
- );
- };
-
- return (
- this.node = node}
- className={cnw.join(' ')}
- onMouseDown={onMouseDown}
- >
- {editor}
- this.refPlaceholder = ref}
- >
- {placeholder}
-
-
- );
- };
-
- componentDidMount () {
- this._isMounted = true;
- };
-
- componentWillUnmount () {
- this._isMounted = false;
- };
+interface EditableRefProps {
+ placeholderCheck: () => void;
+ placeholderSet: (v: string) => void;
+ placeholderHide: () => void;
+ placeholderShow: () => void;
+ setValue: (html: string) => void;
+ getTextValue: () => string;
+ getHtmlValue: () => string;
+ getRange: () => I.TextRange;
+ setRange: (range: I.TextRange) => void;
+ getNode: () => JQuery;
+};
- placeholderCheck () {
- this.getTextValue() ? this.placeholderHide() : this.placeholderShow();
+const Editable = forwardRef (({
+ id = '',
+ classNameWrap = '',
+ classNameEditor = '',
+ classNamePlaceholder = '',
+ readonly = false,
+ placeholder = '',
+ spellcheck = false,
+ maxLength,
+ onSelect,
+ onMouseDown,
+ onMouseUp,
+ onDragStart,
+ onPaste,
+ onInput,
+ onKeyDown,
+ onKeyUp,
+ onFocus,
+ onBlur,
+ onCompositionStart,
+ onCompositionEnd,
+}, ref) => {
+
+ const nodeRef = useRef(null);
+ const placeholderRef = useRef(null);
+ const editableRef = useRef(null);
+ const cnw = [ 'editableWrap', classNameWrap ];
+ const cne = [ 'editable', classNameEditor ];
+ const cnp = [ 'placeholder', classNamePlaceholder ];
+
+ const placeholderCheck = () => {
+ getTextValue() ? placeholderHide() : placeholderShow();
};
- placeholderSet (v: string) {
- $(this.refPlaceholder).text(v);
+ const placeholderSet = (v: string) => {
+ $(placeholderRef.current).text(v);
};
- placeholderHide () {
- $(this.refPlaceholder).hide();
+ const placeholderHide = () => {
+ $(placeholderRef.current).hide();
};
- placeholderShow () {
- $(this.refPlaceholder).show();
+ const placeholderShow = () => {
+ $(placeholderRef.current).show();
};
- setValue (html: string) {
- $(this.refEditable).get(0).innerHTML = U.Common.sanitize(html);
+ const setValue = (html: string) => {
+ $(editableRef.current).get(0).innerHTML = U.Common.sanitize(html);
};
- getTextValue (): string {
- const obj = Mark.cleanHtml($(this.refEditable).html());
+ const getTextValue = (): string => {
+ const obj = Mark.cleanHtml($(editableRef.current).html());
return String(obj.get(0).innerText || '');
};
- getHtmlValue () : string {
- return String($(this.refEditable).html() || '');
+ const getHtmlValue = (): string => {
+ return String($(editableRef.current).html() || '');
};
- getRange (): I.TextRange {
- const range = getRange($(this.refEditable).get(0) as Element);
+ const getRangeHandler = (): I.TextRange => {
+ const range = getRange($(editableRef.current).get(0) as Element);
return range ? { from: range.start, to: range.end } : null;
};
- setRange (range: I.TextRange) {
- if (!range || !this._isMounted) {
+ const setRangeHandler = (range: I.TextRange) => {
+ if (!range) {
return;
};
- const el = $(this.refEditable).get(0);
+ const el = $(editableRef.current).get(0);
el.focus({ preventScroll: true });
setRange(el, { start: range.from, end: range.to });
};
- onPaste (e: any) {
- const { onPaste } = this.props;
+ const onPasteHandler = (e: any) => {
+ placeholderCheck();
if (onPaste) {
onPaste(e);
};
};
- onInput (e: any) {
- const { onInput } = this.props;
-
- this.placeholderCheck();
+ const onInputHandler = (e: any) => {
+ placeholderCheck();
if (onInput) {
onInput(e);
};
};
- onKeyDown (e: any): void {
+ const onKeyDownHandler = (e: any): void => {
// Chinese IME is open
if (keyboard.isComposition) {
return;
};
- const { maxLength, onKeyDown } = this.props;
-
if (maxLength) {
- const text = this.getTextValue();
+ const text = getTextValue();
if ((text.length >= maxLength) && !keyboard.isSpecial(e) && !keyboard.withCommand(e)) {
e.preventDefault();
@@ -213,49 +148,118 @@ class Editable extends React.Component {
};
};
- onKeyUp (e: any): void {
+ const onKeyUpHandler = (e: any): void => {
// Chinese IME is open
if (keyboard.isComposition) {
return;
};
- if (this.props.onKeyUp) {
- this.props.onKeyUp(e);
+ if (onKeyUp) {
+ onKeyUp(e);
};
};
- onFocus (e: any) {
+ const onFocusHandler = (e: any) => {
keyboard.setFocus(true);
- if (this.props.onFocus) {
- this.props.onFocus(e);
+ if (onFocus) {
+ onFocus(e);
};
};
- onBlur (e: any) {
+ const onBlurHandler = (e: any) => {
keyboard.setFocus(false);
- if (this.props.onBlur) {
- this.props.onBlur(e);
+ if (onBlur) {
+ onBlur(e);
};
};
- onCompositionStart (e: any) {
+ const onCompositionStartHandler = (e: any) => {
keyboard.setComposition(true);
- if (this.props.onCompositionStart) {
- this.props.onCompositionStart(e);
+ if (onCompositionStart) {
+ onCompositionStart(e);
};
};
- onCompositionEnd (e: any) {
+ const onCompositionEndHandler = (e: any) => {
keyboard.setComposition(false);
- if (this.props.onCompositionEnd) {
- this.props.onCompositionEnd(e);
+ if (onCompositionEnd) {
+ onCompositionEnd(e);
};
};
-};
+ let editor = null;
+ if (readonly) {
+ cne.push('isReadonly');
+
+ editor = (
+
+ );
+ } else {
+ editor = (
+
+ );
+ };
+
+ useImperativeHandle(ref, () => ({
+ placeholderCheck,
+ placeholderSet,
+ placeholderHide,
+ placeholderShow,
+ setValue,
+ getTextValue,
+ getHtmlValue,
+ getRange: getRangeHandler,
+ setRange: setRangeHandler,
+ getNode: () => $(nodeRef.current),
+ }), []);
+
+ return (
+
+ {editor}
+
+ {placeholder}
+
+
+ );
+
+});
export default Editable;
diff --git a/src/ts/component/form/emailCollection.tsx b/src/ts/component/form/emailCollection.tsx
new file mode 100644
index 0000000000..6eeef03651
--- /dev/null
+++ b/src/ts/component/form/emailCollection.tsx
@@ -0,0 +1,292 @@
+import React, { FC, useState, useRef, useEffect } from 'react';
+import { Label, Checkbox, Input, Button, Icon, Pin } from 'Component';
+import { analytics, C, J, S, translate, U } from 'Lib';
+
+interface Props {
+ onStepChange: () => void;
+ onComplete: () => void;
+};
+
+const EmailCollection: FC = ({
+ onStepChange,
+ onComplete,
+}) => {
+
+ const checkboxTipsRef = useRef(null);
+ const checkboxNewsRef = useRef(null);
+ const emailRef = useRef(null);
+ const buttonRef = useRef(null);
+ const codeRef = useRef(null);
+ const interval = useRef(0);
+ const timeout = useRef(0);
+ const [ step, setStep ] = useState(0);
+ const [ status, setStatus ] = useState('');
+ const [ statusText, setStatusText ] = useState('');
+ const [ countdown, setCountdown ] = useState(0);
+ const [ email, setEmail ] = useState('');
+ const [ subscribeNews, setSubscribeNews ] = useState(false);
+ const [ subscribeTips, setSubscribeTips ] = useState(false);
+ const [ pinDisabled, setPinDisabled ] = useState(false);
+ const [ showCodeSent, setShowCodeSent ] = useState(false);
+
+ let content = null;
+ let descriptionSuffix = 'Description';
+
+ const onCheck = (ref, type: string) => {
+ if (!ref.current) {
+ return;
+ };
+
+ const val = ref.current.getValue();
+
+ ref.current.toggle();
+
+ if (!val) {
+ analytics.event('ClickEmailCollection', { route: 'OnboardingTooltip', step: 1, type });
+ };
+ };
+
+ const setStepHandler = (newStep: number) => {
+ if (step == newStep) {
+ return;
+ };
+
+ setStep(newStep);
+ onStepChange();
+ analytics.event('EmailCollection', { route: 'OnboardingTooltip', step: newStep });
+ };
+
+ const setStatusAndText = (status: string, statusText: string) => {
+ setStatus(status);
+ setStatusText(statusText);
+ };
+
+ const clearStatus = () => {
+ setStatusAndText('', '');
+ };
+
+ const validateEmail = () => {
+ clearStatus();
+
+ window.clearTimeout(timeout.current);
+ timeout.current = window.setTimeout(() => {
+ const value = emailRef.current?.getValue();
+ const isValid = U.Common.checkEmail(value);
+
+ if (value && !isValid) {
+ setStatusAndText('error', translate('errorIncorrectEmail'));
+ };
+
+ buttonRef.current?.setDisabled(!isValid);
+ }, J.Constant.delay.keyboard);
+ };
+
+ const onSubmitEmail = (e: any) => {
+ e.preventDefault();
+
+ if (!buttonRef.current || !emailRef.current) {
+ return;
+ };
+
+ if (buttonRef.current.isDisabled()) {
+ return;
+ };
+
+ analytics.event('ClickEmailCollection', { route: 'OnboardingTooltip', step: 1, type: 'SignUp' });
+
+ setEmail(emailRef.current?.getValue());
+ setSubscribeNews(checkboxNewsRef.current?.getValue());
+ setSubscribeTips(checkboxTipsRef.current?.getValue());
+ };
+
+ const verifyEmail = () => {
+ buttonRef.current?.setLoading(true);
+
+ C.MembershipGetVerificationEmail(email, subscribeNews, subscribeTips, true, (message) => {
+ buttonRef.current?.setLoading(false);
+
+ if (message.error.code) {
+ setStatusAndText('error', message.error.description);
+ return;
+ };
+
+ setStepHandler(1);
+ startCountdown(60);
+ });
+ };
+
+ const onConfirmEmailCode = () => {
+ const code = codeRef.current?.getValue();
+
+ setPinDisabled(true);
+
+ C.MembershipVerifyEmailCode(code, (message) => {
+ if (message.error.code) {
+ setStatusAndText('error', message.error.description);
+ setPinDisabled(false);
+ codeRef.current?.reset();
+ return;
+ };
+
+ setStepHandler(2);
+ });
+ };
+
+ const onResend = (e: any) => {
+ e.preventDefault();
+
+ if (countdown) {
+ return;
+ };
+
+ verifyEmail();
+ analytics.event('ClickEmailCollection', { route: 'OnboardingTooltip', step: 2, type: 'Resend' });
+ };
+
+ const startCountdown = (s: number) => {
+ const { emailConfirmationTime } = S.Common;
+
+ if (!emailConfirmationTime) {
+ S.Common.emailConfirmationTimeSet(U.Date.now());
+ };
+
+ setCountdown(s);
+ setShowCodeSent(true);
+ window.setTimeout(() => setShowCodeSent(false), 2000);
+ };
+
+ const tick = () => {
+ window.clearTimeout(interval.current);
+ interval.current = window.setTimeout(() => setCountdown(countdown => countdown - 1), 1000);
+ };
+
+ const clear = () => {
+ window.clearTimeout(timeout.current);
+ timeout.current = window.setTimeout(() => clearStatus(), 4000);
+ };
+
+ useEffect(() => {
+ buttonRef.current?.setDisabled(true);
+ analytics.event('EmailCollection', { route: 'OnboardingTooltip', step: 1 });
+
+ return () => {
+ window.clearTimeout(timeout.current);
+ window.clearTimeout(interval.current);
+ };
+ });
+
+ useEffect(() => {
+ if (interval.current) {
+ tick();
+ };
+ }, [ showCodeSent ]);
+
+ useEffect(() => {
+ if (status || statusText) {
+ clear();
+ };
+ }, [ status, statusText ]);
+
+ useEffect(() => {
+ if (timeout.current) {
+ clear();
+ };
+ }, [ pinDisabled ]);
+
+ useEffect(() => {
+ if (email) {
+ verifyEmail();
+ };
+ }, [ email, subscribeNews, subscribeTips ]);
+
+ useEffect(() => {
+ if (countdown) {
+ tick();
+ } else {
+ window.clearTimeout(interval.current);
+ S.Common.emailConfirmationTimeSet(0);
+ };
+ }, [ countdown ]);
+
+ switch (step) {
+ case 0: {
+ content = (
+
+ );
+ break;
+ };
+
+ case 1: {
+ content = (
+
+
+
+ {status ? {statusText} : ''}
+
+
+ {showCodeSent ? translate('emailCollectionCodeSent') : translate('popupMembershipResend')}
+ {countdown && !showCodeSent ? U.Common.sprintf(translate('popupMembershipCountdown'), countdown) : ''}
+
+
+ );
+ break;
+ };
+
+ case 2: {
+ descriptionSuffix = 'News';
+ if (subscribeTips) {
+ descriptionSuffix = 'Tips';
+ };
+ if (subscribeTips && subscribeNews) {
+ descriptionSuffix = 'NewsAndTips';
+ };
+
+ content = (
+
+ );
+ break;
+ };
+ };
+
+ return (
+
+
+
+
+ {content}
+
+ );
+};
+
+export default EmailCollection;
\ No newline at end of file
diff --git a/src/ts/component/form/filter.tsx b/src/ts/component/form/filter.tsx
index 122ff11016..b3b617dead 100644
--- a/src/ts/component/form/filter.tsx
+++ b/src/ts/component/form/filter.tsx
@@ -1,4 +1,4 @@
-import * as React from 'react';
+import React, { forwardRef, useImperativeHandle, useEffect, useState, useRef } from 'react';
import $ from 'jquery';
import { Input, Icon } from 'Component';
import { I, keyboard, translate } from 'Lib';
@@ -27,134 +27,81 @@ interface Props {
onIconClick?(e: any): void;
};
-interface State {
- isActive: boolean;
+interface FilterRefProps {
+ focus(): void;
+ blur(): void;
+ setActive(v: boolean): void;
+ setValue(v: string): void;
+ getValue(): string;
+ getRange(): I.TextRange;
+ setRange(range: I.TextRange): void;
};
-class Filter extends React.Component {
-
- public static defaultProps = {
- className: '',
- inputClassName: '',
- tooltipY: I.MenuDirection.Bottom,
+const Filter = forwardRef(({
+ id = '',
+ className = '',
+ inputClassName = '',
+ icon = '',
+ value = '',
+ placeholder = translate('commonFilterClick'),
+ placeholderFocus = '',
+ tooltip = '',
+ tooltipCaption = '',
+ tooltipX = I.MenuDirection.Center,
+ tooltipY = I.MenuDirection.Bottom,
+ focusOnMount = false,
+ onClick,
+ onFocus,
+ onBlur,
+ onKeyDown,
+ onKeyUp,
+ onChange,
+ onSelect,
+ onClear,
+ onIconClick,
+}, ref) => {
+ const nodeRef = useRef(null);
+ const inputRef = useRef(null);
+ const placeholderRef = useRef(null);
+ const [ isFocused, setIsFocused ] = useState(false);
+ const [ isActive, setIsActive ] = useState(false);
+ const cn = [ 'filter', className ];
+
+ if (isFocused) {
+ cn.push('isFocused');
};
- state = {
- isActive: false,
- };
-
- node: any = null;
- isFocused = false;
- placeholder: any = null;
- ref = null;
-
- constructor (props: Props) {
- super(props);
-
- this.onFocus = this.onFocus.bind(this);
- this.onBlur = this.onBlur.bind(this);
- this.onChange = this.onChange.bind(this);
- this.onClear = this.onClear.bind(this);
- this.onKeyDown = this.onKeyDown.bind(this);
- this.onKeyUp = this.onKeyUp.bind(this);
- this.onInput = this.onInput.bind(this);
+ if (isActive) {
+ cn.push('isActive');
};
-
- render () {
- const { isActive } = this.state;
- const { id, value, icon, tooltip, tooltipCaption, tooltipX, tooltipY, placeholder = translate('commonFilterClick'), className, inputClassName, focusOnMount, onKeyDown, onKeyUp, onClick, onIconClick } = this.props;
- const cn = [ 'filter' ];
-
- if (className) {
- cn.push(className);
- };
- if (isActive) {
- cn.push('isActive');
- };
-
- let iconObj = null;
- if (icon) {
- iconObj = (
-
- );
- };
-
- return (
- this.node = node}
- id={id}
- className={cn.join(' ')}
- onClick={onClick}
- >
-
- {iconObj}
-
-
- this.ref = ref}
- id="input"
- className={inputClassName}
- value={value}
- focusOnMount={focusOnMount}
- onFocus={this.onFocus}
- onBlur={this.onBlur}
- onChange={this.onChange}
- onKeyDown={this.onKeyDown}
- onKeyUp={this.onKeyUp}
- onInput={() => this.placeholderCheck()}
- onCompositionStart={() => this.placeholderCheck()}
- onCompositionEnd={() => this.placeholderCheck()}
- />
- {placeholder}
-
-
-
-
-
-
+ let iconObj = null;
+ if (icon) {
+ iconObj = (
+
);
};
- componentDidMount() {
- const node = $(this.node);
-
- this.ref.setValue(this.props.value);
- this.placeholder = node.find('#placeholder');
-
- this.checkButton();
- this.resize();
- };
-
- componentDidUpdate () {
- this.checkButton();
- this.resize();
- };
-
- focus () {
- this.ref.focus();
- this.checkButton();
+ const focus = () => {
+ inputRef.current.focus();
};
- blur () {
- this.ref.blur();
+ const blur = () => {
+ inputRef.current.blur();
};
- onFocus (e: any) {
- const { placeholderFocus, onFocus } = this.props;
-
- this.isFocused = true;
- this.addFocusedClass();
+ const onFocusHandler = (e: any) => {
+ setIsFocused(true);
if (placeholderFocus) {
- this.placeholderSet(placeholderFocus);
+ placeholderSet(placeholderFocus);
};
if (onFocus) {
@@ -162,14 +109,11 @@ class Filter extends React.Component {
};
};
- onBlur (e: any) {
- const { placeholderFocus, placeholder, onBlur } = this.props;
-
- this.isFocused = false;
- this.removeFocusedClass();
+ const onBlurHandler = (e: any) => {
+ setIsFocused(false);
if (placeholderFocus) {
- this.placeholderSet(placeholder);
+ placeholderSet(placeholder);
};
if (onBlur) {
@@ -177,122 +121,161 @@ class Filter extends React.Component {
};
};
- onInput () {
- this.placeholderCheck();
- };
-
- addFocusedClass () {
- this.addClass('isFocused');
- };
-
- removeFocusedClass () {
- this.removeClass('isFocused');
- };
-
- addClass (c: string) {
- $(this.node).addClass(c);
- };
-
- removeClass (c: string) {
- $(this.node).removeClass(c);
- };
-
- setActive (v: boolean) {
- this.setState({ isActive: v });
+ const onSelectHandler = (e: any) => {
+ if (onSelect) {
+ onSelect(e);
+ };
};
- onClear (e: any) {
+ const onClearHandler = (e: any) => {
e.preventDefault();
e.stopPropagation();
- const { onClear } = this.props;
-
- this.ref.setValue('');
- this.ref.focus();
- this.onChange(e, '');
-
+ inputRef.current.setValue('');
+ inputRef.current.focus();
+
+ onChangeHandler(e, '');
if (onClear) {
onClear();
};
};
- onChange (e: any, v: string) {
+ const onChangeHandler = (e: any, v: string) => {
// Chinese IME is open
if (keyboard.isComposition) {
return;
};
- this.checkButton();
+ resize();
- if (this.props.onChange) {
- this.props.onChange(v);
+ if (onChange) {
+ onChange(v);
};
};
- onKeyDown (e: any, v: string): void {
+ const onKeyDownHandler = (e: any, v: string): void => {
// Chinese IME is open
if (keyboard.isComposition) {
return;
};
- if (this.props.onKeyDown) {
- this.props.onKeyDown(e, v);
+ buttonCheck();
+
+ if (onKeyDown) {
+ onKeyDown(e, v);
};
};
- onKeyUp (e: any, v: string): void {
+ const onKeyUpHandler = (e: any, v: string): void => {
// Chinese IME is open
if (keyboard.isComposition) {
return;
};
- if (this.props.onKeyUp) {
- this.props.onKeyUp(e, v);
+ buttonCheck();
+
+ if (onKeyUp) {
+ onKeyUp(e, v);
};
};
- checkButton () {
- $(this.node).toggleClass('active', !!this.getValue());
- this.placeholderCheck();
+ const buttonCheck = () => {
+ $(nodeRef.current).toggleClass('active', Boolean(getValue()));
+ placeholderCheck();
};
- setValue (v: string) {
- this.ref.setValue(v);
- this.checkButton();
+ const getValue = () => {
+ return inputRef.current?.getValue();
};
- getValue () {
- return this.ref.getValue();
+ const getRange = (): I.TextRange => {
+ return inputRef.current.getRange();
};
- getRange () {
- return this.ref.getRange();
+ const setRange = (range: I.TextRange) => {
+ inputRef.current.setRange(range);
};
- setRange (range: I.TextRange) {
- this.ref.setRange(range);
+ const placeholderCheck = () => {
+ getValue() ? placeholderHide() : placeholderShow();
};
- placeholderCheck () {
- this.getValue() ? this.placeholderHide() : this.placeholderShow();
+ const placeholderSet = (v: string) => {
+ $(placeholderRef.current).text(v);
+ };
+
+ const placeholderHide = () => {
+ $(placeholderRef.current).hide();
};
- placeholderSet (v: string) {
- this.placeholder.text(v);
+ const placeholderShow = () => {
+ $(placeholderRef.current).show();
};
-
- placeholderHide () {
- this.placeholder.hide();
+
+ const resize = () => {
+ const ref = $(placeholderRef.current);
+ ref.css({ lineHeight: ref.height() + 'px' });
};
- placeholderShow () {
- this.placeholder.show();
+ const init = () => {
+ buttonCheck();
+ resize();
};
- resize () {
- this.placeholder.css({ lineHeight: this.placeholder.height() + 'px' });
+ useEffect(() => init());
+
+ useImperativeHandle(ref, () => ({
+ focus,
+ blur,
+ setActive: v => setIsActive(v),
+ isFocused: () => isFocused,
+ setValue: (v: string) => inputRef.current.setValue(v),
+ getValue,
+ getRange,
+ setRange,
+ }));
+
+ const val = getValue();
+
+ if (val) {
+ cn.push('active');
};
-};
+ return (
+
+
+ {iconObj}
+
+
+ placeholderCheck()}
+ onCompositionStart={() => placeholderCheck()}
+ onCompositionEnd={() => placeholderCheck()}
+ />
+ {placeholder}
+
+
+
+
+
+
+ );
+});
export default Filter;
\ No newline at end of file
diff --git a/src/ts/component/form/input.tsx b/src/ts/component/form/input.tsx
index e6147b897a..3aaf0658bb 100644
--- a/src/ts/component/form/input.tsx
+++ b/src/ts/component/form/input.tsx
@@ -1,4 +1,6 @@
-import React, { FC, useEffect, useRef, useState, forwardRef, useImperativeHandle } from 'react';
+import React, {
+ useEffect, useRef, useState, forwardRef, useImperativeHandle, ChangeEvent, SyntheticEvent, KeyboardEvent, FormEvent, FocusEvent, ClipboardEvent
+} from 'react';
import $ from 'jquery';
import Inputmask from 'inputmask';
import { I, keyboard } from 'Lib';
@@ -100,84 +102,46 @@ const Input = forwardRef(({
};
const focus = () => {
- inputRef.current?.focus({ preventScroll: true })
+ inputRef.current?.focus({ preventScroll: true });
};
- useEffect(() => {
- if (maskOptions && inputRef.current) {
- new Inputmask(maskOptions.mask, maskOptions).mask($(inputRef.current).get(0));
- };
-
- if (focusOnMount && inputRef.current) {
- focus();
- };
-
- return () => {
- if (isFocused.current) {
- keyboard.setFocus(false);
- keyboard.disableSelection(false);
- };
- };
- }, [ maskOptions, focusOnMount ]);
-
- useImperativeHandle(ref, () => ({
- focus,
- blur: () => inputRef.current?.blur(),
- select: () => inputRef.current?.select(),
- setValue: (v: string) => setValue(String(v || '')),
- getValue: () => String(value || ''),
- setType: (v: string) => setInputType(v),
- setError: (hasError: boolean) => $(inputRef.current).toggleClass('withError', hasError),
- getSelectionRect,
- setPlaceholder: (placeholder: string) => $(inputRef.current).attr({ placeholder }),
- setRange: (range: I.TextRange) => {
- callWithTimeout(() => {
- focus();
- inputRef.current?.setSelectionRange(range.from, range.to);
- });
- },
- getRange: (): I.TextRange | null => rangeRef.current,
- }));
-
const handleEvent = (
handler: ((e: any, value: string) => void) | undefined,
- e: React.SyntheticEvent
+ e: SyntheticEvent
) => {
- let val = null;
- if (e.currentTarget) {
- val = e.currentTarget.value;
- } else
- if (e.target) {
- val = String($(e.target).val());
- };
-
- if (val === null) {
- console.log('[Input Event] No value to handle!');
- return;
- };
- handler?.(e, val);
+ handler?.(e, String($(e.target || e.currentTarget).val() || ''));
};
- const handleChange = (e: React.ChangeEvent) => {
+ const handleChange = (e: ChangeEvent) => {
setValue(e.target.value);
handleEvent(onChange, e);
};
- const handleKeyUp = (e: React.KeyboardEvent) => {
- if ($(inputRef.current).hasClass('disabled')) return;
+ const handleKeyUp = (e: KeyboardEvent) => {
+ if ($(inputRef.current).hasClass('disabled')) {
+ return;
+ };
+
handleEvent(onKeyUp, e);
};
- const handleKeyDown = (e: React.KeyboardEvent) => {
- if ($(inputRef.current).hasClass('disabled')) return;
+ const handleKeyDown = (e: KeyboardEvent) => {
+ if ($(inputRef.current).hasClass('disabled')) {
+ return;
+ };
+
handleEvent(onKeyDown, e);
};
- const handleInput = (e: React.FormEvent) => {
+ const handleInput = (e: FormEvent) => {
handleEvent(onInput, e);
};
- const handleFocus = (e: React.FocusEvent) => {
+ const handleFocus = (e: FocusEvent) => {
+ if (readonly) {
+ return;
+ };
+
isFocused.current = true;
addClass('isFocused');
keyboard.setFocus(true);
@@ -185,7 +149,11 @@ const Input = forwardRef(({
handleEvent(onFocus, e);
};
- const handleBlur = (e: React.FocusEvent) => {
+ const handleBlur = (e: FocusEvent) => {
+ if (readonly) {
+ return;
+ };
+
isFocused.current = false;
removeClass('isFocused');
keyboard.setFocus(false);
@@ -193,7 +161,7 @@ const Input = forwardRef(({
handleEvent(onBlur, e);
};
- const handlePaste = (e: React.ClipboardEvent) => {
+ const handlePaste = (e: ClipboardEvent) => {
e.persist();
callWithTimeout(() => {
updateRange(e);
@@ -201,7 +169,7 @@ const Input = forwardRef(({
});
};
- const handleCut = (e: React.ClipboardEvent) => {
+ const handleCut = (e: ClipboardEvent) => {
e.persist();
callWithTimeout(() => {
updateRange(e);
@@ -209,7 +177,7 @@ const Input = forwardRef(({
});
};
- const handleSelect = (e: React.SyntheticEvent) => {
+ const handleSelect = (e: SyntheticEvent) => {
updateRange(e);
handleEvent(onSelect, e);
};
@@ -219,9 +187,11 @@ const Input = forwardRef(({
onCompositionStart?.();
};
- const handleCompositionEnd = () => {
+ const handleCompositionEnd = (e) => {
keyboard.setComposition(false);
onCompositionEnd?.();
+
+ handleChange(e);
};
const addClass = (className: string) => {
@@ -277,6 +247,46 @@ const Input = forwardRef(({
return rect;
};
+ useEffect(() => setValue(initialValue), []);
+
+ useEffect(() => {
+ if (maskOptions && inputRef.current) {
+ new Inputmask(maskOptions.mask, maskOptions).mask($(inputRef.current).get(0));
+ };
+
+ if (focusOnMount && inputRef.current) {
+ focus();
+ };
+
+ return () => {
+ if (isFocused.current) {
+ keyboard.setFocus(false);
+ keyboard.disableSelection(false);
+ };
+ };
+ }, [ maskOptions, focusOnMount ]);
+
+ useEffect(() => onChange?.($.Event('change'), value), [ value ]);
+
+ useImperativeHandle(ref, () => ({
+ focus,
+ blur: () => inputRef.current?.blur(),
+ select: () => inputRef.current?.select(),
+ setValue: (v: string) => setValue(String(v || '')),
+ getValue: () => String(value || ''),
+ setType: (v: string) => setInputType(v),
+ setError: (hasError: boolean) => $(inputRef.current).toggleClass('withError', hasError),
+ getSelectionRect,
+ setPlaceholder: (placeholder: string) => $(inputRef.current).attr({ placeholder }),
+ setRange: (range: I.TextRange) => {
+ callWithTimeout(() => {
+ focus();
+ inputRef.current?.setSelectionRange(range.from, range.to);
+ });
+ },
+ getRange: (): I.TextRange | null => rangeRef.current,
+ }));
+
return (
{
-
- public static defaultProps = {
- withFile: true,
- canResize: true,
- };
-
- _isMounted = false;
- node: any = null;
- state = {
- focused: false,
- size: Size.Full,
- };
- t = 0;
- refUrl: any = null;
-
- constructor (props: Props) {
- super(props);
-
- this.onSubmit = this.onSubmit.bind(this);
- this.onFocus = this.onFocus.bind(this);
- this.onBlur = this.onBlur.bind(this);
- this.onClickFile = this.onClickFile.bind(this);
+const InputWithFile: FC = ({
+ icon = '',
+ textUrl = translate('inputWithFileTextUrl'),
+ textFile = '',
+ withFile = true,
+ accept,
+ block,
+ readonly = false,
+ canResize = true,
+ onChangeUrl,
+ onChangeFile,
+}) => {
+
+ const [ isFocused, setIsFocused ] = useState(false);
+ const [ size, setSize ] = useState(Size.Full);
+ const nodeRef = useRef(null);
+ const urlRef = useRef(null);
+ const timeout = useRef(0);
+ const cn = [ 'inputWithFile', 'resizable' ];
+ const or = ` ${translate('commonOr')} `;
+ const isSmall = size == Size.Small;
+ const isIcon = size == Size.Icon;
+
+ let placeholder = textUrl;
+ let onClick = null;
+
+ if (!withFile) {
+ cn.push('noFile');
};
- render () {
- const { focused, size } = this.state;
- const { icon, textUrl = translate('inputWithFileTextUrl'), textFile, withFile, readonly } = this.props;
- const cn = [ 'inputWithFile', 'resizable' ];
- const or = ` ${translate('commonOr')} `;
- const onBlur = focused ? this.onBlur : null;
- const onFocus = !focused ? this.onFocus : null;
- const isSmall = size == Size.Small;
- const isIcon = size == Size.Icon;
-
- let placeholder = textUrl;
- let onClick = null;
-
- if (!withFile) {
- cn.push('noFile');
- };
-
- if (isSmall) {
- cn.push('isSmall');
- };
-
- if (readonly) {
- cn.push('isReadonly');
- };
-
- if (isIcon) {
- cn.push('isIcon');
- onClick = e => this.onClickFile(e);
- };
-
- if (focused) {
- cn.push('isFocused');
- };
-
- if (withFile && focused) {
- placeholder += or + (!isSmall ? textFile : '');
- };
-
- return (
- this.node = node}
- className={cn.join(' ')}
- onClick={onClick}
- >
- {icon ? : ''}
-
-
-
+ if (isSmall) {
+ cn.push('isSmall');
+ };
- {withFile ? (
-
- {!isSmall ? {translate('commonOr')} : ''}
- {textFile}
-
- ) : ''}
-
-
- );
+ if (readonly) {
+ cn.push('isReadonly');
};
- componentDidMount () {
- this._isMounted = true;
- this.resize();
- this.rebind();
+ if (isIcon) {
+ cn.push('isIcon');
+ onClick = e => onClickFile(e);
};
- componentDidUpdate () {
- const { focused } = this.state;
- const { block } = this.props;
-
- this.resize();
- this.rebind();
-
- if (focused) {
- if (this.refUrl) {
- this.refUrl.focus();
- };
- focus.set(block.id, { from: 0, to: 0 });
- };
+ if (isFocused) {
+ cn.push('isFocused');
};
- componentWillUnmount () {
- const { focused } = focus.state;
- const { block } = this.props;
-
- this._isMounted = false;
- this.unbind();
-
- if (focused == block.id) {
- keyboard.setFocus(false);
- };
+ if (withFile && isFocused) {
+ placeholder += or + (!isSmall ? textFile : '');
};
-
- rebind () {
- const { canResize } = this.props;
- if (!this._isMounted || !canResize) {
- return;
+
+ const rebind = () => {
+ if (canResize) {
+ $(nodeRef.current).off('resizeMove').on('resizeMove', () => resize());
};
-
- $(this.node).off('resizeMove').on('resizeMove', (e: any) => this.resize());
};
- unbind () {
- const { canResize } = this.props;
- if (!this._isMounted || !canResize) {
- return;
+ const unbind = () => {
+ if (canResize) {
+ $(nodeRef.current).off('resizeMove');
};
-
- $(this.node).off('resizeMove');
};
- resize () {
- const { canResize } = this.props;
+ const resize = () => {
if (!canResize) {
return;
};
raf(() => {
- if (!this._isMounted) {
+ const node = $(nodeRef.current);
+ if (!node.length) {
return;
};
-
- const node = $(this.node);
+
const rect = (node.get(0) as HTMLInputElement).getBoundingClientRect();
-
- let size = Size.Full;
+
+ let s = Size.Full;
if (rect.width <= SMALL_WIDTH) {
- size = Size.Small;
+ s = Size.Small;
};
if (rect.width <= ICON_WIDTH) {
- size = Size.Icon;
+ s = Size.Icon;
};
-
- if (size != this.state.size) {
- this.setState({ size });
+
+ if (s != size) {
+ setSize(s);
};
});
};
- onFocus (e: any) {
+ const onFocusHandler = (e: any) => {
e.stopPropagation();
- const { readonly } = this.props;
- if (readonly) {
- return;
+ if (!readonly) {
+ setIsFocused(true);
};
- this.setState({ focused: true });
};
- onBlur (e: any) {
+ const onBlurHandler = (e: any) => {
e.stopPropagation();
- this.setState({ focused: false });
+ setIsFocused(false);
};
- focus () {
- this.setState({ focused: true });
- };
-
- onChangeUrl (e: any, force: boolean) {
- const { onChangeUrl, readonly } = this.props;
-
+ const onChangeUrlHandler = (e: any, force: boolean) => {
if (readonly) {
return;
};
- window.clearTimeout(this.t);
- this.t = window.setTimeout(() => {
- if (!this.refUrl) {
+ window.clearTimeout(timeout.current);
+ timeout.current = window.setTimeout(() => {
+ if (!urlRef.current) {
return;
};
- const url = this.refUrl.getValue() || '';
+ const url = String(urlRef.current.getValue() || '');
if (!url) {
return;
};
@@ -250,9 +147,7 @@ class InputWithFile extends React.Component {
}, force ? 50 : J.Constant.delay.keyboard);
};
- onClickFile (e: any) {
- const { onChangeFile, accept, readonly } = this.props;
-
+ const onClickFile = (e: any) => {
e.preventDefault();
e.stopPropagation();
@@ -267,11 +162,77 @@ class InputWithFile extends React.Component {
});
};
- onSubmit (e: any) {
+ const onSubmit = (e: any) => {
e.preventDefault();
- this.onChangeUrl(e, true);
+ onChangeUrlHandler(e, true);
};
+
+ const onBlur = isFocused ? onBlurHandler : null;
+ const onFocus = !isFocused ? onFocusHandler : null;
+
+ useEffect(() => {
+ resize();
+ rebind();
+
+ return () => {
+ const { focused } = focus.state;
+
+ unbind();
+
+ if (focused == block.id) {
+ keyboard.setFocus(false);
+ };
+ };
+ }, []);
+
+ useEffect(() => {
+ resize();
+ rebind();
+
+ if (isFocused) {
+ keyboard.setFocus(true);
+ urlRef.current?.focus();
+ focus.set(block.id, { from: 0, to: 0 });
+ };
+
+ }, [ isFocused, size ]);
+ return (
+
+ {icon ? : ''}
+
+
+
+
+ {withFile ? (
+
+ {!isSmall ? {translate('commonOr')} : ''}
+ {textFile}
+
+ ) : ''}
+
+
+ );
};
export default InputWithFile;
\ No newline at end of file
diff --git a/src/ts/component/form/inputWithLabel.tsx b/src/ts/component/form/inputWithLabel.tsx
index c124b6f66e..f9d85e26e3 100644
--- a/src/ts/component/form/inputWithLabel.tsx
+++ b/src/ts/component/form/inputWithLabel.tsx
@@ -1,7 +1,6 @@
-import * as React from 'react';
-import $ from 'jquery';
-import { Input, Icon, Label } from 'Component';
-import { I, translate } from 'Lib';
+import React, { forwardRef, useRef, useEffect, useState, useImperativeHandle } from 'react';
+import { Input, Label } from 'Component';
+import { I } from 'Lib';
interface Props {
label: string;
@@ -16,76 +15,56 @@ interface Props {
onMouseLeave?(e: any): void;
};
-class InputWithLabel extends React.Component {
-
- node: any = null;
- isFocused = false;
- placeholder: any = null;
- ref = null;
-
- constructor (props: Props) {
- super(props);
-
- this.onFocus = this.onFocus.bind(this);
- this.onBlur = this.onBlur.bind(this);
- };
-
- render () {
- const { label } = this.props;
-
- return (
- this.node = node}
- onClick={() => this.ref.focus()}
- className="inputWithLabel"
- >
-
-
-
- this.ref = ref}
- {...this.props}
- onFocus={this.onFocus}
- onBlur={this.onBlur}
- />
-
-
-
- );
- };
-
- componentDidMount() {
- const node = $(this.node);
+interface InputWithLabelRefProps {
+ focus(): void;
+ blur(): void;
+ setValue(v: string): void;
+ getValue(): string;
+ setRange(range: I.TextRange): void;
+ isFocused(): boolean;
+};
- this.ref.setValue(this.props.value);
+const InputWithLabel = forwardRef((props, ref) => {
+ const {
+ label = '',
+ value = '',
+ readonly = false,
+ onFocus,
+ onBlur,
+ } = props;
+
+ const nodeRef = useRef(null);
+ const inputRef = useRef(null);
+ const [ isFocused, setIsFocused ] = useState(false);
+ const cn = [ 'inputWithLabel' ];
+
+ if (isFocused) {
+ cn.push('isFocused');
};
- focus () {
- this.ref.focus();
+ const focus = () => {
+ inputRef.current?.focus();
};
- blur () {
- this.ref.blur();
+ const blur = () => {
+ inputRef.current?.blur();
};
- setValue (v: string) {
- this.ref.setValue(v);
+ const setValue = (v: string) => {
+ inputRef.current?.setValue(v);
};
- getValue () {
- return this.ref.getValue();
+ const getValue = () => {
+ return inputRef.current?.getValue();
};
- setRange (range: I.TextRange) {
- this.ref.setRange(range);
+ const setRange = (range: I.TextRange) => {
+ inputRef.current?.setRange(range);
};
- onFocus (e: any) {
- const { onFocus, readonly } = this.props;
-
+ const onFocusHandler = (e: any) => {
if (!readonly) {
- this.isFocused = true;
- this.addFocusedClass();
+ setIsFocused(true);
};
if (onFocus) {
@@ -93,12 +72,9 @@ class InputWithLabel extends React.Component {
};
};
- onBlur (e: any) {
- const { onBlur, readonly } = this.props;
-
+ const onBlurHandler = (e: any) => {
if (!readonly) {
- this.isFocused = false;
- this.removeFocusedClass();
+ setIsFocused(false);
};
if (onBlur) {
@@ -106,16 +82,37 @@ class InputWithLabel extends React.Component {
};
};
- addFocusedClass () {
- const node = $(this.node);
- node.addClass('isFocused');
- };
+ useEffect(() => inputRef.current.setValue(value), []);
+
+ useImperativeHandle(ref, () => ({
+ focus,
+ blur,
+ setValue,
+ getValue,
+ setRange,
+ isFocused: () => isFocused,
+ }));
+
+ return (
+
+
+
+
+
- removeFocusedClass () {
- const node = $(this.node);
- node.removeClass('isFocused');
- };
+
+
+ );
-};
+});
-export default InputWithLabel;
+export default InputWithLabel;
\ No newline at end of file
diff --git a/src/ts/component/form/phrase.tsx b/src/ts/component/form/phrase.tsx
index e62e8fd8ef..0c79c60d2b 100644
--- a/src/ts/component/form/phrase.tsx
+++ b/src/ts/component/form/phrase.tsx
@@ -1,11 +1,11 @@
-import * as React from 'react';
+import React, { forwardRef, useRef, useState, useEffect, useImperativeHandle } from 'react';
import $ from 'jquery';
import { getRange, setRange } from 'selection-ranges';
import { Icon } from 'Component';
import { J, S, keyboard, Storage } from 'Lib';
interface Props {
- value: string;
+ value?: string;
className?: string;
readonly?: boolean;
isHidden?: boolean;
@@ -18,10 +18,11 @@ interface Props {
onClick?: (e: any) => void;
};
-interface State {
- phrase: string[];
- isHidden: boolean;
- hasError: boolean;
+interface PhraseRefProps {
+ setValue: (value: string) => void;
+ getValue: () => string;
+ setError: (hasError: boolean) => void;
+ focus: () => void;
};
const COLORS = [
@@ -34,133 +35,55 @@ const COLORS = [
'lime',
];
-class Phrase extends React.Component {
-
- public static defaultProps: Props = {
- value: '',
- className: '',
- };
-
- state: State = {
- isHidden: true,
- hasError: false,
- phrase: [],
- };
-
- node = null;
- refEntry = null;
- refPlaceholder = null;
- range = null;
-
- constructor (props: Props) {
- super(props);
-
- this.onSelect = this.onSelect.bind(this);
- this.onClick = this.onClick.bind(this);
- this.onKeyDown = this.onKeyDown.bind(this);
- this.onKeyUp = this.onKeyUp.bind(this);
- this.onPaste = this.onPaste.bind(this);
- this.onFocus = this.onFocus.bind(this);
- this.onBlur = this.onBlur.bind(this);
- this.onToggle = this.onToggle.bind(this);
- };
-
- render () {
- const { readonly, className, onCopy, placeholder } = this.props;
- const { isHidden, hasError, phrase } = this.state;
- const cn = [ 'phraseWrapper', className ];
-
- if (isHidden) {
- cn.push('isHidden');
- };
-
- if (hasError) {
- cn.push('hasError');
- };
-
- if (readonly) {
- cn.push('isReadonly');
- };
-
- const renderWord = (word: string, index: number) => {
- const color = COLORS[index % COLORS.length];
- const cn = isHidden ? `bg bg-${color}` : `textColor textColor-${color}`;
-
- return {word};
- };
-
- let placeholderEl = null;
- if (placeholder) {
- placeholderEl = (
- this.refPlaceholder = ref}
- id="placeholder"
- className="placeholder"
- >
- {placeholder}
-
- );
- };
-
- return (
- this.node = ref}
- className={cn.join(' ')}
- onClick={this.onClick}
- >
-
- {!phrase.length ? : ''}
- {phrase.map(renderWord)}
- this.refEntry = ref}
- id="entry"
- contentEditable={true}
- suppressContentEditableWarning={true}
- onKeyDown={this.onKeyDown}
- onKeyUp={this.onKeyUp}
- onPaste={this.onPaste}
- onBlur={this.onBlur}
- onFocus={this.onFocus}
- onSelect={this.onSelect}
- >
- {'\n'}
-
-
-
- {placeholderEl}
-
-
-
- );
+const Phrase = forwardRef(({
+ value = '',
+ className = '',
+ readonly = false,
+ isHidden: initialHidden = false,
+ checkPin = false,
+ placeholder = '',
+ onKeyDown,
+ onChange,
+ onToggle,
+ onCopy,
+ onClick,
+}, ref) => {
+
+ const [ isHidden, setIsHidden ] = useState(false);
+ const [ hasError, setHasError ] = useState(false);
+ const [ dummy, setDummy ] = useState(0);
+ const placeholderRef = useRef(null);
+ const entryRef = useRef(null);
+ const range = useRef(null);
+ const phrase = useRef([]);
+ const cn = [ 'phraseWrapper', className ];
+
+ if (isHidden) {
+ cn.push('isHidden');
};
- componentDidMount () {
- const { value, isHidden } = this.props;
-
- this.setState({ isHidden });
- this.setValue(value);
- this.focus();
+ if (hasError) {
+ cn.push('hasError');
};
- componentDidUpdate () {
- this.placeholderCheck();
+ if (readonly) {
+ cn.push('isReadonly');
};
- onClick (e: any) {
- this.focus();
+ const onClickHandler = (e: any) => {
+ focus();
- if (this.props.onClick) {
- this.props.onClick(e);
+ if (onClick) {
+ onClick(e);
};
};
- onKeyDown (e: React.KeyboardEvent) {
- const { onKeyDown } = this.props;
- const entry = $(this.refEntry);
+ const onKeyDownHandler = (e: React.KeyboardEvent) => {
+ const entry = $(entryRef.current);
keyboard.shortcut('space, enter', e, () => {
e.preventDefault();
- this.updateValue();
+ updateValue();
});
keyboard.shortcut('backspace', e, () => {
@@ -173,69 +96,70 @@ class Phrase extends React.Component {
e.preventDefault();
- this.setState(({ phrase }) => {
- phrase.pop();
- return { phrase };
- });
+ phrase.current.pop();
+ setDummy(dummy + 1);
});
- this.placeholderCheck();
+ placeholderCheck();
if (onKeyDown) {
onKeyDown(e);
};
};
- onKeyUp (e: React.KeyboardEvent) {
- this.placeholderCheck();
+ const onKeyUp = (e: React.KeyboardEvent) => {
+ placeholderCheck();
};
- updateValue () {
- const value = this.getEntryValue();
+ const updateValue = () => {
+ const value = getEntryValue();
if (!value.length) {
return;
};
- this.clear();
+ clear();
- this.state.phrase = this.checkValue(this.state.phrase.concat([ value ]));
- this.setState({ phrase: this.state.phrase });
+ phrase.current = checkValue(phrase.current.concat(value.split(' ')));
+ setDummy(dummy + 1);
};
- onPaste (e) {
+ const onPaste = (e) => {
e.preventDefault();
const cb = e.clipboardData || e.originalEvent.clipboardData;
- const text = this.normalizeWhiteSpace(cb.getData('text/plain'));
+ const text = normalizeWhiteSpace(cb.getData('text/plain'));
+
+ clear();
+ phrase.current = checkValue(phrase.current.concat(text.split(' ')));
+
+ if (text) {
+ placeholderHide();
+ };
- this.clear();
- this.setState(({ phrase }) => ({ phrase: this.checkValue(phrase.concat(text.split(' '))) }));
+ setDummy(dummy + 1);
};
- onBlur () {
- this.placeholderCheck();
+ const onBlur = () => {
+ placeholderCheck();
};
- onFocus () {
- this.placeholderCheck();
+ const onFocus = () => {
+ placeholderCheck();
};
- onSelect () {
- const node = $(this.node);
- const entry = node.find('#entry');
+ const onSelect = () => {
+ const entry = $(entryRef.current);
if (entry.length) {
- this.range = getRange(entry.get(0));
+ range.current = getRange(entry.get(0));
};
};
- onToggle () {
- const { checkPin, onToggle } = this.props;
- const { isHidden } = this.state;
+ const onToggleHandler = () => {
const pin = Storage.getPin();
const onSuccess = () => {
- this.setState({ isHidden: !isHidden });
+ setIsHidden(!isHidden);
if (onToggle) {
onToggle(!isHidden);
@@ -249,56 +173,118 @@ class Phrase extends React.Component {
};
};
- checkValue (v: string[]) {
+ const checkValue = (v: string[]) => {
return v.map(it => it.substring(0, J.Constant.count.phrase.letter)).filter(it => it).slice(0, J.Constant.count.phrase.word);
};
- setError (v: boolean) {
- this.setState({ hasError: v });
+ const setError = (v: boolean) => {
+ setHasError(v);
};
- focus () {
- const entry = $(this.refEntry);
+ const focus = () => {
+ if (readonly) {
+ return;
+ };
+
+ const entry = $(entryRef.current);
entry.trigger('focus');
- setRange(entry.get(0), this.range || { start: 0, end: 0 });
+ setRange(entry.get(0), range.current || { start: 0, end: 0 });
};
- clear () {
- $(this.refEntry).text('');
+ const clear = () => {
+ $(entryRef.current).text('');
};
- getEntryValue () {
- return this.normalizeWhiteSpace($(this.refEntry).text()).toLowerCase();
+ const getEntryValue = () => {
+ return normalizeWhiteSpace($(entryRef.current).text()).toLowerCase();
};
- normalizeWhiteSpace = (val: string) => {
+ const normalizeWhiteSpace = (val: string) => {
return String(val || '').replace(/\s\s+/g, ' ').trim() || '';
};
- setValue (value: string) {
- const text = this.normalizeWhiteSpace(value);
- const phrase = text.length ? text.split(' '): [];
+ const setValue = (value: string) => {
+ const text = normalizeWhiteSpace(value);
- this.setState({ phrase });
+ phrase.current = text.length ? text.split(' '): [];
+ setDummy(dummy + 1);
};
- getValue () {
- return this.state.phrase.join(' ').trim().toLowerCase();
+ const getValue = () => {
+ return phrase.current.join(' ').trim().toLowerCase();
};
- placeholderCheck () {
- this.getValue().length || this.getEntryValue() ? this.placeholderHide() : this.placeholderShow();
+ const placeholderCheck = () => {
+ getValue().length || getEntryValue() ? placeholderHide() : placeholderShow();
};
- placeholderHide () {
- $(this.refPlaceholder).hide();
+ const placeholderHide= () => {
+ $(placeholderRef.current).hide();
};
- placeholderShow () {
- $(this.refPlaceholder).show();
+ const placeholderShow = () => {
+ $(placeholderRef.current).show();
};
-};
+ useEffect(() => {
+ setIsHidden(initialHidden);
+ setValue(value);
+ focus();
+ }, []);
+
+ useEffect(() => {
+ placeholderCheck();
+ }, [ phrase ]);
+
+ useImperativeHandle(ref, () => ({
+ setValue,
+ getValue,
+ setError,
+ focus,
+ onToggle: onToggleHandler,
+ }));
+
+ return (
+
+
+ {!phrase.current.length ? : ''}
+ {phrase.current.map((item: string, i: number) => {
+ const color = COLORS[i % COLORS.length];
+ const cn = isHidden ? `bg bg-${color}` : `textColor textColor-${color}`;
+ const word = isHidden ? '•'.repeat(item.length) : item;
+
+ return (
+
+ {word}
+
+ );
+ })}
+
+ {'\n'}
+
+
+
+ {placeholder ? {placeholder} : ''}
+
+
+
+ );
+
+});
export default Phrase;
\ No newline at end of file
diff --git a/src/ts/component/form/pin.tsx b/src/ts/component/form/pin.tsx
index 9860e3dcdc..b2d021f8c1 100644
--- a/src/ts/component/form/pin.tsx
+++ b/src/ts/component/form/pin.tsx
@@ -1,4 +1,4 @@
-import * as React from 'react';
+import React, { forwardRef, useRef, useState, useEffect, useImperativeHandle } from 'react';
import sha1 from 'sha1';
import { Input } from 'Component';
import { keyboard } from 'Lib';
@@ -14,141 +14,90 @@ interface Props {
readonly?: boolean;
};
-type State = {
- index: number;
+interface PinRefProps {
+ clear: () => void;
+ reset: () => void;
+ focus: () => void;
+ getValue: () => string;
};
-/**
- * This component provides an input field for a pin code
- */
-
const TIMEOUT_DURATION = 150;
-class Pin extends React.Component {
-
- public static defaultProps = {
- pinLength: 6,
- expectedPin: null,
- focusOnMount: true,
- isVisible: false,
- isNumeric: false,
- };
-
- state = {
- index: 0,
- };
-
- inputRefs = [];
-
- // This timeout is used so that the input boxes first show the inputted value as text, then hides it as password showing (•)
- timeout = 0;
-
- render () {
- const { pinLength, isNumeric, readonly } = this.props;
- const props: any = {
- maxLength: 1,
- onKeyUp: this.onInputKeyUp,
- readonly,
- };
-
- if (isNumeric) {
- props.inputMode = 'numeric';
- };
+const Pin = forwardRef(({
+ isNumeric = false,
+ pinLength = 6,
+ expectedPin = null,
+ focusOnMount = true,
+ isVisible = false,
+ readonly = false,
+ onSuccess = () => {},
+ onError = () => {},
+}, ref) => {
- return (
-
- {Array(pinLength).fill(null).map((_, i) => (
- this.inputRefs[i] = ref}
- key={i}
- onPaste={e => this.onPaste(e, i)}
- onFocus={() => this.onInputFocus(i)}
- onKeyDown={e => this.onInputKeyDown(e, i)}
- onChange={(_, value) => this.onInputChange(i, value)}
- {...props}
- />
- ))}
-
- );
- };
-
- componentDidMount () {
- if (this.props.focusOnMount) {
- this.focus();
- };
- this.rebind();
- };
+ const inputRefs = useRef([]);
+ const index = useRef(0);
- componentWillUnmount () {
- window.clearTimeout(this.timeout);
- this.unbind();
+ const rebind = () => {
+ unbind();
+ $(window).on('mousedown.pin', e => e.preventDefault());
};
- rebind = () => {
- this.unbind();
- $(window).on('mousedown.pin', e => { e.preventDefault(); });
- };
-
- unbind = () => {
+ const unbind = () => {
$(window).off('mousedown.pin');
};
- focus = () => {
- this.inputRefs[this.state.index].focus();
+ const focus = () => {
+ inputRefs.current[index.current].focus();
};
- onClick = () => {
- this.focus();
+ const onClick = () => {
+ focus();
};
/** triggers when all the pin characters have been entered in, resetting state and calling callbacks */
- check = () => {
- const { expectedPin } = this.props;
- const pin = this.getValue();
+ const check = () => {
+ const pin = getValue();
const success = !expectedPin || (expectedPin === sha1(pin));
- const onSuccess = this.props.onSuccess || (() => {});
- const onError = this.props.onError || (() => {});
success ? onSuccess(pin) : onError();
};
/** returns the pin state stored in the input DOM */
- getValue = () => {
- return this.inputRefs.map((input) => input.getValue()).join('');
+ const getValue = () => {
+ return inputRefs.current.map((input) => input.getValue()).join('');
};
/** sets all the input boxes to empty string */
- clear = () => {
- for (const i in this.inputRefs) {
- this.inputRefs[i].setValue('');
+ const clear = () => {
+ for (const i in inputRefs.current) {
+ inputRefs.current[i].setValue('');
};
};
/** resets state */
- reset () {
- this.setState({ index: 0 }, () => {
- this.clear();
- this.focus();
-
- for (const i in this.inputRefs) {
- this.inputRefs[i].setType('text');
- };
- });
+ const reset = () => {
+ index.current = 0;
+ clear();
+ focus();
+
+ for (const i in inputRefs.current) {
+ inputRefs.current[i].setType('text');
+ };
};
// Input subcomponent methods
- onInputFocus = (index: number) => {
- this.setState({ index });
+ const onInputFocus = (idx: number) => {
+ index.current = idx;
};
- onInputKeyDown = (e, index: number) => {
- const { isNumeric } = this.props;
- const prev = this.inputRefs[index - 1];
+ const onInputKeyDown = (e, index: number) => {
+ const current = inputRefs.current[index];
+ const prev = inputRefs.current[index - 1];
if (prev) {
keyboard.shortcut('backspace', e, () => {
- prev.setValue('');
+ current.setValue('');
prev.setType('text');
prev.focus();
});
@@ -161,18 +110,15 @@ class Pin extends React.Component {
};
};
- onInputKeyUp = () => {
- const { pinLength } = this.props;
-
- if (this.getValue().length === pinLength) {
- this.check();
+ const onInputKeyUp = () => {
+ if (getValue().length === pinLength) {
+ check();
};
};
- onInputChange = (index: number, value: string) => {
- const { isVisible, isNumeric } = this.props;
- const input = this.inputRefs[index];
- const next = this.inputRefs[index + 1];
+ const onInputChange = (index: number, value: string) => {
+ const input = inputRefs.current[index];
+ const next = inputRefs.current[index + 1];
let newValue = value;
if (isNumeric) {
@@ -196,29 +142,70 @@ class Pin extends React.Component {
};
if (!isVisible) {
- this.timeout = window.setTimeout(() => input.setType('password'), TIMEOUT_DURATION);
+ window.setTimeout(() => input.setType('password'), TIMEOUT_DURATION);
};
};
- async onPaste (e: any, index: number) {
+ const onPaste = async (e: any, index: number) => {
e.preventDefault();
- const { pinLength } = this.props;
const text = await navigator.clipboard.readText();
const value = String(text || '').split('');
for (let i = index; i < pinLength; i++) {
- const input = this.inputRefs[i];
+ const input = inputRefs.current[i];
const char = value[i - index] || '';
input.setValue(char);
input.setType('text');
};
- this.inputRefs[pinLength - 1].focus();
- this.check();
+ inputRefs.current[pinLength - 1].focus();
+ check();
};
-};
+ useImperativeHandle(ref, () => ({
+ clear,
+ reset,
+ focus,
+ getValue,
+ }));
+
+ useEffect(() => {
+ if (focusOnMount) {
+ window.setTimeout(() => focus(), 10);
+ };
+
+ rebind();
+ return () => unbind();
+ }, []);
+
+ const props: any = {
+ maxLength: 1,
+ onKeyUp: onInputKeyUp,
+ readonly,
+ };
+
+ if (isNumeric) {
+ props.inputMode = 'numeric';
+ };
+
+ return (
+
+ {Array(pinLength).fill(null).map((_, i) => (
+ inputRefs.current[i] = ref}
+ key={i}
+ onPaste={e => onPaste(e, i)}
+ onFocus={() => onInputFocus(i)}
+ onKeyDown={e => onInputKeyDown(e, i)}
+ onChange={(_, value) => onInputChange(i, value)}
+ {...props}
+ />
+ ))}
+
+ );
+
+});
-export default Pin;
+export default Pin;
\ No newline at end of file
diff --git a/src/ts/component/form/select.tsx b/src/ts/component/form/select.tsx
index f61f1a4a76..fac21b5ccd 100644
--- a/src/ts/component/form/select.tsx
+++ b/src/ts/component/form/select.tsx
@@ -1,6 +1,6 @@
-import * as React from 'react';
+import React, { forwardRef, useState, useEffect, useImperativeHandle, MouseEvent } from 'react';
import $ from 'jquery';
-import { I, S, Relation } from 'Lib';
+import { I, S, U, Relation } from 'Lib';
import { Icon, MenuItemVertical } from 'Component';
interface Props {
@@ -11,7 +11,7 @@ interface Props {
element?: string;
value: any;
options: I.Option[];
- noFilter: boolean;
+ noFilter?: boolean;
isMultiple?: boolean;
showOn?: string;
readonly?: boolean;
@@ -19,159 +19,82 @@ interface Props {
onChange? (id: any): void;
};
-interface State {
- value: string[];
- options: I.Option[];
+interface SelectRefProps {
+ getValue: () => any;
+ setValue: (v: any) => void;
+ setOptions: (options: I.Option[]) => void;
};
-class Select extends React.Component {
-
- public static defaultProps = {
- initial: '',
- noFilter: true,
- showOn: 'click',
- };
-
- _isMounted = false;
- state = {
- value: [],
- options: [] as I.Option[]
- };
-
- constructor (props: Props) {
- super(props);
-
- this.show = this.show.bind(this);
- this.hide = this.hide.bind(this);
+const Select = forwardRef(({
+ id = '',
+ initial = '',
+ className = '',
+ arrowClassName = '',
+ element = '',
+ value: initialValue = [],
+ options: initialOptions = [],
+ noFilter = true,
+ isMultiple = false,
+ showOn = 'click',
+ readonly = false,
+ menuParam = {},
+ onChange,
+}, ref) => {
+ const [ value, setValue ] = useState(initialValue);
+ const [ options, setOptions ] = useState(initialOptions);
+ const cn = [ 'select', className ];
+ const acn = [ 'arrow', arrowClassName ];
+ const current: any[] = [];
+
+ if (className) {
+ cn.push(className);
};
-
- render () {
- const { id, className, arrowClassName, readonly, showOn } = this.props;
- const { options } = this.state;
- const cn = [ 'select' ];
- const acn = [ 'arrow', (arrowClassName ? arrowClassName : '') ];
- const value = Relation.getArrayValue(this.state.value);
- const current: any[] = [];
-
- if (className) {
- cn.push(className);
- };
-
- if (readonly) {
- cn.push('isReadonly');
- };
- value.forEach((id: string) => {
- const option = options.find(item => item.id == id);
- if (option) {
- current.push(option);
- };
- });
-
- if (!current.length && options.length) {
- current.push(options[0]);
- };
-
- let onClick = null;
- let onMouseDown = null;
- let onMouseEnter = null;
-
- if (showOn == 'mouseDown') {
- onMouseDown = this.show;
- };
-
- if (showOn == 'click') {
- onClick = this.show;
- };
-
- if (showOn == 'mouseEnter') {
- onMouseEnter = this.show;
- };
-
- return (
-
- {current ? (
-
- {current.map((item: any, i: number) => (
-
- ))}
-
-
- ) : ''}
-
- );
+ if (readonly) {
+ cn.push('isReadonly');
};
-
- componentDidMount () {
- this._isMounted = true;
- const options = this.getOptions();
-
- let value = Relation.getArrayValue(this.props.value);
- if (!value.length && options.length) {
- value = [ options[0].id ];
+ let val = Relation.getArrayValue(value);
+ val.forEach((id: string) => {
+ const option = options.find(item => item.id == id);
+ if (option) {
+ current.push(option);
};
+ });
- this.setState({ value, options });
- };
-
- componentWillUnmount () {
- this._isMounted = false;
+ if (!current.length && options.length) {
+ current.push(options[0]);
};
- getOptions () {
- const { initial } = this.props;
- const options = [];
+ const getOptions = () => {
+ const ret = [];
if (initial) {
- options.push({ id: '', name: initial, isInitial: true });
+ ret.push({ id: '', name: initial, isInitial: true });
};
- for (const option of this.props.options) {
- options.push(option);
+ for (const option of initialOptions) {
+ ret.push(option);
};
-
- return options;
- };
-
- setOptions (options: any[]) {
- this.setState({ options });
+ return ret;
};
- getValue (): any {
- const { isMultiple } = this.props;
- const value = Relation.getArrayValue(this.state.value);
-
- return isMultiple ? value : value[0];
+ const getValue = (val: any): any => {
+ return isMultiple ? val : (val.length ? val[0] : '');
};
-
- setValue (v: any) {
- const value = Relation.getArrayValue(v);
- if (this._isMounted) {
- this.state.value = value;
- this.setState({ value });
- };
+ const setValueHandler = (v: any) => {
+ setValue(Relation.getArrayValue(v));
};
- show (e: React.MouseEvent) {
+ const show = (e: MouseEvent) => {
e.stopPropagation();
- const { id, onChange, noFilter, isMultiple, readonly } = this.props;
- const { value, options } = this.state;
- const elementId = `#select-${id}`;
- const element = this.props.element || elementId;
-
if (readonly) {
return;
};
- const mp = this.props.menuParam || {};
+ const el = element || `#select-${id}`;
+ const mp = menuParam || {};
let onOpen = null;
let onClose = null;
@@ -185,18 +108,18 @@ class Select extends React.Component {
delete(mp.onClose);
};
- const menuParam = Object.assign({
- element,
+ const param = Object.assign({
+ element: el,
noFlipX: true,
onOpen: (context: any) => {
- window.setTimeout(() => $(element).addClass('isFocused'));
+ window.setTimeout(() => $(el).addClass('isFocused'));
if (onOpen) {
onOpen(context);
};
},
onClose: () => {
- window.setTimeout(() => $(element).removeClass('isFocused'));
+ window.setTimeout(() => $(el).removeClass('isFocused'));
if (onClose) {
onClose();
@@ -204,47 +127,97 @@ class Select extends React.Component {
},
}, mp);
- menuParam.data = Object.assign({
+ param.data = Object.assign({
noFilter,
noClose: true,
value,
- options,
+ options: U.Menu.prepareForSelect(options),
onSelect: (e: any, item: any) => {
- let { value } = this.state;
-
if (item.id !== '') {
if (isMultiple) {
- value = value.includes(item.id) ? value.filter(it => it != item.id) : [ ...value, item.id ];
+ val = val.includes(item.id) ? val.filter(it => it != item.id) : [ ...val, item.id ];
} else {
- value = [ item.id ];
+ val = [ item.id ];
};
} else {
- value = [];
+ val = [];
};
- this.setValue(value);
+ setValueHandler(val);
if (onChange) {
- onChange(this.getValue());
+ onChange(getValue(val));
};
if (!isMultiple) {
- this.hide();
+ hide();
} else {
- S.Menu.updateData('select', { value });
+ S.Menu.updateData('select', { value: val });
};
},
}, mp.data || {});
S.Menu.closeAll([ 'select' ], () => {
- S.Menu.open('select', menuParam);
+ S.Menu.open('select', param);
});
};
- hide () {
+ const hide = () => {
S.Menu.close('select');
};
-
-};
+
+ let onClick = null;
+ let onMouseDown = null;
+ let onMouseEnter = null;
+
+ if (showOn == 'mouseDown') {
+ onMouseDown = show;
+ };
+
+ if (showOn == 'click') {
+ onClick = show;
+ };
+
+ if (showOn == 'mouseEnter') {
+ onMouseEnter = show;
+ };
+
+ useEffect(() => {
+ const options = getOptions();
+
+ let val = Relation.getArrayValue(initialValue);
+ if (!val.length && options.length) {
+ val = [ options[0].id ];
+ };
+
+ setValue(val);
+ setOptions(options);
+ }, []);
+
+ useImperativeHandle(ref, () => ({
+ getValue: () => getValue(val),
+ setValue: setValueHandler,
+ setOptions: (options: I.Option[]) => setOptions(options),
+ }));
+
+ return (
+
+ {current ? (
+ <>
+ {current.map((item: any, i: number) => (
+
+ ))}
+
+ >
+ ) : ''}
+
+ );
+});
export default Select;
\ No newline at end of file
diff --git a/src/ts/component/form/switch.tsx b/src/ts/component/form/switch.tsx
index 4b085a4580..62aef6301b 100644
--- a/src/ts/component/form/switch.tsx
+++ b/src/ts/component/form/switch.tsx
@@ -46,7 +46,7 @@ const Switch = forwardRef(({
};
};
- useEffect(() => setValue(initialValue));
+ useEffect(() => setValue(initialValue), []);
useImperativeHandle(ref, () => ({
getValue: () => value,
diff --git a/src/ts/component/form/textarea.tsx b/src/ts/component/form/textarea.tsx
index bd41fe9fec..52d91485ce 100644
--- a/src/ts/component/form/textarea.tsx
+++ b/src/ts/component/form/textarea.tsx
@@ -1,4 +1,4 @@
-import * as React from 'react';
+import React, { forwardRef, useRef, useState, useEffect, useImperativeHandle } from 'react';
import $ from 'jquery';
import { keyboard } from 'Lib';
@@ -22,182 +22,150 @@ interface Props {
onPaste?(e: any): void;
};
-interface State {
- value: string;
+interface TextareaRefProps {
+ focus(): void;
+ select(): void;
+ getValue(): string;
+ setError(v: boolean): void;
+ addClass(v: string): void;
+ removeClass(v: string): void;
};
-class Textarea extends React.Component {
-
- public static defaultProps = {
- value: ''
- };
-
- _isMounted = false;
- node: any = null;
- textAreaElement: HTMLTextAreaElement;
-
- state = {
- value: '',
- };
-
- constructor (props: Props) {
- super(props);
-
- this.onChange = this.onChange.bind(this);
- this.onKeyDown = this.onKeyDown.bind(this);
- this.onKeyUp = this.onKeyUp.bind(this);
- this.onInput = this.onInput.bind(this);
- this.onFocus = this.onFocus.bind(this);
- this.onBlur = this.onBlur.bind(this);
- this.onCopy = this.onCopy.bind(this);
- this.onPaste = this.onPaste.bind(this);
- };
-
- render () {
- const { id, name, className, placeholder, rows, autoComplete, readonly, maxLength } = this.props;
- const { value } = this.state;
- const cn = [ 'textarea' ];
-
- if (className) {
- cn.push(className);
+const Textarea = forwardRef(({
+ id = '',
+ name = '',
+ placeholder = '',
+ className = '',
+ rows = null,
+ value: initialValue = '',
+ autoComplete = null,
+ maxLength = null,
+ readonly = false,
+ onChange,
+ onKeyDown,
+ onKeyUp,
+ onInput,
+ onFocus,
+ onBlur,
+ onCopy,
+ onPaste,
+}, ref) => {
+ const [ value, setValue ] = useState(initialValue);
+ const nodeRef = useRef(null);
+ const cn = [ 'textarea' ];
+
+ if (className) {
+ cn.push(className);
+ };
+
+ const onChangeHandler = (e: any) => {
+ setValue(e.target.value);
+ if (onChange) {
+ onChange(e, e.target.value);
};
-
- return (
- |