Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
29 changes: 28 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions packages/super-editor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@
"tippy.js": "^6.3.7",
"vite-plugin-node-polyfills": "^0.22.0",
"y-prosemirror": "^1.2.12",
"yjs": "13.6.19"
"yjs": "13.6.19",
"@floating-ui/dom": "^1.7.0"
},
"devDependencies": {
"@playwright/test": "^1.51.0",
Expand All @@ -87,6 +88,7 @@
"vite": "^5.4.12",
"vitest": "^1.6.1",
"vue-draggable-next": "^2.2.1",
"which": "^5.0.0"
"which": "^5.0.0",
"@floating-ui/dom": "^1.7.0"
}
}
20 changes: 18 additions & 2 deletions packages/super-editor/src/assets/styles/extensions/pagination.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
:root {
--sd-editor-separator-height: 18px;
}

.pagination-section-header {
cursor: default;
}
Expand All @@ -23,15 +27,27 @@
.pagination-separator {
position: relative;
display: block;
height: 18px;
min-height: 18px;
height: var(--sd-editor-separator-height);
min-height: var(--sd-editor-separator-height);
min-width: 100%;
width: 100%;
border-top: 1px solid #DBDBDB;
border-bottom: 1px solid #DBDBDB;
cursor: default;
}

.pagination-separator--table {
border: 0;
}

.pagination-separator-floating {
position: fixed;
height: var(--sd-editor-separator-height);
border-top: 1px solid #DBDBDB;
border-bottom: 1px solid #DBDBDB;
pointer-events: none;
}

.pagination-inner {
position: absolute;
top: 0;
Expand Down
4 changes: 3 additions & 1 deletion packages/super-editor/src/components/pagination-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ export function adjustPaginationBreaks(editorElem, editor) {
if (!firstLeft) firstLeft = left;
if (left !== firstLeft) {
const diff = left - firstLeft;
// Note: elements with "position: fixed" do not work correctly with transform style.
// node.style.left = `${diff}px`;
node.style.transform = `translateX(${diff}px)`;
}
});
};
};
1 change: 1 addition & 0 deletions packages/super-editor/src/core/Editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ export class Editor extends EventEmitter {
lastSelection: null,
suppressDefaultDocxStyles: false,
jsonOverride: false,
paginationFloatingClass: null,
onBeforeCreate: () => null,
onCreate: () => null,
onUpdate: () => null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,13 @@ onMounted(async () => {
</style>

<style scoped>
.sd-toolbar {
width: 100%;
background: white;
position: relative;
z-index: 1;
}

.page-spacer {
height: 11in;
width: 60px;
Expand All @@ -199,6 +206,8 @@ onMounted(async () => {
}

.dev-app__layout {
display: flex;
flex-direction: column;
width: 100%;
height: 100vh;
}
Expand Down
114 changes: 107 additions & 7 deletions packages/super-editor/src/extensions/pagination/pagination.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,13 @@ import { ImagePlaceholderPluginKey } from '@extensions/image/imageHelpers/imageP
import { LinkedStylesPluginKey } from '@extensions/linked-styles/linked-styles.js';
import { findParentNodeClosestToPos } from '@core/helpers/findParentNodeClosestToPos.js';
import { generateDocxRandomId } from '../../core/helpers/index.js';
import { computePosition, autoUpdate, hide } from '@floating-ui/dom';

const SEPARATOR_CLASS = 'pagination-separator';
const SEPARATOR_FLOATING_CLASS = 'pagination-separator-floating';

const isDebugging = false;
const cleanupFunctions = new Set();

export const Pagination = Extension.create({
name: 'pagination',
Expand Down Expand Up @@ -42,6 +47,7 @@ export const Pagination = Extension.create({
*/
addPmPlugins() {
const editor = this.editor;

let isUpdating = false;

// Used to prevent unnecessary transactions
Expand Down Expand Up @@ -129,7 +135,7 @@ export const Pagination = Extension.create({
let previousDecorations = DecorationSet.empty;

return {
update: (view) => {
update: (view, prevState) => {
if (!shouldUpdate || isUpdating) return;

isUpdating = true;
Expand All @@ -141,6 +147,7 @@ export const Pagination = Extension.create({
*/
if (isDebugging) console.debug('--- Calling performUpdate ---')
performUpdate(editor, view, previousDecorations);

isUpdating = false;
shouldUpdate = false;
},
Expand All @@ -155,6 +162,10 @@ export const Pagination = Extension.create({

return [paginationPlugin];
},

onDestroy() {
cleanupFloatingSeparators();
},
});

/**
Expand Down Expand Up @@ -218,8 +229,9 @@ const getHeaderFooterId = (currentPageNumber, sectionType, editor, node = null)
* @returns {void}
*/
const performUpdate = (editor, view, previousDecorations) => {
const sectionData = editor.storage.pagination.sectionData;
const sectionData = editor.storage.pagination.sectionData;
const newDecorations = calculatePageBreaks(view, editor, sectionData);
const editorElement = editor.options.element;

// Skip updating if decorations haven't changed
if (!previousDecorations.eq(newDecorations)) {
Expand All @@ -229,7 +241,18 @@ const performUpdate = (editor, view, previousDecorations) => {
);

view.dispatch(updateTransaction);
}

requestAnimationFrame(() => {
requestAnimationFrame(() => {
cleanupFloatingSeparators();
const separators = [...editorElement.querySelectorAll(`.${SEPARATOR_CLASS}--table`)];
separators.forEach((separator) => {
const { cleanup } = createFloatingSeparator(separator, editor);
cleanupFunctions.add(cleanup);
});
});
});
};

// Emit that pagination has been updated
editor.emit('paginationUpdate');
Expand Down Expand Up @@ -370,8 +393,11 @@ function generateInternalPageBreaks(doc, view, editor, sectionData) {

if (isHardBreakNode || shouldAddPageBreak) {
const $currentPos = view.state.doc.resolve(currentPos);
const table = findParentNodeClosestToPos($currentPos, (node) => node.type.name === 'table');
const tableRow = findParentNodeClosestToPos($currentPos, (node) => node.type.name === 'tableRow');

let isInTable = (table || tableRow) ? true : false;

if (tableRow) {
// If the node is in a table cell, then split the entire row.
currentNode = tableRow.node;
Expand Down Expand Up @@ -408,7 +434,7 @@ function generateInternalPageBreaks(doc, view, editor, sectionData) {
const pageSpacer = Decoration.widget(breakPos, spacingNode, { key: 'stable-key' });
decorations.push(pageSpacer);

const pageBreak = createPageBreak({ editor, header, footer });
const pageBreak = createPageBreak({ editor, header, footer, isInTable });
decorations.push(Decoration.widget(breakPos, pageBreak, { key: 'stable-key' }));

// Check if we have a hard page break node
Expand Down Expand Up @@ -637,7 +663,7 @@ const onHeaderFooterDblClick = (editor, currentFocusedSectionEditor) => {
* @param {HTMLElement} param0.footer The footer element
* @returns {HTMLElement} The page break element
*/
function createPageBreak({ editor, header, footer, footerBottom = null, isFirstHeader, isLastFooter }) {
function createPageBreak({ editor, header, footer, footerBottom = null, isFirstHeader, isLastFooter, isInTable = false }) {
const { pageSize, pageMargins } = editor.converter.pageStyles;

let sectionHeight = 0;
Expand All @@ -661,9 +687,11 @@ function createPageBreak({ editor, header, footer, footerBottom = null, isFirstH
const separatorHeight = 20;
sectionHeight += separatorHeight;
const separator = document.createElement('div');
separator.className = 'pagination-separator';
separator.classList.add(SEPARATOR_CLASS);
if (isInTable) {
separator.classList.add(`${SEPARATOR_CLASS}--table`);
}
if (isDebugging) separator.style.backgroundColor = 'green';

innerDiv.appendChild(separator);
}

Expand Down Expand Up @@ -731,3 +759,75 @@ const onImageLoad = (editor) => {
editor.view.dispatch(newTr);
});
};

function createFloatingSeparator(separator, editor) {
const floatingSeparator = document.createElement('div');
floatingSeparator.classList.add(SEPARATOR_FLOATING_CLASS);
floatingSeparator.dataset.floatingSeparator = '';

const { paginationFloatingClass } = editor.options;
if (paginationFloatingClass) {
floatingSeparator.classList.add(paginationFloatingClass);
}

document.body.append(floatingSeparator);

const updatePosition = () => {
computePosition(separator, floatingSeparator, {
strategy: 'fixed',
placement: 'top-start',
middleware: [
hide({
padding: {
top: 21,
bottom: 21,
},
}),
{
name: 'copy',
fn: ({ elements }) => {
const rect = elements.reference.getBoundingClientRect();
return {
x: rect.left,
y: rect.top,
data: {
width: rect.width,
height: rect.height,
},
};
},
},
],
}).then(({ x, y, middlewareData }) => {
Object.assign(floatingSeparator.style, {
top: `${y}px`,
left: `${x}px`,
width: `${middlewareData.copy.width}px`,
height: `${middlewareData.copy.height}px`,
visibility: middlewareData.hide?.referenceHidden ? 'hidden' : 'visible',
});
});
};

const cleanup = autoUpdate(
separator,
floatingSeparator,
updatePosition,
// { animationFrame: true },
);

const extendedCleanup = () => {
floatingSeparator?.remove();
cleanup();
};

return {
cleanup: extendedCleanup,
updatePosition,
};
}

function cleanupFloatingSeparators() {
cleanupFunctions.forEach((cleanup) => cleanup());
cleanupFunctions.clear();
}
2 changes: 1 addition & 1 deletion packages/super-editor/vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export default defineConfig(({ mode }) => {
__APP_VERSION__: JSON.stringify(superdocVersion),
},
optimizeDeps: {
exclude: ['yjs', 'tippy.js']
exclude: ['yjs', 'tippy.js', '@floating-ui/dom']
},
build: {
target: 'es2020',
Expand Down
1 change: 1 addition & 0 deletions packages/superdoc/src/SuperDoc.vue
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ const onEditorException = ({ error, editor }) => {
const editorOptions = (doc) => {
const options = {
pagination: proxy.$superdoc.config.pagination,
paginationFloatingClass: proxy.$superdoc.config.paginationFloatingClass,
documentId: doc.id,
user: proxy.$superdoc.user,
users: proxy.$superdoc.users,
Expand Down
1 change: 1 addition & 0 deletions packages/superdoc/src/core/SuperDoc.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ export class SuperDoc extends EventEmitter {
title: 'SuperDoc',
conversations: [],
pagination: false, // Optional: Whether to show pagination in SuperEditors
paginationFloatingClass: null,
isInternal: false,

// toolbar config
Expand Down
Loading
Loading