Skip to content
Merged
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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions examples/vue-custom-mark/public/headphones-solid.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
83 changes: 78 additions & 5 deletions examples/vue-custom-mark/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,70 @@ import { SuperDoc } from '@harbour-enterprises/superdoc';
import UploadFile from '../../shared/vue/UploadFile/UploadFile.vue';
import sampleDocument from '../../shared/data/sample-document.docx?url';

// These custom SVG icons are in our public folder as an example. You should import in the correct way for your project.
import headphonesSVG from '/headphones-solid.svg?raw';
import chevronDownSVG from '/circle-chevron-down-solid.svg?raw';

// This is our custom mark that we are creating for this example
import { CustomMark } from './custom-mark.js';


/** Customization of toolbar buttons with custom button, icon */
const customButton = {
type: 'button',
name: 'insertCustomMark',

// Since this command is already in editor.commands (from the custom-mark extension), we can use the command name directly
command: 'setMyCustomMark',

tooltip: 'Insert Custom Mark',
group: 'center',
icon: headphonesSVG, // You can use a custom icon here
};

const customButtonUsingCustomFunction = {
type: 'button',
name: 'insertCustomMark',

// We can also pass in a function as the command
command: () => {
const id = Math.random().toString(36).substring(2, 7);
return superdoc.value?.activeEditor?.commands.setMyCustomMark(id);
},

tooltip: 'Insert Custom Mark',
group: 'center',
icon: headphonesSVG, // You can use a custom icon here
};

const customDropDown = {
type: 'dropdown',
name: 'customDropdown',
command: ({ item, option }) => {
if (!item || !option) return; // Case where the dropdown is being expanded or collapsed but no option selected

const { key, label } = option; // This is from the options array defined below

// Do something with the selected option here
// For example, we can call a command or a custom function based on the key
console.log(`[customDropDown Selected option: ${label} (key: ${key})`);
if (key === 'custom-mark') {
superdoc.value?.activeEditor?.commands.setMyCustomMark();
} else if (key === 'export-docx') {
exportDocx();
}
},
tooltip: 'Custom Dropdown',
group: 'center',
icon: chevronDownSVG,
hasCaret: true,
suppressActiveHighlight: true,
options: [
{ label: 'Insert Custom Mark', key: 'custom-mark', },
{ label: 'Export to DOCX', key: 'export-docx', },
],
};

const superdoc = shallowRef(null);
const init = (fileToLoad) => {
superdoc.value = new SuperDoc({
Expand All @@ -18,12 +79,25 @@ const init = (fileToLoad) => {
// Load the document if provided, otherwise load the sample document
document: fileToLoad ? { data: fileToLoad } : sampleDocument,

// Initialize the toolbar
toolbar: '#toolbar',
toolbarGroups: ['center'],

editorExtensions: [CustomMark],
onReady: myCustomOnReady,

modules: {

// Customize the toolbar
toolbar: {
selector: '#toolbar',
toulbarGroups: ['center'],

// You can pass in custom buttons here
customButtons: [
customButton,
customButtonUsingCustomFunction,
customDropDown,
],
}

}
});
};

Expand All @@ -38,7 +112,6 @@ const myCustomOnReady = () => {
});
}


/**
* This is an example of how to export the content of the editor to a DOCX file.
* @param { Editor } editor - The editor instance.
Expand Down
2 changes: 1 addition & 1 deletion examples/vue-custom-mark/src/custom-mark.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const CustomMark = Extensions.Mark.create({
addCommands() {
return {
setMyCustomMark: (id) => ({ commands, tr }) => {
if (!id) throw new Error('id is required for my custom mark');
if (!id) id = Math.random().toString(36).substring(2, 7);

// set Mark accepts the attributes that are defined in the addAttributes method
commands.setMark(this.name, { 'data-custom-id': id });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ const closeDropdowns = () => {
const handleSelect = (item, option) => {
closeDropdowns();
const value = item.dropdownValueKey.value ? option[item.dropdownValueKey.value] : option.label;
emit('command', { item, argument: value });
emit('command', { item, argument: value, option });
};

const handleClickOutside = (e) => {
Expand Down
4 changes: 2 additions & 2 deletions packages/super-editor/src/components/toolbar/Toolbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ const onWindowResized = async () => {
};
const onResizeThrottled = throttle(onWindowResized, 300);

const handleCommand = ({ item, argument }) => {
proxy.$toolbar.emitCommand({ item, argument });
const handleCommand = ({ item, argument, option }) => {
proxy.$toolbar.emitCommand({ item, argument, option });
};
</script>

Expand Down
26 changes: 21 additions & 5 deletions packages/super-editor/src/components/toolbar/super-toolbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { toolbarIcons } from './toolbarIcons.js';
import { getQuickFormatList } from '@extensions/linked-styles/linked-styles.js';
import { getAvailableColorOptions, makeColorOption, renderColorOptions } from './color-dropdown-helpers.js';
import { isInTable } from '@helpers/isInTable.js';
import { useToolbarItem } from '@components/toolbar/use-toolbar-item';

/**
* @typedef {Object} ToolbarConfig
Expand All @@ -26,6 +27,7 @@ import { isInTable } from '@helpers/isInTable.js';
* @property {Object} [editor=null] - The editor instance
* @property {string} [aiApiKey=null] - API key for AI integration
* @property {string} [aiEndpoint=null] - Endpoint for AI integration
* @property {ToolbarItem[]} [customButtons=[]] - Custom buttons to add to the toolbar
*/

/**
Expand Down Expand Up @@ -136,6 +138,7 @@ export class SuperToolbar extends EventEmitter {
editor: null,
aiApiKey: null,
aiEndpoint: null,
customButtons: [],
};

/**
Expand Down Expand Up @@ -526,7 +529,7 @@ export class SuperToolbar extends EventEmitter {
* @returns {ToolbarItem[]} An array of toolbar items in the specified group
*/
getToolbarItemByGroup(groupName) {
return this.toolbarItems.filter((item) => item.group.value === groupName);
return this.toolbarItems.filter((item) => (item.group?.value || 'center') === groupName);
}

/**
Expand All @@ -549,6 +552,10 @@ export class SuperToolbar extends EventEmitter {
#makeToolbarItems(superToolbar, icons, isDev = false) {
const documentWidth = document.documentElement.clientWidth; // take into account the scrollbar
const { defaultItems, overflowItems } = makeDefaultItems(superToolbar, isDev, documentWidth, this.role, icons);
const customItems = this.config.customButtons || [];
if (customItems.length) {
defaultItems.push(...customItems.map((item) => useToolbarItem({...item})));
};

let allConfigItems = [
...defaultItems.map((item) => item.name.value),
Expand Down Expand Up @@ -738,15 +745,15 @@ export class SuperToolbar extends EventEmitter {
* @param {*} [params.argument] - The argument passed to the command
* @returns {*} The result of the executed command, undefined if no result is returned
*/
emitCommand({ item, argument }) {
emitCommand({ item, argument, option }) {
this.activeEditor?.focus();
const { command } = item;

if (!command) {
return;
}

this.log('(emmitCommand) Command:', command, item, argument);
this.log('(emmitCommand) Command:', command, '\n\titem:', item, '\n\targument:', argument, '\n\toption:', option);

// Check if we have a custom or overloaded command defined
if (command in this.#interceptedCommands) {
Expand All @@ -755,10 +762,19 @@ export class SuperToolbar extends EventEmitter {

if (command in this.activeEditor?.commands) {
this.activeEditor.commands[command](argument);
this.updateToolbarState();
} else {
}

// If the command is a function, call it with the argument
else if (typeof command === 'function') {
command({ item, argument, option });
}

// If we don't know what to do with this command, throw an error
else {
throw new Error(`[super-toolbar 🎨] Command not found: ${command}`);
}

this.updateToolbarState();
}

/**
Expand Down
Loading