diff --git a/src-web/components/ExportDataDialog.tsx b/src-web/components/ExportDataDialog.tsx
index 1219de7d..a5f2ebc1 100644
--- a/src-web/components/ExportDataDialog.tsx
+++ b/src-web/components/ExportDataDialog.tsx
@@ -4,7 +4,7 @@ import { useCallback, useMemo, useState } from 'react';
import slugify from 'slugify';
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
import { useWorkspaces } from '../hooks/useWorkspaces';
-import { count } from '../lib/pluralize';
+import { pluralizeCount } from '../lib/pluralize';
import { invokeCmd } from '../lib/tauri';
import { Button } from './core/Button';
import { Checkbox } from './core/Checkbox';
@@ -128,7 +128,7 @@ function ExportDataDialogContent({
disabled={noneSelected}
onClick={() => handleExport()}
>
- Export {count('Workspace', numSelected, { omitSingle: true, noneWord: 'Nothing' })}
+ Export {pluralizeCount('Workspace', numSelected, { omitSingle: true, noneWord: 'Nothing' })}
diff --git a/src-web/components/GrpcEditor.tsx b/src-web/components/GrpcEditor.tsx
index ffa9ffcc..61ab7f50 100644
--- a/src-web/components/GrpcEditor.tsx
+++ b/src-web/components/GrpcEditor.tsx
@@ -14,7 +14,7 @@ import { useAlert } from '../hooks/useAlert';
import type { ReflectResponseService } from '../hooks/useGrpc';
import { tryFormatJson } from '../lib/formatters';
import type { GrpcRequest } from '@yaakapp-internal/models';
-import { count } from '../lib/pluralize';
+import { pluralizeCount } from '../lib/pluralize';
import { Button } from './core/Button';
import { FormattedError } from './core/FormattedError';
import { InlineCode } from './core/InlineCode';
@@ -164,7 +164,7 @@ export function GrpcEditor({
: reflectionError
? 'Server Error'
: protoFiles.length > 0
- ? count('File', protoFiles.length)
+ ? pluralizeCount('File', protoFiles.length)
: services != null && protoFiles.length === 0
? 'Schema Detected'
: 'Select Schema'}
diff --git a/src-web/components/GrpcProtoSelection.tsx b/src-web/components/GrpcProtoSelection.tsx
index 6c8c7dfa..b14f6686 100644
--- a/src-web/components/GrpcProtoSelection.tsx
+++ b/src-web/components/GrpcProtoSelection.tsx
@@ -2,7 +2,7 @@ import { open } from '@tauri-apps/plugin-dialog';
import { useGrpc } from '../hooks/useGrpc';
import { useGrpcProtoFiles } from '../hooks/useGrpcProtoFiles';
import { useGrpcRequest } from '../hooks/useGrpcRequest';
-import { count } from '../lib/pluralize';
+import { pluralizeCount } from '../lib/pluralize';
import { Banner } from './core/Banner';
import { Button } from './core/Button';
import { IconButton } from './core/IconButton';
@@ -76,7 +76,7 @@ export function GrpcProtoSelection({ requestId }: Props) {
);
})}
- {services?.length > 5 && count('other', services?.length - 5)}
+ {services?.length > 5 && pluralizeCount('other', services?.length - 5)}
)}
diff --git a/src-web/components/RecentConnectionsDropdown.tsx b/src-web/components/RecentConnectionsDropdown.tsx
index 956d97f4..fd09c799 100644
--- a/src-web/components/RecentConnectionsDropdown.tsx
+++ b/src-web/components/RecentConnectionsDropdown.tsx
@@ -2,7 +2,7 @@ import { formatDistanceToNowStrict } from 'date-fns';
import { useDeleteGrpcConnection } from '../hooks/useDeleteGrpcConnection';
import { useDeleteGrpcConnections } from '../hooks/useDeleteGrpcConnections';
import type { GrpcConnection } from '@yaakapp-internal/models';
-import { count } from '../lib/pluralize';
+import { pluralizeCount } from '../lib/pluralize';
import { Dropdown } from './core/Dropdown';
import { Icon } from './core/Icon';
import { IconButton } from './core/IconButton';
@@ -34,7 +34,7 @@ export function RecentConnectionsDropdown({
},
{
key: 'clear-all',
- label: `Clear ${count('Connection', connections.length)}`,
+ label: `Clear ${pluralizeCount('Connection', connections.length)}`,
onSelect: deleteAllConnections.mutate,
hidden: connections.length <= 1,
disabled: connections.length === 0,
diff --git a/src-web/components/core/Editor/Editor.tsx b/src-web/components/core/Editor/Editor.tsx
index 1f5cef8e..4cb73050 100644
--- a/src-web/components/core/Editor/Editor.tsx
+++ b/src-web/components/core/Editor/Editor.tsx
@@ -1,6 +1,7 @@
-import { defaultKeymap } from '@codemirror/commands';
-import { forceParsing } from '@codemirror/language';
-import { Compartment, EditorState, type Extension } from '@codemirror/state';
+import { defaultKeymap, historyField } from '@codemirror/commands';
+import { foldState, forceParsing } from '@codemirror/language';
+import type { EditorStateConfig, Extension } from '@codemirror/state';
+import { Compartment, EditorState } from '@codemirror/state';
import { keymap, placeholder as placeholderExt, tooltips } from '@codemirror/view';
import type { EnvironmentVariable } from '@yaakapp-internal/models';
import type { TemplateFunction } from '@yaakapp-internal/plugin';
@@ -74,6 +75,8 @@ export interface EditorProps {
stateKey: string | null;
}
+const stateFields = { history: historyField, folds: foldState };
+
const emptyVariables: EnvironmentVariable[] = [];
export const Editor = forwardRef(function Editor(
@@ -305,40 +308,42 @@ export const Editor = forwardRef(function E
onClickPathParameter,
});
+ const extensions = [
+ languageCompartment.of(langExt),
+ placeholderCompartment.current.of(
+ placeholderExt(placeholderElFromText(placeholder ?? '')),
+ ),
+ wrapLinesCompartment.current.of(wrapLines ? [EditorView.lineWrapping] : []),
+ ...getExtensions({
+ container,
+ readOnly,
+ singleLine,
+ hideGutter,
+ stateKey,
+ onChange: handleChange,
+ onPaste: handlePaste,
+ onPasteOverwrite: handlePasteOverwrite,
+ onFocus: handleFocus,
+ onBlur: handleBlur,
+ onKeyDown: handleKeyDown,
+ }),
+ ...(extraExtensions ?? []),
+ ];
+
const cachedJsonState = getCachedEditorState(defaultValue ?? '', stateKey);
- const state =
- cachedJsonState ??
- EditorState.create({
- doc: `${defaultValue ?? ''}`,
- extensions: [
- languageCompartment.of(langExt),
- placeholderCompartment.current.of(
- placeholderExt(placeholderElFromText(placeholder ?? '')),
- ),
- wrapLinesCompartment.current.of(wrapLines ? [EditorView.lineWrapping] : []),
- ...getExtensions({
- container,
- readOnly,
- singleLine,
- hideGutter,
- stateKey,
- onChange: handleChange,
- onPaste: handlePaste,
- onPasteOverwrite: handlePasteOverwrite,
- onFocus: handleFocus,
- onBlur: handleBlur,
- onKeyDown: handleKeyDown,
- }),
- ...(extraExtensions ?? []),
- ],
- });
+ const doc = `${defaultValue ?? ''}`;
+ const config: EditorStateConfig = { extensions, doc };
+
+ const state = cachedJsonState
+ ? EditorState.fromJSON(cachedJsonState, config, stateFields)
+ : EditorState.create(config);
const view = new EditorView({ state, parent: container });
// For large documents, the parser may parse the max number of lines and fail to add
// things like fold markers because of it.
- // This forces it to parse more but keeps the timeout to the default of 100ms.
+ // This forces it to parse more but keeps the timeout to the default of 100 ms.
forceParsing(view, 9e6, 100);
cm.current = { view, languageCompartment };
@@ -544,24 +549,25 @@ const placeholderElFromText = (text: string) => {
return el;
};
-declare global {
- interface Window {
- editorStates: Record;
- }
-}
-window.editorStates = window.editorStates ?? {};
-
function saveCachedEditorState(stateKey: string | null, state: EditorState | null) {
if (!stateKey || state == null) return;
- window.editorStates[stateKey] = state;
+ sessionStorage.setItem(stateKey, JSON.stringify(state.toJSON(stateFields)));
}
function getCachedEditorState(doc: string, stateKey: string | null) {
if (stateKey == null) return;
- const state = window.editorStates[stateKey] ?? null;
- if (state == null) return null;
- if (state.doc.toString() !== doc) return null;
+ const stateStr = sessionStorage.getItem(stateKey)
+ if (stateStr == null) return null;
- return state;
+ try {
+ const state = JSON.parse(stateStr);
+ if (state.doc !== doc) return null;
+
+ return state;
+ } catch {
+ // Nothing
+ }
+
+ return null;
}
diff --git a/src-web/components/core/Editor/extensions.ts b/src-web/components/core/Editor/extensions.ts
index e0302f42..9df08c2a 100644
--- a/src-web/components/core/Editor/extensions.ts
+++ b/src-web/components/core/Editor/extensions.ts
@@ -6,8 +6,8 @@ import {
} from '@codemirror/autocomplete';
import { history, historyKeymap, indentWithTab } from '@codemirror/commands';
import { javascript } from '@codemirror/lang-javascript';
-import { markdown } from '@codemirror/lang-markdown';
import { json } from '@codemirror/lang-json';
+import { markdown } from '@codemirror/lang-markdown';
import { xml } from '@codemirror/lang-xml';
import type { LanguageSupport } from '@codemirror/language';
import {
@@ -37,7 +37,8 @@ import type { EnvironmentVariable } from '@yaakapp-internal/models';
import type { TemplateFunction } from '@yaakapp-internal/plugin';
import { graphql } from 'cm6-graphql';
import { EditorView } from 'codemirror';
-import type {EditorProps} from "./Editor";
+import { pluralizeCount } from '../../../lib/pluralize';
+import type { EditorProps } from './Editor';
import { pairs } from './pairs/extension';
import { text } from './text/extension';
import { twig } from './twig/extension';
@@ -162,11 +163,15 @@ export const multiLineExtensions = ({ hideGutter }: { hideGutter?: boolean }) =>
const el = document.createElement('span');
el.onclick = onclick;
el.className = 'cm-foldPlaceholder';
- el.innerText = prepared;
+ el.innerText = prepared || '…';
el.title = 'unfold';
el.ariaLabel = 'folded code';
return el;
},
+ /**
+ * Show the number of items when code folded. NOTE: this doesn't get called when restoring
+ * a previous serialized editor state, which is a bummer
+ */
preparePlaceholder(state, range) {
let count: number | undefined;
let startToken = '{';
@@ -191,13 +196,9 @@ export const multiLineExtensions = ({ hideGutter }: { hideGutter?: boolean }) =>
}
if (count !== undefined) {
- const label = isArray ? 'item' : 'prop';
- const plural = count === 1 ? '' : 's';
-
- return `${count} ${label}${plural}`;
+ const label = isArray ? 'item' : 'key';
+ return pluralizeCount(label, count);
}
-
- return '…';
},
}),
EditorState.allowMultipleSelections.of(true),
diff --git a/src-web/hooks/useDeleteSendHistory.tsx b/src-web/hooks/useDeleteSendHistory.tsx
index 3eb1f419..6ccde5d4 100644
--- a/src-web/hooks/useDeleteSendHistory.tsx
+++ b/src-web/hooks/useDeleteSendHistory.tsx
@@ -1,5 +1,5 @@
import { useSetAtom } from 'jotai/index';
-import { count } from '../lib/pluralize';
+import { pluralizeCount } from '../lib/pluralize';
import { invokeCmd } from '../lib/tauri';
import { getActiveWorkspaceId } from './useActiveWorkspace';
import { useAlert } from './useAlert';
@@ -15,8 +15,8 @@ export function useDeleteSendHistory() {
const httpResponses = useHttpResponses();
const grpcConnections = useGrpcConnections();
const labels = [
- httpResponses.length > 0 ? count('Http Response', httpResponses.length) : null,
- grpcConnections.length > 0 ? count('Grpc Connection', grpcConnections.length) : null,
+ httpResponses.length > 0 ? pluralizeCount('Http Response', httpResponses.length) : null,
+ grpcConnections.length > 0 ? pluralizeCount('Grpc Connection', grpcConnections.length) : null,
].filter((l) => l != null);
return useFastMutation({
diff --git a/src-web/hooks/useImportData.tsx b/src-web/hooks/useImportData.tsx
index 3abf0161..e8561339 100644
--- a/src-web/hooks/useImportData.tsx
+++ b/src-web/hooks/useImportData.tsx
@@ -10,7 +10,7 @@ import { Button } from '../components/core/Button';
import { FormattedError } from '../components/core/FormattedError';
import { VStack } from '../components/core/Stacks';
import { ImportDataDialog } from '../components/ImportDataDialog';
-import { count } from '../lib/pluralize';
+import { pluralizeCount } from '../lib/pluralize';
import { invokeCmd } from '../lib/tauri';
import { getActiveWorkspace } from './useActiveWorkspace';
import { useAlert } from './useAlert';
@@ -47,11 +47,11 @@ export function useImportData() {
return (
- - {count('Workspace', workspaces.length)}
- - {count('Environment', environments.length)}
- - {count('Folder', folders.length)}
- - {count('HTTP Request', httpRequests.length)}
- - {count('GRPC Request', grpcRequests.length)}
+ - {pluralizeCount('Workspace', workspaces.length)}
+ - {pluralizeCount('Environment', environments.length)}
+ - {pluralizeCount('Folder', folders.length)}
+ - {pluralizeCount('HTTP Request', httpRequests.length)}
+ - {pluralizeCount('GRPC Request', grpcRequests.length)}