Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
ac7fe3c
chore(release): 0.16.0 [skip ci]
semantic-release-bot Sep 3, 2025
9bc488d
fix: imports encoded in utf-16 break DocxZipper
harbournick Sep 3, 2025
6d09115
fix: imports encoded in utf-16 break DocxZipper
harbournick Sep 3, 2025
850eac3
chore: test patching
caio-pizzol Sep 3, 2025
505e27b
fix: semantic release range
caio-pizzol Sep 3, 2025
5a4f309
chore: test patching
caio-pizzol Sep 3, 2025
8753421
Merge branch 'main' of https://github.com/Harbour-Enterprises/SuperDo…
caio-pizzol Sep 3, 2025
1fda655
fix: update release naming pattern in .releaserc.json for better vers…
caio-pizzol Sep 3, 2025
afc6414
Merge branch 'main' of https://github.com/Harbour-Enterprises/SuperDo…
caio-pizzol Sep 3, 2025
905df1f
Merge branch 'main' of https://github.com/Harbour-Enterprises/SuperDo…
caio-pizzol Sep 3, 2025
2af6097
Merge branch 'main' of https://github.com/Harbour-Enterprises/SuperDo…
caio-pizzol Sep 3, 2025
34c7189
Merge branch 'main' of https://github.com/Harbour-Enterprises/SuperDo…
caio-pizzol Sep 3, 2025
0f5546b
Merge branch 'main' of https://github.com/Harbour-Enterprises/SuperDo…
caio-pizzol Sep 3, 2025
7381930
Merge branch 'main' of https://github.com/Harbour-Enterprises/SuperDo…
caio-pizzol Sep 3, 2025
0abf060
Merge branch 'main' of https://github.com/Harbour-Enterprises/SuperDo…
caio-pizzol Sep 3, 2025
d617907
Merge branch 'main' of https://github.com/Harbour-Enterprises/SuperDo…
caio-pizzol Sep 3, 2025
c49bfb0
Merge branch 'main' of https://github.com/Harbour-Enterprises/SuperDo…
caio-pizzol Sep 3, 2025
e9c331c
chore(release): 0.16.1 [skip ci]
semantic-release-bot Sep 3, 2025
53c07c5
fix: restore stored marks if they exist (#863)
artem-harbour Sep 3, 2025
87b4e20
chore(release): 0.16.2 [skip ci]
semantic-release-bot Sep 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/super-editor/src/components/slash-menu/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export async function getEditorContext(editor, event) {
node = state.doc.nodeAt(pos);
}

// We need to check if we have anything in the clipboard and request permission if needed
// We need to check if we have anything in the clipboard
const clipboardContent = await readFromClipboard(state);

return {
Expand Down
1 change: 0 additions & 1 deletion packages/super-editor/src/core/Editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,6 @@ export class Editor extends EventEmitter {
// async (file) => url;
handleImageUpload: null,

// telemetry
telemetry: null,

// Docx xml updated by User
Expand Down
78 changes: 33 additions & 45 deletions packages/super-editor/src/core/utilities/clipboardUtils.js
Original file line number Diff line number Diff line change
@@ -1,48 +1,41 @@
// @ts-nocheck
// clipboardUtils.js

import { DOMParser } from 'prosemirror-model';
import { DOMSerializer, DOMParser } from 'prosemirror-model';

/**
* Checks if clipboard read permission is granted and handles permission prompts.
* Returns true if clipboard-read permission is granted. If state is "prompt" it will
* proactively trigger a readText() call which will surface the browser permission
* dialog to the user. Falls back gracefully in older browsers that lack the
* Permissions API.
* @returns {Promise<boolean>} Whether clipboard read permission is granted
* Serializes the current selection in the editor state to HTML and plain text for clipboard use.
* @param {EditorState} state - The ProseMirror editor state containing the current selection.
* @returns {{ htmlString: string, text: string }} An object with the HTML string and plain text of the selection.
*/
export async function ensureClipboardPermission() {
if (typeof navigator === 'undefined' || !navigator.clipboard) {
return false;
}

// Some older browsers do not expose navigator.permissions – assume granted
if (!navigator.permissions || typeof navigator.permissions.query !== 'function') {
return true;
}
export function serializeSelectionToClipboard(state) {
const { from, to } = state.selection;
const slice = state.selection.content();
const htmlContainer = document.createElement('div');
htmlContainer.appendChild(DOMSerializer.fromSchema(state.schema).serializeFragment(slice.content));
const htmlString = htmlContainer.innerHTML;
const text = state.doc.textBetween(from, to);
return { htmlString, text };
}

/**
* Writes HTML and plain text data to the system clipboard.
* Uses the Clipboard API if available, otherwise falls back to plain text.
* @param {{ htmlString: string, text: string }} param0 - The HTML and plain text to write to the clipboard.
* @returns {Promise<void>} A promise that resolves when the clipboard write is complete.
*/
export async function writeToClipboard({ htmlString, text }) {
try {
// @ts-ignore – string literal is valid at runtime; TS lib DOM typing not available in .js file
const status = await navigator.permissions.query({ name: 'clipboard-read' });

if (status.state === 'granted') {
return true;
}

if (status.state === 'prompt') {
// Trigger a readText() to make the browser show its permission prompt.
try {
await navigator.clipboard.readText();
return true;
} catch {
return false;
}
if (navigator.clipboard && window.ClipboardItem) {
const clipboardItem = new window.ClipboardItem({
'text/html': new Blob([htmlString], { type: 'text/html' }),
'text/plain': new Blob([text], { type: 'text/plain' }),
});
await navigator.clipboard.write([clipboardItem]);
} else {
await navigator.clipboard.writeText(text);
}

// If we hit this area this is state === 'denied'
return false;
} catch {
return false;
} catch (e) {
console.error('Error writing to clipboard', e);
}
}

Expand All @@ -55,9 +48,7 @@ export async function ensureClipboardPermission() {
export async function readFromClipboard(state) {
let html = '';
let text = '';
const hasPermission = await ensureClipboardPermission();

if (hasPermission && navigator.clipboard && navigator.clipboard.read) {
if (navigator.clipboard && navigator.clipboard.read) {
try {
const items = await navigator.clipboard.read();
for (const item of items) {
Expand All @@ -69,13 +60,10 @@ export async function readFromClipboard(state) {
}
}
} catch {
// Fallback to plain text read; may still fail if permission denied
try {
text = await navigator.clipboard.readText();
} catch {}
text = await navigator.clipboard.readText();
}
} else {
// permissions denied or API unavailable; leave content empty
text = await navigator.clipboard.readText();
}
let content = null;
if (html) {
Expand Down

This file was deleted.

16 changes: 12 additions & 4 deletions packages/super-editor/src/extensions/block-node/block-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { helpers } from '@core/index.js';
import { Plugin, PluginKey } from 'prosemirror-state';
import { ReplaceStep } from 'prosemirror-transform';
import { v4 as uuidv4 } from 'uuid';
import { Transaction } from 'prosemirror-state';

const { findChildren } = helpers;
const SD_BLOCK_ID_ATTRIBUTE_NAME = 'sdBlockId';
Expand Down Expand Up @@ -122,13 +123,12 @@ export const BlockNode = Extension.create({
// Check for new block nodes and if none found, we don't need to do anything
if (hasInitialized && !checkForNewBlockNodesInTrs(transactions)) return null;

let tr = null;
const { tr } = newState;
let changed = false;
newState.doc.descendants((node, pos) => {
// Only allow block nodes with a valid sdBlockId attribute
if (!nodeAllowsSdBlockIdAttr(node) || !nodeNeedsSdBlockId(node)) return null;

tr = tr ?? newState.tr;
tr.setNodeMarkup(
pos,
undefined,
Expand All @@ -141,7 +141,14 @@ export const BlockNode = Extension.create({
changed = true;
});

if (changed && !hasInitialized) hasInitialized = true;
if (changed && !hasInitialized) {
hasInitialized = true;
}

// Restore marks if they exist.
// `tr.setNodeMarkup` resets the stored marks.
tr.setStoredMarks(newState.tr.storedMarks);

return changed ? tr : null;
},
}),
Expand Down Expand Up @@ -171,7 +178,8 @@ export const nodeNeedsSdBlockId = (node) => {
/**
* Check for new block nodes in ProseMirror transactions.
* Iterate through the list of transactions, and in each tr check if there are any new block nodes.
* @param {Array<Transaction>} transactions - The ProseMirror transactions to check.
* @readonly
* @param {readonly Transaction[]} transactions - The ProseMirror transactions to check.
* @returns {boolean} - True if new block nodes are found, false otherwise.
*/
export const checkForNewBlockNodesInTrs = (transactions) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/superdoc/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@harbour-enterprises/superdoc",
"type": "module",
"version": "0.16.0-next.9",
"version": "0.16.2",
"license": "AGPL-3.0",
"readme": "../../README.md",
"files": [
Expand Down
1 change: 0 additions & 1 deletion packages/superdoc/src/core/SuperDoc.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,6 @@ export class SuperDoc extends EventEmitter {

isDev: false,

// telemetry config
telemetry: null,

// Events
Expand Down
11 changes: 6 additions & 5 deletions packages/superdoc/src/stores/comments-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -472,11 +472,12 @@ export const useCommentsStore = defineStore('comments', () => {

// Create comments for tracked changes
// that do not have a corresponding comment (created in Word).
const { tr } = editor.view.state;
const { dispatch } = editor.view;

groupedChanges.forEach(({ insertedMark, deletionMark, formatMark }, index) => {
console.debug(`Create comment for track change: ${index}`);

const { dispatch } = editor.view;
const { tr } = editor.view.state;

const foundComment = commentsList.value.find(
(i) =>
i.commentId === insertedMark?.mark.attrs.id ||
Expand All @@ -488,6 +489,7 @@ export const useCommentsStore = defineStore('comments', () => {
if (foundComment) {
if (isLastIteration) {
tr.setMeta(CommentsPluginKey, { type: 'force' });
dispatch(tr);
}
return;
}
Expand All @@ -502,10 +504,9 @@ export const useCommentsStore = defineStore('comments', () => {
if (isLastIteration) tr.setMeta(CommentsPluginKey, { type: 'force' });
tr.setMeta(CommentsPluginKey, { type: 'forceTrackChanges' });
tr.setMeta(TrackChangesBasePluginKey, trackChangesPayload);
dispatch(tr);
}
});

dispatch(tr);
};

const translateCommentsForExport = () => {
Expand Down