fix: Unable to access localStorage in document embedded in iframe with third party cookies blocked (#4777)
* fix: Pasting from Microsoft Office pastes image. Closes #3058 * fix: Use Storage wrapper implementation for all editor calls to localStorage closes #4776
This commit is contained in:
@@ -52,8 +52,9 @@ import visualbasic from "refractor/lang/visual-basic";
|
||||
import yaml from "refractor/lang/yaml";
|
||||
import zig from "refractor/lang/zig";
|
||||
|
||||
import { UserPreferences } from "@shared/types";
|
||||
import { Dictionary } from "~/hooks/useDictionary";
|
||||
import { UserPreferences } from "../../types";
|
||||
import Storage from "../../utils/Storage";
|
||||
|
||||
import toggleBlockType from "../commands/toggleBlockType";
|
||||
import { MarkdownSerializerState } from "../lib/markdown/serializer";
|
||||
@@ -220,7 +221,7 @@ export default class CodeFence extends Node {
|
||||
commands({ type, schema }: { type: NodeType; schema: Schema }) {
|
||||
return (attrs: Record<string, any>) =>
|
||||
toggleBlockType(type, schema.nodes.paragraph, {
|
||||
language: localStorage?.getItem(PERSISTENCE_KEY) || DEFAULT_LANGUAGE,
|
||||
language: Storage.get(PERSISTENCE_KEY, DEFAULT_LANGUAGE),
|
||||
...attrs,
|
||||
});
|
||||
}
|
||||
@@ -302,7 +303,7 @@ export default class CodeFence extends Node {
|
||||
|
||||
view.dispatch(transaction);
|
||||
|
||||
localStorage?.setItem(PERSISTENCE_KEY, language);
|
||||
Storage.set(PERSISTENCE_KEY, language);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -364,7 +365,7 @@ export default class CodeFence extends Node {
|
||||
inputRules({ type }: { type: NodeType }) {
|
||||
return [
|
||||
textblockTypeInputRule(/^```$/, type, () => ({
|
||||
language: localStorage?.getItem(PERSISTENCE_KEY) || DEFAULT_LANGUAGE,
|
||||
language: Storage.get(PERSISTENCE_KEY, DEFAULT_LANGUAGE),
|
||||
})),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from "prosemirror-model";
|
||||
import { Plugin, Selection } from "prosemirror-state";
|
||||
import { Decoration, DecorationSet } from "prosemirror-view";
|
||||
import Storage from "../../utils/Storage";
|
||||
import backspaceToParagraph from "../commands/backspaceToParagraph";
|
||||
import splitHeading from "../commands/splitHeading";
|
||||
import toggleBlockType from "../commands/toggleBlockType";
|
||||
@@ -151,9 +152,9 @@ export default class Heading extends Node {
|
||||
const persistKey = headingToPersistenceKey(node, this.editor.props.id);
|
||||
|
||||
if (collapsed) {
|
||||
localStorage?.setItem(persistKey, "collapsed");
|
||||
Storage.set(persistKey, "collapsed");
|
||||
} else {
|
||||
localStorage?.removeItem(persistKey);
|
||||
Storage.remove(persistKey);
|
||||
}
|
||||
|
||||
view.dispatch(transaction);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Plugin } from "prosemirror-state";
|
||||
import { findBlockNodes } from "prosemirror-utils";
|
||||
import { Decoration, DecorationSet } from "prosemirror-view";
|
||||
import Storage from "../../utils/Storage";
|
||||
import Extension from "../lib/Extension";
|
||||
import { headingToPersistenceKey } from "../lib/headingToSlug";
|
||||
import findCollapsedNodes from "../queries/findCollapsedNodes";
|
||||
@@ -40,7 +41,7 @@ export default class Folding extends Extension {
|
||||
block.node,
|
||||
this.editor.props.id
|
||||
);
|
||||
const persistedState = localStorage?.getItem(persistKey);
|
||||
const persistedState = Storage.get(persistKey);
|
||||
|
||||
if (persistedState === "collapsed") {
|
||||
tr.setNodeMarkup(block.pos, undefined, {
|
||||
|
||||
95
shared/utils/Storage.ts
Normal file
95
shared/utils/Storage.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Storage is a wrapper class for localStorage that allow safe usage when
|
||||
* localStorage is not available.
|
||||
*/
|
||||
class Storage {
|
||||
interface: typeof localStorage | MemoryStorage;
|
||||
|
||||
public constructor() {
|
||||
try {
|
||||
localStorage.setItem("test", "test");
|
||||
localStorage.removeItem("test");
|
||||
this.interface = localStorage;
|
||||
} catch (_err) {
|
||||
this.interface = new MemoryStorage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a value in storage. For efficiency, this method will remove the
|
||||
* value if it is undefined.
|
||||
*
|
||||
* @param key The key to set under.
|
||||
* @param value The value to set
|
||||
*/
|
||||
public set<T>(key: string, value: T) {
|
||||
try {
|
||||
if (value === undefined) {
|
||||
this.remove(key);
|
||||
} else {
|
||||
this.interface.setItem(key, JSON.stringify(value));
|
||||
}
|
||||
} catch (_err) {
|
||||
// Ignore errors
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a value from storage.
|
||||
*
|
||||
* @param key The key to get.
|
||||
* @param fallback The fallback value if the key doesn't exist.
|
||||
* @returns The value or undefined if it doesn't exist.
|
||||
*/
|
||||
public get(key: string, fallback?: any) {
|
||||
try {
|
||||
const value = this.interface.getItem(key);
|
||||
if (typeof value === "string") {
|
||||
return JSON.parse(value);
|
||||
}
|
||||
} catch (_err) {
|
||||
// Ignore errors
|
||||
}
|
||||
|
||||
return fallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a value from storage.
|
||||
*
|
||||
* @param key The key to remove.
|
||||
*/
|
||||
public remove(key: string) {
|
||||
try {
|
||||
this.interface.removeItem(key);
|
||||
} catch (_err) {
|
||||
// Ignore errors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* MemoryStorage is a simple in-memory storage implementation that is used
|
||||
* when localStorage is not available.
|
||||
*/
|
||||
class MemoryStorage {
|
||||
private data = {};
|
||||
|
||||
getItem(key: string) {
|
||||
return this.data[key] || null;
|
||||
}
|
||||
|
||||
setItem(key: string, value: any) {
|
||||
return (this.data[key] = String(value));
|
||||
}
|
||||
|
||||
removeItem(key: string) {
|
||||
return delete this.data[key];
|
||||
}
|
||||
|
||||
clear() {
|
||||
return (this.data = {});
|
||||
}
|
||||
}
|
||||
|
||||
export default new Storage();
|
||||
Reference in New Issue
Block a user