feat(editor): Migrate copyPaste mixin to composables (no-changelog) (#8179)

This commit is contained in:
Alex Grozav 2023-12-29 12:13:24 +02:00 committed by GitHub
parent 216ec079c9
commit f5a4bfe40c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 212 additions and 332 deletions

View file

@ -50,7 +50,6 @@
"chart.js": "^4.4.0",
"codemirror-lang-html-n8n": "^1.0.0",
"codemirror-lang-n8n-expression": "^0.2.0",
"copy-to-clipboard": "^3.3.3",
"dateformat": "^3.0.3",
"esprima-next": "5.8.4",
"fast-json-stable-stringify": "^2.1.0",

View file

@ -23,12 +23,11 @@
<script lang="ts">
import { defineComponent } from 'vue';
import { copyPaste } from '@/mixins/copyPaste';
import { useToast } from '@/composables/useToast';
import { i18n } from '@/plugins/i18n';
import { useClipboard } from '@/composables/useClipboard';
export default defineComponent({
mixins: [copyPaste],
props: {
label: {
type: String,
@ -68,14 +67,17 @@ export default defineComponent({
},
},
setup() {
const clipboard = useClipboard();
return {
clipboard,
...useToast(),
};
},
methods: {
copy(): void {
this.$emit('copy');
this.copyToClipboard(this.value);
void this.clipboard.copy(this.value);
this.showMessage({
title: this.toastTitle,

View file

@ -121,7 +121,6 @@
import { defineComponent } from 'vue';
import { mapStores } from 'pinia';
import VueJsonPretty from 'vue-json-pretty';
import { copyPaste } from '@/mixins/copyPaste';
import { useToast } from '@/composables/useToast';
import { MAX_DISPLAY_DATA_SIZE } from '@/constants';
@ -134,16 +133,19 @@ import type {
import { sanitizeHtml } from '@/utils/htmlUtils';
import { useNDVStore } from '@/stores/ndv.store';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { useClipboard } from '@/composables/useClipboard';
export default defineComponent({
name: 'NodeErrorView',
components: {
VueJsonPretty,
},
mixins: [copyPaste],
props: ['error'],
setup() {
const clipboard = useClipboard();
return {
clipboard,
...useToast(),
};
},
@ -283,7 +285,7 @@ export default defineComponent({
return [currentParameter];
},
copyCause() {
this.copyToClipboard(JSON.stringify(this.error.cause));
void this.clipboard.copy(JSON.stringify(this.error.cause));
this.copySuccess();
},
copySuccess() {

View file

@ -64,7 +64,6 @@
import { defineComponent } from 'vue';
import { mapStores } from 'pinia';
import { useToast } from '@/composables/useToast';
import { copyPaste } from '@/mixins/copyPaste';
import Modal from './Modal.vue';
import type { IFormInputs, IInviteResponse, IUser } from '@/Interface';
import { ROLE } from '@/utils/userUtils';
@ -73,6 +72,7 @@ import { useUsersStore } from '@/stores/users.store';
import { useSettingsStore } from '@/stores/settings.store';
import { useUIStore } from '@/stores/ui.store';
import { createEventBus } from 'n8n-design-system/utils';
import { useClipboard } from '@/composables/useClipboard';
const NAME_EMAIL_FORMAT_REGEX = /^.* <(.*)>$/;
@ -90,14 +90,16 @@ function getEmail(email: string): string {
export default defineComponent({
name: 'InviteUsersModal',
components: { Modal },
mixins: [copyPaste],
props: {
modalName: {
type: String,
},
},
setup() {
const clipboard = useClipboard();
return {
clipboard,
...useToast(),
};
},
@ -258,7 +260,7 @@ export default defineComponent({
if (successfulUrlInvites.length) {
if (successfulUrlInvites.length === 1) {
this.copyToClipboard(successfulUrlInvites[0].user.inviteAcceptUrl);
void this.clipboard.copy(successfulUrlInvites[0].user.inviteAcceptUrl);
}
this.showMessage({
@ -328,7 +330,7 @@ export default defineComponent({
},
onCopyInviteLink(user: IUser) {
if (user.inviteAcceptUrl && this.showInviteUrls) {
this.copyToClipboard(user.inviteAcceptUrl);
void this.clipboard.copy(user.inviteAcceptUrl);
this.showCopyInviteLinkToast([]);
}
},

View file

@ -140,20 +140,22 @@ import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui.store';
import { useNDVStore } from '@/stores/ndv.store';
import { useUsersStore } from '@/stores/users.store';
import { copyPaste } from '@/mixins/copyPaste';
import { mfaEventBus } from '@/event-bus';
import { useToast } from '@/composables/useToast';
//@ts-ignore
import QrcodeVue from 'qrcode.vue';
import { useClipboard } from '@/composables/useClipboard';
export default defineComponent({
name: 'MfaSetupModal',
components: {
Modal,
QrcodeVue,
},
mixins: [copyPaste],
setup() {
const clipboard = useClipboard();
return {
clipboard,
...useToast(),
};
},
@ -199,7 +201,7 @@ export default defineComponent({
});
},
onCopySecretToClipboard() {
this.copyToClipboard(this.secret);
void this.clipboard.copy(this.secret);
this.showToast({
title: this.$locale.baseText('mfa.setup.step1.toast.copyToClipboard.title'),
message: this.$locale.baseText('mfa.setup.step1.toast.copyToClipboard.message'),

View file

@ -66,18 +66,21 @@ import { defineComponent } from 'vue';
import { useToast } from '@/composables/useToast';
import { FORM_TRIGGER_NODE_TYPE, OPEN_URL_PANEL_TRIGGER_NODE_TYPES } from '@/constants';
import { copyPaste } from '@/mixins/copyPaste';
import { workflowHelpers } from '@/mixins/workflowHelpers';
import { useClipboard } from '@/composables/useClipboard';
export default defineComponent({
name: 'NodeWebhooks',
mixins: [copyPaste, workflowHelpers],
mixins: [workflowHelpers],
props: [
'node', // NodeUi
'nodeType', // INodeTypeDescription
],
setup() {
const clipboard = useClipboard();
return {
clipboard,
...useToast(),
};
},
@ -136,7 +139,7 @@ export default defineComponent({
methods: {
copyWebhookUrl(webhookData: IWebhookDescription): void {
const webhookUrl = this.getWebhookUrlDisplay(webhookData);
this.copyToClipboard(webhookUrl);
void this.clipboard.copy(webhookUrl);
this.showMessage({
title: this.baseText.copyTitle,

View file

@ -51,7 +51,7 @@
type="secondary"
:title="$locale.baseText('nodeErrorView.copyToClipboard')"
icon="copy"
@click="copyToClipboard(raw)"
@click="onCopyToClipboard(raw)"
/>
<VueMarkdown :source="jsonToMarkdown(raw as JsonMarkdown)" :class="$style.markdown" />
</div>
@ -68,7 +68,7 @@ import { ref, onMounted } from 'vue';
import type { ParsedAiContent } from './useAiContentParsers';
import { useAiContentParsers } from './useAiContentParsers';
import VueMarkdown from 'vue-markdown-render';
import { useCopyToClipboard } from '@/composables/useCopyToClipboard';
import { useClipboard } from '@/composables/useClipboard';
import { useI18n } from '@/composables/useI18n';
import { useToast } from '@/composables/useToast';
import { NodeConnectionType, type IDataObject } from 'n8n-workflow';
@ -78,13 +78,15 @@ const props = defineProps<{
}>();
const i18n = useI18n();
const clipboard = useClipboard();
const { showMessage } = useToast();
const contentParsers = useAiContentParsers();
// eslint-disable-next-line @typescript-eslint/no-use-before-define
const isExpanded = ref(getInitialExpandedState());
const isShowRaw = ref(false);
const contentParsed = ref(false);
const parsedRun = ref(undefined as ParsedAiContent | undefined);
function getInitialExpandedState() {
const collapsedTypes = {
input: [NodeConnectionType.AiDocument, NodeConnectionType.AiTextSplitter],
@ -155,12 +157,9 @@ function onBlockHeaderClick() {
isExpanded.value = !isExpanded.value;
}
function copyToClipboard(content: IDataObject | IDataObject[]) {
const copyToClipboardFn = useCopyToClipboard();
const { showMessage } = useToast();
function onCopyToClipboard(content: IDataObject | IDataObject[]) {
try {
copyToClipboardFn(JSON.stringify(content, undefined, 2));
void clipboard.copy(JSON.stringify(content, undefined, 2));
showMessage({
title: i18n.baseText('generic.copiedToClipboard'),
type: 'success',

View file

@ -41,7 +41,6 @@ import { mapStores } from 'pinia';
import jp from 'jsonpath';
import type { INodeUi } from '@/Interface';
import type { IDataObject } from 'n8n-workflow';
import { copyPaste } from '@/mixins/copyPaste';
import { pinData } from '@/mixins/pinData';
import { genericHelpers } from '@/mixins/genericHelpers';
import { clearJsonKey, convertPath } from '@/utils/typesUtils';
@ -52,6 +51,7 @@ import { useNodeHelpers } from '@/composables/useNodeHelpers';
import { useToast } from '@/composables/useToast';
import { useI18n } from '@/composables/useI18n';
import { nonExistingJsonPath } from '@/constants';
import { useClipboard } from '@/composables/useClipboard';
type JsonPathData = {
path: string;
@ -60,8 +60,7 @@ type JsonPathData = {
export default defineComponent({
name: 'RunDataJsonActions',
mixins: [genericHelpers, pinData, copyPaste],
mixins: [genericHelpers, pinData],
props: {
node: {
type: Object as PropType<INodeUi>,
@ -96,9 +95,12 @@ export default defineComponent({
setup() {
const i18n = useI18n();
const nodeHelpers = useNodeHelpers();
const clipboard = useClipboard();
return {
i18n,
nodeHelpers,
clipboard,
...useToast(),
};
},
@ -222,7 +224,7 @@ export default defineComponent({
in_execution_log: this.isReadOnlyRoute,
});
this.copyToClipboard(value);
void this.clipboard.copy(value);
},
},
});

View file

@ -134,7 +134,7 @@ const getIconBySchemaType = (type: Schema['type']): string => {
</template>
<style lang="scss" module>
@import '@/styles/css-animation-helpers.scss';
@import '@/styles/variables';
.item {
display: block;

View file

@ -91,7 +91,7 @@ onUnmounted(() => {
</template>
<style lang="scss" module>
@import '@/styles/css-animation-helpers.scss';
@import '@/styles/variables';
.ioSearch {
margin-right: var(--spacing-s);

View file

@ -117,16 +117,12 @@ import NodeExecuteButton from '@/components/NodeExecuteButton.vue';
import { workflowHelpers } from '@/mixins/workflowHelpers';
import CopyInput from '@/components/CopyInput.vue';
import NodeIcon from '@/components/NodeIcon.vue';
import { copyPaste } from '@/mixins/copyPaste';
import { useUIStore } from '@/stores/ui.store';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { useNDVStore } from '@/stores/ndv.store';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import type { N8nInfoAccordion } from 'n8n-design-system';
import { createEventBus } from 'n8n-design-system/utils';
type HelpRef = InstanceType<typeof N8nInfoAccordion>;
export default defineComponent({
name: 'TriggerPanel',
components: {
@ -134,7 +130,7 @@ export default defineComponent({
CopyInput,
NodeIcon,
},
mixins: [workflowHelpers, copyPaste],
mixins: [workflowHelpers],
props: {
nodeName: {
type: String,

View file

@ -4,14 +4,14 @@ import { computed, nextTick, onMounted, ref, watch } from 'vue';
import type { EnvironmentVariable, Rule, RuleGroup } from '@/Interface';
import { useI18n } from '@/composables/useI18n';
import { useToast } from '@/composables/useToast';
import { useCopyToClipboard } from '@/composables/useCopyToClipboard';
import { useClipboard } from '@/composables/useClipboard';
import { EnterpriseEditionFeature } from '@/constants';
import { useSettingsStore } from '@/stores/settings.store';
import { useUsersStore } from '@/stores/users.store';
import { getVariablesPermissions } from '@/permissions';
const i18n = useI18n();
const copyToClipboard = useCopyToClipboard();
const clipboard = useClipboard();
const { showMessage } = useToast();
const settingsStore = useSettingsStore();
const usersStore = useUsersStore();
@ -120,7 +120,7 @@ function onValidate(key: string, value: boolean) {
}
function onUsageClick() {
copyToClipboard(usage.value);
void clipboard.copy(usage.value);
showMessage({
title: i18n.baseText('variables.row.usage.copiedToClipboard'),
type: 'success',

View file

@ -9,7 +9,7 @@
v-for="item in props.items"
:key="item.address"
:class="$style.accordionItem"
@click="copyToClipboard(item.address)"
@click="onCopyToClipboard(item.address)"
>
{{ item.family }}: <span :class="$style.clickable">{{ item.address }}</span>
{{ item.internal ? '(internal)' : '' }}
@ -22,7 +22,7 @@
<script setup lang="ts">
import type { IPushDataWorkerStatusPayload } from '@/Interface';
import WorkerAccordion from './WorkerAccordion.ee.vue';
import { useCopyToClipboard } from '@/composables/useCopyToClipboard';
import { useClipboard } from '@/composables/useClipboard';
import { useI18n } from '@/composables/useI18n';
import { useToast } from '@/composables/useToast';
@ -31,13 +31,12 @@ const props = defineProps<{
}>();
const i18n = useI18n();
const clipboard = useClipboard();
const { showMessage } = useToast();
function copyToClipboard(content: string) {
const copyToClipboardFn = useCopyToClipboard();
const { showMessage } = useToast();
function onCopyToClipboard(content: string) {
try {
copyToClipboardFn(content);
void clipboard.copy(content);
showMessage({
title: i18n.baseText('workerList.item.copyAddressToClipboard'),
type: 'success',

View file

@ -0,0 +1,54 @@
import { render } from '@testing-library/vue';
import userEvent from '@testing-library/user-event';
import { defineComponent, h, ref } from 'vue';
import { useClipboard } from '@/composables/useClipboard';
const testValue = 'This is a test';
const TestComponent = defineComponent({
setup() {
const pasted = ref('');
const clipboard = useClipboard({
onPaste(data) {
pasted.value = data;
},
});
return () =>
h('div', [
h('button', {
'data-test-id': 'copy',
onClick: () => {
void clipboard.copy(testValue);
},
}),
h('div', { 'data-test-id': 'paste' }, pasted.value),
]);
},
});
describe('useClipboard()', () => {
beforeAll(() => {
userEvent.setup();
});
describe('copy()', () => {
it('should copy text value', async () => {
const { getByTestId } = render(TestComponent);
const copyButton = getByTestId('copy');
copyButton.click();
expect((window.navigator.clipboard as unknown as { items: string[] }).items).toHaveLength(1);
});
});
describe('onClipboardPasteEvent()', () => {
it('should trigger on clipboard paste event', async () => {
const { getByTestId } = render(TestComponent);
const pasteElement = getByTestId('paste');
await userEvent.paste(testValue);
expect(pasteElement.textContent).toEqual(testValue);
});
});
});

View file

@ -0,0 +1,80 @@
import { onBeforeUnmount, onMounted, ref } from 'vue';
import { debounce } from 'lodash-es';
import { useClipboard as useClipboardCore } from '@vueuse/core';
type ClipboardEventFn = (data: string, event?: ClipboardEvent) => void;
export function useClipboard(
options: {
onPaste: ClipboardEventFn;
} = {
onPaste() {},
},
) {
const { copy, copied, isSupported, text } = useClipboardCore();
const ignoreClasses = ['el-messsage-box', 'ignore-key-press'];
const initialized = ref(false);
const onPasteCallback = ref<ClipboardEventFn | null>(options.onPaste || null);
/**
* Handles copy/paste events
* @param event
*/
function onPaste(event: ClipboardEvent) {
if (!onPasteCallback.value) {
return;
}
// Check if the event got emitted from a message box or from something
// else which should ignore the copy/paste
const path = event.composedPath?.() as HTMLElement[];
for (const pathElement of path) {
if (
pathElement.className &&
ignoreClasses.some((className) => pathElement.className.includes?.(className))
) {
return;
}
}
const clipboardData = event.clipboardData;
if (clipboardData !== null) {
const clipboardValue = clipboardData.getData('text/plain');
onPasteCallback.value(clipboardValue, event);
}
}
const debouncedOnPaste = debounce(onPaste, 1000, { leading: true });
/**
* Initialize copy/paste elements and events
*/
onMounted(() => {
if (initialized.value) {
return;
}
document.addEventListener('paste', debouncedOnPaste);
initialized.value = true;
});
/**
* Remove copy/paste elements and events
*/
onBeforeUnmount(() => {
if (initialized.value) {
document.removeEventListener('paste', debouncedOnPaste);
}
});
return {
copy,
copied,
isSupported,
text,
onPaste: onPasteCallback,
};
}

View file

@ -1,5 +0,0 @@
import copyToClipboard from 'copy-to-clipboard';
export function useCopyToClipboard(): (text: string) => void {
return copyToClipboard;
}

View file

@ -5,7 +5,6 @@ import '@jsplumb/browser-ui/css/jsplumbtoolkit.css';
import 'n8n-design-system/css/index.scss';
import './n8n-theme.scss';
import './styles/autocomplete-theme.scss';
import '@fontsource/open-sans/latin-400.css';
import '@fontsource/open-sans/latin-600.css';

View file

@ -1,242 +0,0 @@
/**
* Captures any pasted data and sends it to method "receivedCopyPasteData" which has to be
* defined on the component which uses this mixin
*/
import { defineComponent } from 'vue';
import { debounce } from 'lodash-es';
export const copyPaste = defineComponent({
data() {
return {
copyPasteElementsGotCreated: false,
hiddenInput: null as null | Element,
onPaste: null as null | Function,
onBeforePaste: null as null | Function,
};
},
mounted() {
if (this.copyPasteElementsGotCreated) {
return;
}
this.copyPasteElementsGotCreated = true;
// Define the style of the html elements that get created to make
// sure that they are not visible
const style = document.createElement('style');
style.type = 'text/css';
style.innerHTML = `
.hidden-copy-paste {
position: absolute;
bottom: 0;
left: 0;
width: 10px;
height: 10px;
display: block;
font-size: 1px;
z-index: -1;
color: transparent;
background: transparent;
overflow: hidden;
border: none;
padding: 0;
resize: none;
outline: none;
-webkit-user-select: text;
user-select: text;
}
`;
document.getElementsByTagName('head')[0].appendChild(style);
// Code is mainly from
// https://www.lucidchart.com/techblog/2014/12/02/definitive-guide-copying-pasting-javascript/
const isSafari =
navigator.appVersion.search('Safari') !== -1 &&
navigator.appVersion.search('Chrome') === -1 &&
navigator.appVersion.search('CrMo') === -1 &&
navigator.appVersion.search('CriOS') === -1;
const isIe =
navigator.userAgent.toLowerCase().indexOf('msie') !== -1 ||
navigator.userAgent.toLowerCase().indexOf('trident') !== -1;
const hiddenInput = document.createElement('input');
hiddenInput.setAttribute('type', 'text');
hiddenInput.setAttribute('id', 'hidden-input-copy-paste');
hiddenInput.setAttribute('class', 'hidden-copy-paste');
hiddenInput.setAttribute('data-test-id', 'hidden-copy-paste');
this.hiddenInput = hiddenInput;
document.body.append(hiddenInput);
let ieClipboardDiv: HTMLDivElement | null = null;
if (isIe) {
ieClipboardDiv = document.createElement('div');
ieClipboardDiv.setAttribute('id', 'hidden-ie-clipboard-copy-paste');
ieClipboardDiv.setAttribute('class', 'hidden-copy-paste');
ieClipboardDiv.setAttribute('contenteditable', 'true');
document.body.append(ieClipboardDiv);
this.onBeforePaste = () => {
// @ts-ignore
if (hiddenInput.is(':focus')) {
this.focusIeClipboardDiv(ieClipboardDiv as HTMLDivElement);
}
};
// @ts-ignore
document.addEventListener('beforepaste', this.onBeforePaste, true);
}
let userInput = '';
const hiddenInputListener = (text: string) => {};
hiddenInput.addEventListener('input', (e) => {
const value = hiddenInput.value;
userInput += value;
hiddenInputListener(userInput);
// There is a bug (sometimes) with Safari and the input area can't be updated during
// the input event, so we update the input area after the event is done being processed
if (isSafari) {
hiddenInput.focus();
setTimeout(() => {
this.focusHiddenArea(hiddenInput);
}, 0);
} else {
this.focusHiddenArea(hiddenInput);
}
});
this.onPaste = debounce(
(e) => {
const event = 'paste';
// Check if the event got emitted from a message box or from something
// else which should ignore the copy/paste
// @ts-ignore
const path = e.path || e.composedPath?.();
for (let index = 0; index < path.length; index++) {
if (
path[index].className &&
typeof path[index].className === 'string' &&
(path[index].className.includes('el-message-box') ||
path[index].className.includes('ignore-key-press'))
) {
return;
}
}
if (ieClipboardDiv !== null) {
this.ieClipboardEvent(event, ieClipboardDiv);
} else {
this.standardClipboardEvent(event, e as ClipboardEvent);
// @ts-ignore
if (
!document.activeElement ||
(document.activeElement &&
['textarea', 'text', 'email', 'password'].indexOf(document.activeElement.type) === -1)
) {
// That it still allows to paste into text, email, password & textarea-fields we
// check if we can identify the active element and if so only
// run it if something else is selected.
this.focusHiddenArea(hiddenInput);
e.preventDefault();
}
}
},
1000,
{ leading: true },
);
// Set clipboard event listeners on the document.
// @ts-ignore
document.addEventListener('paste', this.onPaste);
},
beforeUnmount() {
if (this.hiddenInput) {
this.hiddenInput.remove();
}
if (this.onPaste) {
// @ts-ignore
document.removeEventListener('paste', this.onPaste);
}
if (this.onBeforePaste) {
// @ts-ignore
document.removeEventListener('beforepaste', this.onBeforePaste);
}
},
methods: {
receivedCopyPasteData(plainTextData: string, event?: ClipboardEvent): void {
// THIS HAS TO BE DEFINED IN COMPONENT!
},
// For every browser except IE, we can easily get and set data on the clipboard
standardClipboardEvent(clipboardEventName: string, event: ClipboardEvent) {
const clipboardData = event.clipboardData;
if (clipboardData !== null && clipboardEventName === 'paste') {
const clipboardText = clipboardData.getData('text/plain');
this.receivedCopyPasteData(clipboardText, event);
}
},
// For IE, we can get/set Text or URL just as we normally would
ieClipboardEvent(clipboardEventName: string, ieClipboardDiv: HTMLDivElement) {
// @ts-ignore
const clipboardData = window.clipboardData;
if (clipboardEventName === 'paste') {
const clipboardText = clipboardData.getData('Text');
// @ts-ignore
ieClipboardDiv.empty();
this.receivedCopyPasteData(clipboardText);
}
},
// Focuses an element to be ready for copy/paste (used exclusively for IE)
focusIeClipboardDiv(ieClipboardDiv: HTMLDivElement) {
ieClipboardDiv.focus();
const range = document.createRange();
// @ts-ignore
range.selectNodeContents(ieClipboardDiv.get(0));
const selection = window.getSelection();
if (selection !== null) {
selection.removeAllRanges();
selection.addRange(range);
}
},
focusHiddenArea(hiddenInput: HTMLInputElement) {
// In order to ensure that the browser will fire clipboard events, we always need to have something selected
hiddenInput.value = ' ';
hiddenInput.focus();
hiddenInput.select();
},
/**
* Copies given data to clipboard
*/
copyToClipboard(value: string): void {
// FROM: https://hackernoon.com/copying-text-to-clipboard-with-javascript-df4d4988697f
const element = document.createElement('textarea'); // Create a <textarea> element
element.value = value; // Set its value to the string that you want copied
element.setAttribute('readonly', ''); // Make it readonly to be tamper-proof
element.style.position = 'absolute';
element.style.left = '-9999px'; // Move outside the screen to make it invisible
document.body.appendChild(element); // Append the <textarea> element to the HTML document
const selection = document.getSelection();
if (selection === null) {
return;
}
const selected =
selection.rangeCount > 0 // Check if there is any content selected previously
? selection.getRangeAt(0) // Store selection if found
: false; // Mark as false to know no selection existed before
element.select(); // Select the <textarea> content
document.execCommand('copy'); // Copy - only works as a result of a user action (e.g. click events)
document.body.removeChild(element); // Remove the <textarea> element
if (selected) {
// If a selection existed before copying
selection.removeAllRanges(); // Unselect everything on the HTML document
selection.addRange(selected); // Restore the original selection
}
},
},
});

View file

@ -1,3 +1,5 @@
@import 'styles/plugins';
:root {
--node-type-background-l: 95%;

View file

@ -1031,10 +1031,10 @@
"nodeView.cantExecuteNoTrigger": "Cannot execute workflow",
"nodeView.canvasAddButton.addATriggerNodeBeforeExecuting": "Add a Trigger Node before executing the workflow",
"nodeView.canvasAddButton.addFirstStep": "Add first step…",
"nodeView.confirmMessage.receivedCopyPasteData.cancelButtonText": "",
"nodeView.confirmMessage.receivedCopyPasteData.confirmButtonText": "Yes, import",
"nodeView.confirmMessage.receivedCopyPasteData.headline": "Import Workflow?",
"nodeView.confirmMessage.receivedCopyPasteData.message": "Workflow will be imported from<br /><i>{plainTextData}<i>",
"nodeView.confirmMessage.onClipboardPasteEvent.cancelButtonText": "",
"nodeView.confirmMessage.onClipboardPasteEvent.confirmButtonText": "Yes, import",
"nodeView.confirmMessage.onClipboardPasteEvent.headline": "Import Workflow?",
"nodeView.confirmMessage.onClipboardPasteEvent.message": "Workflow will be imported from<br /><i>{plainTextData}<i>",
"nodeView.confirmMessage.debug.cancelButtonText": "Cancel",
"nodeView.confirmMessage.debug.confirmButtonText": "Unpin",
"nodeView.confirmMessage.debug.headline": "Unpin workflow data",

View file

@ -0,0 +1 @@
@import "codemirror";

View file

@ -239,7 +239,6 @@ import {
UPDATE_WEBHOOK_ID_NODE_TYPES,
TIME,
} from '@/constants';
import { copyPaste } from '@/mixins/copyPaste';
import { genericHelpers } from '@/mixins/genericHelpers';
import { moveNodeWorkflow } from '@/mixins/moveNodeWorkflow';
@ -372,6 +371,7 @@ import { sourceControlEventBus } from '@/event-bus/source-control';
import { getConnectorPaintStyleData, OVERLAY_ENDPOINT_ARROW_ID } from '@/utils/nodeViewUtils';
import { useViewStacks } from '@/components/Node/NodeCreator/composables/useViewStacks';
import { useExternalHooks } from '@/composables/useExternalHooks';
import { useClipboard } from '@/composables/useClipboard';
interface AddNodeOptions {
position?: XYPosition;
@ -394,15 +394,7 @@ export default defineComponent({
CanvasControls,
ContextMenu,
},
mixins: [
copyPaste,
genericHelpers,
moveNodeWorkflow,
workflowHelpers,
workflowRun,
debounceHelper,
pinData,
],
mixins: [genericHelpers, moveNodeWorkflow, workflowHelpers, workflowRun, debounceHelper, pinData],
async beforeRouteLeave(to, from, next) {
if (
getNodeViewTab(to) === MAIN_HEADER_TABS.EXECUTIONS ||
@ -469,6 +461,7 @@ export default defineComponent({
const contextMenu = useContextMenu();
const dataSchema = useDataSchema();
const nodeHelpers = useNodeHelpers();
const clipboard = useClipboard();
return {
locale,
@ -476,6 +469,7 @@ export default defineComponent({
dataSchema,
nodeHelpers,
externalHooks,
clipboard,
...useCanvasMouseSelect(),
...useGlobalLinkActions(),
...useTitleChange(),
@ -756,6 +750,8 @@ export default defineComponent({
this.titleReset();
window.addEventListener('message', this.onPostMessageReceived);
this.clipboard.onPaste.value = this.onClipboardPasteEvent;
this.startLoading();
const loadPromises = [
this.loadActiveWorkflows(),
@ -1823,7 +1819,7 @@ export default defineComponent({
const nodeData = JSON.stringify(workflowToCopy, null, 2);
this.copyToClipboard(nodeData);
this.clipboard.copy(nodeData);
if (data.nodes.length > 0) {
if (!isCut) {
this.showMessage({
@ -1928,7 +1924,7 @@ export default defineComponent({
/**
* This method gets called when data got pasted into the window
*/
async receivedCopyPasteData(plainTextData: string): Promise<void> {
async onClipboardPasteEvent(plainTextData: string): Promise<void> {
if (this.readOnlyEnv) {
return;
}
@ -1948,17 +1944,17 @@ export default defineComponent({
}
const importConfirm = await this.confirm(
this.$locale.baseText('nodeView.confirmMessage.receivedCopyPasteData.message', {
this.$locale.baseText('nodeView.confirmMessage.onClipboardPasteEvent.message', {
interpolate: { plainTextData },
}),
this.$locale.baseText('nodeView.confirmMessage.receivedCopyPasteData.headline'),
this.$locale.baseText('nodeView.confirmMessage.onClipboardPasteEvent.headline'),
{
type: 'warning',
confirmButtonText: this.$locale.baseText(
'nodeView.confirmMessage.receivedCopyPasteData.confirmButtonText',
'nodeView.confirmMessage.onClipboardPasteEvent.confirmButtonText',
),
cancelButtonText: this.$locale.baseText(
'nodeView.confirmMessage.receivedCopyPasteData.cancelButtonText',
'nodeView.confirmMessage.onClipboardPasteEvent.cancelButtonText',
),
dangerouslyUseHTMLString: true,
},

View file

@ -240,7 +240,7 @@ const openPricingPage = () => {
</template>
<style lang="scss" module>
@import '@/styles/css-animation-helpers.scss';
@import '@/styles/variables';
.center > div {
justify-content: center;

View file

@ -91,7 +91,6 @@ import { EnterpriseEditionFeature, INVITE_USER_MODAL_KEY, VIEWS } from '@/consta
import type { IUser, IUserListAction } from '@/Interface';
import { useToast } from '@/composables/useToast';
import { copyPaste } from '@/mixins/copyPaste';
import { useUIStore } from '@/stores/ui.store';
import { useSettingsStore } from '@/stores/settings.store';
import { useUsersStore } from '@/stores/users.store';
@ -99,12 +98,15 @@ import { useUsageStore } from '@/stores/usage.store';
import { useSSOStore } from '@/stores/sso.store';
import { hasPermission } from '@/rbac/permissions';
import { ROLE } from '@/utils/userUtils';
import { useClipboard } from '@/composables/useClipboard';
export default defineComponent({
name: 'SettingsUsersView',
mixins: [copyPaste],
setup() {
const clipboard = useClipboard();
return {
clipboard,
...useToast(),
};
},
@ -222,7 +224,7 @@ export default defineComponent({
async onCopyInviteLink(userId: string) {
const user = this.usersStore.getUserById(userId);
if (user?.inviteAcceptUrl) {
this.copyToClipboard(user.inviteAcceptUrl);
void this.clipboard.copy(user.inviteAcceptUrl);
this.showToast({
type: 'success',
@ -235,7 +237,7 @@ export default defineComponent({
const user = this.usersStore.getUserById(userId);
if (user) {
const url = await this.usersStore.getUserPasswordResetLink(user);
this.copyToClipboard(url.link);
void this.clipboard.copy(url.link);
this.showToast({
type: 'success',

View file

@ -1035,9 +1035,6 @@ importers:
codemirror-lang-n8n-expression:
specifier: ^0.2.0
version: 0.2.0(@codemirror/state@6.3.3)(@codemirror/view@6.22.3)(@lezer/common@1.1.0)
copy-to-clipboard:
specifier: ^3.3.3
version: 3.3.3
dateformat:
specifier: ^3.0.3
version: 3.0.3
@ -13362,12 +13359,6 @@ packages:
is-plain-object: 5.0.0
dev: true
/copy-to-clipboard@3.3.3:
resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==}
dependencies:
toggle-selection: 1.0.6
dev: false
/core-js-compat@3.32.0:
resolution: {integrity: sha512-7a9a3D1k4UCVKnLhrgALyFcP7YCsLOQIxPd0dKjf/6GuPcgyiGP70ewWdCGrSK7evyhymi0qO4EqCmSJofDeYw==}
dependencies:
@ -24525,10 +24516,6 @@ packages:
resolution: {integrity: sha512-vXk8htr8mIl3hc2s2mDkaPTBfqmqZA2o0x7eXbxUibdrpEIPdpM0L9hH/RvEvlgSM+ZTgS34sGipk5+VrLJCLA==}
dev: true
/toggle-selection@1.0.6:
resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==}
dev: false
/toidentifier@1.0.1:
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
engines: {node: '>=0.6'}