mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
feat(editor): Migrate copyPaste mixin to composables (no-changelog) (#8179)
This commit is contained in:
parent
216ec079c9
commit
f5a4bfe40c
|
@ -50,7 +50,6 @@
|
||||||
"chart.js": "^4.4.0",
|
"chart.js": "^4.4.0",
|
||||||
"codemirror-lang-html-n8n": "^1.0.0",
|
"codemirror-lang-html-n8n": "^1.0.0",
|
||||||
"codemirror-lang-n8n-expression": "^0.2.0",
|
"codemirror-lang-n8n-expression": "^0.2.0",
|
||||||
"copy-to-clipboard": "^3.3.3",
|
|
||||||
"dateformat": "^3.0.3",
|
"dateformat": "^3.0.3",
|
||||||
"esprima-next": "5.8.4",
|
"esprima-next": "5.8.4",
|
||||||
"fast-json-stable-stringify": "^2.1.0",
|
"fast-json-stable-stringify": "^2.1.0",
|
||||||
|
|
|
@ -23,12 +23,11 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import { copyPaste } from '@/mixins/copyPaste';
|
|
||||||
import { useToast } from '@/composables/useToast';
|
import { useToast } from '@/composables/useToast';
|
||||||
import { i18n } from '@/plugins/i18n';
|
import { i18n } from '@/plugins/i18n';
|
||||||
|
import { useClipboard } from '@/composables/useClipboard';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
mixins: [copyPaste],
|
|
||||||
props: {
|
props: {
|
||||||
label: {
|
label: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -68,14 +67,17 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
|
const clipboard = useClipboard();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
clipboard,
|
||||||
...useToast(),
|
...useToast(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
copy(): void {
|
copy(): void {
|
||||||
this.$emit('copy');
|
this.$emit('copy');
|
||||||
this.copyToClipboard(this.value);
|
void this.clipboard.copy(this.value);
|
||||||
|
|
||||||
this.showMessage({
|
this.showMessage({
|
||||||
title: this.toastTitle,
|
title: this.toastTitle,
|
||||||
|
|
|
@ -121,7 +121,6 @@
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import { mapStores } from 'pinia';
|
import { mapStores } from 'pinia';
|
||||||
import VueJsonPretty from 'vue-json-pretty';
|
import VueJsonPretty from 'vue-json-pretty';
|
||||||
import { copyPaste } from '@/mixins/copyPaste';
|
|
||||||
import { useToast } from '@/composables/useToast';
|
import { useToast } from '@/composables/useToast';
|
||||||
import { MAX_DISPLAY_DATA_SIZE } from '@/constants';
|
import { MAX_DISPLAY_DATA_SIZE } from '@/constants';
|
||||||
|
|
||||||
|
@ -134,16 +133,19 @@ import type {
|
||||||
import { sanitizeHtml } from '@/utils/htmlUtils';
|
import { sanitizeHtml } from '@/utils/htmlUtils';
|
||||||
import { useNDVStore } from '@/stores/ndv.store';
|
import { useNDVStore } from '@/stores/ndv.store';
|
||||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||||
|
import { useClipboard } from '@/composables/useClipboard';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'NodeErrorView',
|
name: 'NodeErrorView',
|
||||||
components: {
|
components: {
|
||||||
VueJsonPretty,
|
VueJsonPretty,
|
||||||
},
|
},
|
||||||
mixins: [copyPaste],
|
|
||||||
props: ['error'],
|
props: ['error'],
|
||||||
setup() {
|
setup() {
|
||||||
|
const clipboard = useClipboard();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
clipboard,
|
||||||
...useToast(),
|
...useToast(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -283,7 +285,7 @@ export default defineComponent({
|
||||||
return [currentParameter];
|
return [currentParameter];
|
||||||
},
|
},
|
||||||
copyCause() {
|
copyCause() {
|
||||||
this.copyToClipboard(JSON.stringify(this.error.cause));
|
void this.clipboard.copy(JSON.stringify(this.error.cause));
|
||||||
this.copySuccess();
|
this.copySuccess();
|
||||||
},
|
},
|
||||||
copySuccess() {
|
copySuccess() {
|
||||||
|
|
|
@ -64,7 +64,6 @@
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import { mapStores } from 'pinia';
|
import { mapStores } from 'pinia';
|
||||||
import { useToast } from '@/composables/useToast';
|
import { useToast } from '@/composables/useToast';
|
||||||
import { copyPaste } from '@/mixins/copyPaste';
|
|
||||||
import Modal from './Modal.vue';
|
import Modal from './Modal.vue';
|
||||||
import type { IFormInputs, IInviteResponse, IUser } from '@/Interface';
|
import type { IFormInputs, IInviteResponse, IUser } from '@/Interface';
|
||||||
import { ROLE } from '@/utils/userUtils';
|
import { ROLE } from '@/utils/userUtils';
|
||||||
|
@ -73,6 +72,7 @@ import { useUsersStore } from '@/stores/users.store';
|
||||||
import { useSettingsStore } from '@/stores/settings.store';
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
import { useUIStore } from '@/stores/ui.store';
|
||||||
import { createEventBus } from 'n8n-design-system/utils';
|
import { createEventBus } from 'n8n-design-system/utils';
|
||||||
|
import { useClipboard } from '@/composables/useClipboard';
|
||||||
|
|
||||||
const NAME_EMAIL_FORMAT_REGEX = /^.* <(.*)>$/;
|
const NAME_EMAIL_FORMAT_REGEX = /^.* <(.*)>$/;
|
||||||
|
|
||||||
|
@ -90,14 +90,16 @@ function getEmail(email: string): string {
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'InviteUsersModal',
|
name: 'InviteUsersModal',
|
||||||
components: { Modal },
|
components: { Modal },
|
||||||
mixins: [copyPaste],
|
|
||||||
props: {
|
props: {
|
||||||
modalName: {
|
modalName: {
|
||||||
type: String,
|
type: String,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
|
const clipboard = useClipboard();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
clipboard,
|
||||||
...useToast(),
|
...useToast(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -258,7 +260,7 @@ export default defineComponent({
|
||||||
|
|
||||||
if (successfulUrlInvites.length) {
|
if (successfulUrlInvites.length) {
|
||||||
if (successfulUrlInvites.length === 1) {
|
if (successfulUrlInvites.length === 1) {
|
||||||
this.copyToClipboard(successfulUrlInvites[0].user.inviteAcceptUrl);
|
void this.clipboard.copy(successfulUrlInvites[0].user.inviteAcceptUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.showMessage({
|
this.showMessage({
|
||||||
|
@ -328,7 +330,7 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
onCopyInviteLink(user: IUser) {
|
onCopyInviteLink(user: IUser) {
|
||||||
if (user.inviteAcceptUrl && this.showInviteUrls) {
|
if (user.inviteAcceptUrl && this.showInviteUrls) {
|
||||||
this.copyToClipboard(user.inviteAcceptUrl);
|
void this.clipboard.copy(user.inviteAcceptUrl);
|
||||||
this.showCopyInviteLinkToast([]);
|
this.showCopyInviteLinkToast([]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -140,20 +140,22 @@ import { mapStores } from 'pinia';
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
import { useUIStore } from '@/stores/ui.store';
|
||||||
import { useNDVStore } from '@/stores/ndv.store';
|
import { useNDVStore } from '@/stores/ndv.store';
|
||||||
import { useUsersStore } from '@/stores/users.store';
|
import { useUsersStore } from '@/stores/users.store';
|
||||||
import { copyPaste } from '@/mixins/copyPaste';
|
|
||||||
import { mfaEventBus } from '@/event-bus';
|
import { mfaEventBus } from '@/event-bus';
|
||||||
import { useToast } from '@/composables/useToast';
|
import { useToast } from '@/composables/useToast';
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
import QrcodeVue from 'qrcode.vue';
|
import QrcodeVue from 'qrcode.vue';
|
||||||
|
import { useClipboard } from '@/composables/useClipboard';
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'MfaSetupModal',
|
name: 'MfaSetupModal',
|
||||||
components: {
|
components: {
|
||||||
Modal,
|
Modal,
|
||||||
QrcodeVue,
|
QrcodeVue,
|
||||||
},
|
},
|
||||||
mixins: [copyPaste],
|
|
||||||
setup() {
|
setup() {
|
||||||
|
const clipboard = useClipboard();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
clipboard,
|
||||||
...useToast(),
|
...useToast(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -199,7 +201,7 @@ export default defineComponent({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onCopySecretToClipboard() {
|
onCopySecretToClipboard() {
|
||||||
this.copyToClipboard(this.secret);
|
void this.clipboard.copy(this.secret);
|
||||||
this.showToast({
|
this.showToast({
|
||||||
title: this.$locale.baseText('mfa.setup.step1.toast.copyToClipboard.title'),
|
title: this.$locale.baseText('mfa.setup.step1.toast.copyToClipboard.title'),
|
||||||
message: this.$locale.baseText('mfa.setup.step1.toast.copyToClipboard.message'),
|
message: this.$locale.baseText('mfa.setup.step1.toast.copyToClipboard.message'),
|
||||||
|
|
|
@ -66,18 +66,21 @@ import { defineComponent } from 'vue';
|
||||||
|
|
||||||
import { useToast } from '@/composables/useToast';
|
import { useToast } from '@/composables/useToast';
|
||||||
import { FORM_TRIGGER_NODE_TYPE, OPEN_URL_PANEL_TRIGGER_NODE_TYPES } from '@/constants';
|
import { FORM_TRIGGER_NODE_TYPE, OPEN_URL_PANEL_TRIGGER_NODE_TYPES } from '@/constants';
|
||||||
import { copyPaste } from '@/mixins/copyPaste';
|
|
||||||
import { workflowHelpers } from '@/mixins/workflowHelpers';
|
import { workflowHelpers } from '@/mixins/workflowHelpers';
|
||||||
|
import { useClipboard } from '@/composables/useClipboard';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'NodeWebhooks',
|
name: 'NodeWebhooks',
|
||||||
mixins: [copyPaste, workflowHelpers],
|
mixins: [workflowHelpers],
|
||||||
props: [
|
props: [
|
||||||
'node', // NodeUi
|
'node', // NodeUi
|
||||||
'nodeType', // INodeTypeDescription
|
'nodeType', // INodeTypeDescription
|
||||||
],
|
],
|
||||||
setup() {
|
setup() {
|
||||||
|
const clipboard = useClipboard();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
clipboard,
|
||||||
...useToast(),
|
...useToast(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -136,7 +139,7 @@ export default defineComponent({
|
||||||
methods: {
|
methods: {
|
||||||
copyWebhookUrl(webhookData: IWebhookDescription): void {
|
copyWebhookUrl(webhookData: IWebhookDescription): void {
|
||||||
const webhookUrl = this.getWebhookUrlDisplay(webhookData);
|
const webhookUrl = this.getWebhookUrlDisplay(webhookData);
|
||||||
this.copyToClipboard(webhookUrl);
|
void this.clipboard.copy(webhookUrl);
|
||||||
|
|
||||||
this.showMessage({
|
this.showMessage({
|
||||||
title: this.baseText.copyTitle,
|
title: this.baseText.copyTitle,
|
||||||
|
|
|
@ -51,7 +51,7 @@
|
||||||
type="secondary"
|
type="secondary"
|
||||||
:title="$locale.baseText('nodeErrorView.copyToClipboard')"
|
:title="$locale.baseText('nodeErrorView.copyToClipboard')"
|
||||||
icon="copy"
|
icon="copy"
|
||||||
@click="copyToClipboard(raw)"
|
@click="onCopyToClipboard(raw)"
|
||||||
/>
|
/>
|
||||||
<VueMarkdown :source="jsonToMarkdown(raw as JsonMarkdown)" :class="$style.markdown" />
|
<VueMarkdown :source="jsonToMarkdown(raw as JsonMarkdown)" :class="$style.markdown" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -68,7 +68,7 @@ import { ref, onMounted } from 'vue';
|
||||||
import type { ParsedAiContent } from './useAiContentParsers';
|
import type { ParsedAiContent } from './useAiContentParsers';
|
||||||
import { useAiContentParsers } from './useAiContentParsers';
|
import { useAiContentParsers } from './useAiContentParsers';
|
||||||
import VueMarkdown from 'vue-markdown-render';
|
import VueMarkdown from 'vue-markdown-render';
|
||||||
import { useCopyToClipboard } from '@/composables/useCopyToClipboard';
|
import { useClipboard } from '@/composables/useClipboard';
|
||||||
import { useI18n } from '@/composables/useI18n';
|
import { useI18n } from '@/composables/useI18n';
|
||||||
import { useToast } from '@/composables/useToast';
|
import { useToast } from '@/composables/useToast';
|
||||||
import { NodeConnectionType, type IDataObject } from 'n8n-workflow';
|
import { NodeConnectionType, type IDataObject } from 'n8n-workflow';
|
||||||
|
@ -78,13 +78,15 @@ const props = defineProps<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
|
const clipboard = useClipboard();
|
||||||
|
const { showMessage } = useToast();
|
||||||
const contentParsers = useAiContentParsers();
|
const contentParsers = useAiContentParsers();
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||||
const isExpanded = ref(getInitialExpandedState());
|
const isExpanded = ref(getInitialExpandedState());
|
||||||
const isShowRaw = ref(false);
|
const isShowRaw = ref(false);
|
||||||
const contentParsed = ref(false);
|
const contentParsed = ref(false);
|
||||||
const parsedRun = ref(undefined as ParsedAiContent | undefined);
|
const parsedRun = ref(undefined as ParsedAiContent | undefined);
|
||||||
|
|
||||||
function getInitialExpandedState() {
|
function getInitialExpandedState() {
|
||||||
const collapsedTypes = {
|
const collapsedTypes = {
|
||||||
input: [NodeConnectionType.AiDocument, NodeConnectionType.AiTextSplitter],
|
input: [NodeConnectionType.AiDocument, NodeConnectionType.AiTextSplitter],
|
||||||
|
@ -155,12 +157,9 @@ function onBlockHeaderClick() {
|
||||||
isExpanded.value = !isExpanded.value;
|
isExpanded.value = !isExpanded.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyToClipboard(content: IDataObject | IDataObject[]) {
|
function onCopyToClipboard(content: IDataObject | IDataObject[]) {
|
||||||
const copyToClipboardFn = useCopyToClipboard();
|
|
||||||
const { showMessage } = useToast();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
copyToClipboardFn(JSON.stringify(content, undefined, 2));
|
void clipboard.copy(JSON.stringify(content, undefined, 2));
|
||||||
showMessage({
|
showMessage({
|
||||||
title: i18n.baseText('generic.copiedToClipboard'),
|
title: i18n.baseText('generic.copiedToClipboard'),
|
||||||
type: 'success',
|
type: 'success',
|
||||||
|
|
|
@ -41,7 +41,6 @@ import { mapStores } from 'pinia';
|
||||||
import jp from 'jsonpath';
|
import jp from 'jsonpath';
|
||||||
import type { INodeUi } from '@/Interface';
|
import type { INodeUi } from '@/Interface';
|
||||||
import type { IDataObject } from 'n8n-workflow';
|
import type { IDataObject } from 'n8n-workflow';
|
||||||
import { copyPaste } from '@/mixins/copyPaste';
|
|
||||||
import { pinData } from '@/mixins/pinData';
|
import { pinData } from '@/mixins/pinData';
|
||||||
import { genericHelpers } from '@/mixins/genericHelpers';
|
import { genericHelpers } from '@/mixins/genericHelpers';
|
||||||
import { clearJsonKey, convertPath } from '@/utils/typesUtils';
|
import { clearJsonKey, convertPath } from '@/utils/typesUtils';
|
||||||
|
@ -52,6 +51,7 @@ import { useNodeHelpers } from '@/composables/useNodeHelpers';
|
||||||
import { useToast } from '@/composables/useToast';
|
import { useToast } from '@/composables/useToast';
|
||||||
import { useI18n } from '@/composables/useI18n';
|
import { useI18n } from '@/composables/useI18n';
|
||||||
import { nonExistingJsonPath } from '@/constants';
|
import { nonExistingJsonPath } from '@/constants';
|
||||||
|
import { useClipboard } from '@/composables/useClipboard';
|
||||||
|
|
||||||
type JsonPathData = {
|
type JsonPathData = {
|
||||||
path: string;
|
path: string;
|
||||||
|
@ -60,8 +60,7 @@ type JsonPathData = {
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'RunDataJsonActions',
|
name: 'RunDataJsonActions',
|
||||||
mixins: [genericHelpers, pinData, copyPaste],
|
mixins: [genericHelpers, pinData],
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
node: {
|
node: {
|
||||||
type: Object as PropType<INodeUi>,
|
type: Object as PropType<INodeUi>,
|
||||||
|
@ -96,9 +95,12 @@ export default defineComponent({
|
||||||
setup() {
|
setup() {
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const nodeHelpers = useNodeHelpers();
|
const nodeHelpers = useNodeHelpers();
|
||||||
|
const clipboard = useClipboard();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
i18n,
|
i18n,
|
||||||
nodeHelpers,
|
nodeHelpers,
|
||||||
|
clipboard,
|
||||||
...useToast(),
|
...useToast(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -222,7 +224,7 @@ export default defineComponent({
|
||||||
in_execution_log: this.isReadOnlyRoute,
|
in_execution_log: this.isReadOnlyRoute,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.copyToClipboard(value);
|
void this.clipboard.copy(value);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -134,7 +134,7 @@ const getIconBySchemaType = (type: Schema['type']): string => {
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
@import '@/styles/css-animation-helpers.scss';
|
@import '@/styles/variables';
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
display: block;
|
display: block;
|
||||||
|
|
|
@ -91,7 +91,7 @@ onUnmounted(() => {
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
@import '@/styles/css-animation-helpers.scss';
|
@import '@/styles/variables';
|
||||||
|
|
||||||
.ioSearch {
|
.ioSearch {
|
||||||
margin-right: var(--spacing-s);
|
margin-right: var(--spacing-s);
|
||||||
|
|
|
@ -117,16 +117,12 @@ import NodeExecuteButton from '@/components/NodeExecuteButton.vue';
|
||||||
import { workflowHelpers } from '@/mixins/workflowHelpers';
|
import { workflowHelpers } from '@/mixins/workflowHelpers';
|
||||||
import CopyInput from '@/components/CopyInput.vue';
|
import CopyInput from '@/components/CopyInput.vue';
|
||||||
import NodeIcon from '@/components/NodeIcon.vue';
|
import NodeIcon from '@/components/NodeIcon.vue';
|
||||||
import { copyPaste } from '@/mixins/copyPaste';
|
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
import { useUIStore } from '@/stores/ui.store';
|
||||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
import { useNDVStore } from '@/stores/ndv.store';
|
import { useNDVStore } from '@/stores/ndv.store';
|
||||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||||
import type { N8nInfoAccordion } from 'n8n-design-system';
|
|
||||||
import { createEventBus } from 'n8n-design-system/utils';
|
import { createEventBus } from 'n8n-design-system/utils';
|
||||||
|
|
||||||
type HelpRef = InstanceType<typeof N8nInfoAccordion>;
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'TriggerPanel',
|
name: 'TriggerPanel',
|
||||||
components: {
|
components: {
|
||||||
|
@ -134,7 +130,7 @@ export default defineComponent({
|
||||||
CopyInput,
|
CopyInput,
|
||||||
NodeIcon,
|
NodeIcon,
|
||||||
},
|
},
|
||||||
mixins: [workflowHelpers, copyPaste],
|
mixins: [workflowHelpers],
|
||||||
props: {
|
props: {
|
||||||
nodeName: {
|
nodeName: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|
|
@ -4,14 +4,14 @@ import { computed, nextTick, onMounted, ref, watch } from 'vue';
|
||||||
import type { EnvironmentVariable, Rule, RuleGroup } from '@/Interface';
|
import type { EnvironmentVariable, Rule, RuleGroup } from '@/Interface';
|
||||||
import { useI18n } from '@/composables/useI18n';
|
import { useI18n } from '@/composables/useI18n';
|
||||||
import { useToast } from '@/composables/useToast';
|
import { useToast } from '@/composables/useToast';
|
||||||
import { useCopyToClipboard } from '@/composables/useCopyToClipboard';
|
import { useClipboard } from '@/composables/useClipboard';
|
||||||
import { EnterpriseEditionFeature } from '@/constants';
|
import { EnterpriseEditionFeature } from '@/constants';
|
||||||
import { useSettingsStore } from '@/stores/settings.store';
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
import { useUsersStore } from '@/stores/users.store';
|
import { useUsersStore } from '@/stores/users.store';
|
||||||
import { getVariablesPermissions } from '@/permissions';
|
import { getVariablesPermissions } from '@/permissions';
|
||||||
|
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const copyToClipboard = useCopyToClipboard();
|
const clipboard = useClipboard();
|
||||||
const { showMessage } = useToast();
|
const { showMessage } = useToast();
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
const usersStore = useUsersStore();
|
const usersStore = useUsersStore();
|
||||||
|
@ -120,7 +120,7 @@ function onValidate(key: string, value: boolean) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function onUsageClick() {
|
function onUsageClick() {
|
||||||
copyToClipboard(usage.value);
|
void clipboard.copy(usage.value);
|
||||||
showMessage({
|
showMessage({
|
||||||
title: i18n.baseText('variables.row.usage.copiedToClipboard'),
|
title: i18n.baseText('variables.row.usage.copiedToClipboard'),
|
||||||
type: 'success',
|
type: 'success',
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
v-for="item in props.items"
|
v-for="item in props.items"
|
||||||
:key="item.address"
|
:key="item.address"
|
||||||
:class="$style.accordionItem"
|
:class="$style.accordionItem"
|
||||||
@click="copyToClipboard(item.address)"
|
@click="onCopyToClipboard(item.address)"
|
||||||
>
|
>
|
||||||
{{ item.family }}: <span :class="$style.clickable">{{ item.address }}</span>
|
{{ item.family }}: <span :class="$style.clickable">{{ item.address }}</span>
|
||||||
{{ item.internal ? '(internal)' : '' }}
|
{{ item.internal ? '(internal)' : '' }}
|
||||||
|
@ -22,7 +22,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { IPushDataWorkerStatusPayload } from '@/Interface';
|
import type { IPushDataWorkerStatusPayload } from '@/Interface';
|
||||||
import WorkerAccordion from './WorkerAccordion.ee.vue';
|
import WorkerAccordion from './WorkerAccordion.ee.vue';
|
||||||
import { useCopyToClipboard } from '@/composables/useCopyToClipboard';
|
import { useClipboard } from '@/composables/useClipboard';
|
||||||
import { useI18n } from '@/composables/useI18n';
|
import { useI18n } from '@/composables/useI18n';
|
||||||
import { useToast } from '@/composables/useToast';
|
import { useToast } from '@/composables/useToast';
|
||||||
|
|
||||||
|
@ -31,13 +31,12 @@ const props = defineProps<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
|
const clipboard = useClipboard();
|
||||||
function copyToClipboard(content: string) {
|
|
||||||
const copyToClipboardFn = useCopyToClipboard();
|
|
||||||
const { showMessage } = useToast();
|
const { showMessage } = useToast();
|
||||||
|
|
||||||
|
function onCopyToClipboard(content: string) {
|
||||||
try {
|
try {
|
||||||
copyToClipboardFn(content);
|
void clipboard.copy(content);
|
||||||
showMessage({
|
showMessage({
|
||||||
title: i18n.baseText('workerList.item.copyAddressToClipboard'),
|
title: i18n.baseText('workerList.item.copyAddressToClipboard'),
|
||||||
type: 'success',
|
type: 'success',
|
||||||
|
|
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
80
packages/editor-ui/src/composables/useClipboard.ts
Normal file
80
packages/editor-ui/src/composables/useClipboard.ts
Normal 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,
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,5 +0,0 @@
|
||||||
import copyToClipboard from 'copy-to-clipboard';
|
|
||||||
|
|
||||||
export function useCopyToClipboard(): (text: string) => void {
|
|
||||||
return copyToClipboard;
|
|
||||||
}
|
|
|
@ -5,7 +5,6 @@ import '@jsplumb/browser-ui/css/jsplumbtoolkit.css';
|
||||||
import 'n8n-design-system/css/index.scss';
|
import 'n8n-design-system/css/index.scss';
|
||||||
|
|
||||||
import './n8n-theme.scss';
|
import './n8n-theme.scss';
|
||||||
import './styles/autocomplete-theme.scss';
|
|
||||||
|
|
||||||
import '@fontsource/open-sans/latin-400.css';
|
import '@fontsource/open-sans/latin-400.css';
|
||||||
import '@fontsource/open-sans/latin-600.css';
|
import '@fontsource/open-sans/latin-600.css';
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
@import 'styles/plugins';
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--node-type-background-l: 95%;
|
--node-type-background-l: 95%;
|
||||||
|
|
||||||
|
|
|
@ -1031,10 +1031,10 @@
|
||||||
"nodeView.cantExecuteNoTrigger": "Cannot execute workflow",
|
"nodeView.cantExecuteNoTrigger": "Cannot execute workflow",
|
||||||
"nodeView.canvasAddButton.addATriggerNodeBeforeExecuting": "Add a Trigger Node before executing the workflow",
|
"nodeView.canvasAddButton.addATriggerNodeBeforeExecuting": "Add a Trigger Node before executing the workflow",
|
||||||
"nodeView.canvasAddButton.addFirstStep": "Add first step…",
|
"nodeView.canvasAddButton.addFirstStep": "Add first step…",
|
||||||
"nodeView.confirmMessage.receivedCopyPasteData.cancelButtonText": "",
|
"nodeView.confirmMessage.onClipboardPasteEvent.cancelButtonText": "",
|
||||||
"nodeView.confirmMessage.receivedCopyPasteData.confirmButtonText": "Yes, import",
|
"nodeView.confirmMessage.onClipboardPasteEvent.confirmButtonText": "Yes, import",
|
||||||
"nodeView.confirmMessage.receivedCopyPasteData.headline": "Import Workflow?",
|
"nodeView.confirmMessage.onClipboardPasteEvent.headline": "Import Workflow?",
|
||||||
"nodeView.confirmMessage.receivedCopyPasteData.message": "Workflow will be imported from<br /><i>{plainTextData}<i>",
|
"nodeView.confirmMessage.onClipboardPasteEvent.message": "Workflow will be imported from<br /><i>{plainTextData}<i>",
|
||||||
"nodeView.confirmMessage.debug.cancelButtonText": "Cancel",
|
"nodeView.confirmMessage.debug.cancelButtonText": "Cancel",
|
||||||
"nodeView.confirmMessage.debug.confirmButtonText": "Unpin",
|
"nodeView.confirmMessage.debug.confirmButtonText": "Unpin",
|
||||||
"nodeView.confirmMessage.debug.headline": "Unpin workflow data",
|
"nodeView.confirmMessage.debug.headline": "Unpin workflow data",
|
||||||
|
|
1
packages/editor-ui/src/styles/plugins/index.scss
Normal file
1
packages/editor-ui/src/styles/plugins/index.scss
Normal file
|
@ -0,0 +1 @@
|
||||||
|
@import "codemirror";
|
|
@ -239,7 +239,6 @@ import {
|
||||||
UPDATE_WEBHOOK_ID_NODE_TYPES,
|
UPDATE_WEBHOOK_ID_NODE_TYPES,
|
||||||
TIME,
|
TIME,
|
||||||
} from '@/constants';
|
} from '@/constants';
|
||||||
import { copyPaste } from '@/mixins/copyPaste';
|
|
||||||
import { genericHelpers } from '@/mixins/genericHelpers';
|
import { genericHelpers } from '@/mixins/genericHelpers';
|
||||||
import { moveNodeWorkflow } from '@/mixins/moveNodeWorkflow';
|
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 { getConnectorPaintStyleData, OVERLAY_ENDPOINT_ARROW_ID } from '@/utils/nodeViewUtils';
|
||||||
import { useViewStacks } from '@/components/Node/NodeCreator/composables/useViewStacks';
|
import { useViewStacks } from '@/components/Node/NodeCreator/composables/useViewStacks';
|
||||||
import { useExternalHooks } from '@/composables/useExternalHooks';
|
import { useExternalHooks } from '@/composables/useExternalHooks';
|
||||||
|
import { useClipboard } from '@/composables/useClipboard';
|
||||||
|
|
||||||
interface AddNodeOptions {
|
interface AddNodeOptions {
|
||||||
position?: XYPosition;
|
position?: XYPosition;
|
||||||
|
@ -394,15 +394,7 @@ export default defineComponent({
|
||||||
CanvasControls,
|
CanvasControls,
|
||||||
ContextMenu,
|
ContextMenu,
|
||||||
},
|
},
|
||||||
mixins: [
|
mixins: [genericHelpers, moveNodeWorkflow, workflowHelpers, workflowRun, debounceHelper, pinData],
|
||||||
copyPaste,
|
|
||||||
genericHelpers,
|
|
||||||
moveNodeWorkflow,
|
|
||||||
workflowHelpers,
|
|
||||||
workflowRun,
|
|
||||||
debounceHelper,
|
|
||||||
pinData,
|
|
||||||
],
|
|
||||||
async beforeRouteLeave(to, from, next) {
|
async beforeRouteLeave(to, from, next) {
|
||||||
if (
|
if (
|
||||||
getNodeViewTab(to) === MAIN_HEADER_TABS.EXECUTIONS ||
|
getNodeViewTab(to) === MAIN_HEADER_TABS.EXECUTIONS ||
|
||||||
|
@ -469,6 +461,7 @@ export default defineComponent({
|
||||||
const contextMenu = useContextMenu();
|
const contextMenu = useContextMenu();
|
||||||
const dataSchema = useDataSchema();
|
const dataSchema = useDataSchema();
|
||||||
const nodeHelpers = useNodeHelpers();
|
const nodeHelpers = useNodeHelpers();
|
||||||
|
const clipboard = useClipboard();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
locale,
|
locale,
|
||||||
|
@ -476,6 +469,7 @@ export default defineComponent({
|
||||||
dataSchema,
|
dataSchema,
|
||||||
nodeHelpers,
|
nodeHelpers,
|
||||||
externalHooks,
|
externalHooks,
|
||||||
|
clipboard,
|
||||||
...useCanvasMouseSelect(),
|
...useCanvasMouseSelect(),
|
||||||
...useGlobalLinkActions(),
|
...useGlobalLinkActions(),
|
||||||
...useTitleChange(),
|
...useTitleChange(),
|
||||||
|
@ -756,6 +750,8 @@ export default defineComponent({
|
||||||
this.titleReset();
|
this.titleReset();
|
||||||
window.addEventListener('message', this.onPostMessageReceived);
|
window.addEventListener('message', this.onPostMessageReceived);
|
||||||
|
|
||||||
|
this.clipboard.onPaste.value = this.onClipboardPasteEvent;
|
||||||
|
|
||||||
this.startLoading();
|
this.startLoading();
|
||||||
const loadPromises = [
|
const loadPromises = [
|
||||||
this.loadActiveWorkflows(),
|
this.loadActiveWorkflows(),
|
||||||
|
@ -1823,7 +1819,7 @@ export default defineComponent({
|
||||||
|
|
||||||
const nodeData = JSON.stringify(workflowToCopy, null, 2);
|
const nodeData = JSON.stringify(workflowToCopy, null, 2);
|
||||||
|
|
||||||
this.copyToClipboard(nodeData);
|
this.clipboard.copy(nodeData);
|
||||||
if (data.nodes.length > 0) {
|
if (data.nodes.length > 0) {
|
||||||
if (!isCut) {
|
if (!isCut) {
|
||||||
this.showMessage({
|
this.showMessage({
|
||||||
|
@ -1928,7 +1924,7 @@ export default defineComponent({
|
||||||
/**
|
/**
|
||||||
* This method gets called when data got pasted into the window
|
* 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) {
|
if (this.readOnlyEnv) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1948,17 +1944,17 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
const importConfirm = await this.confirm(
|
const importConfirm = await this.confirm(
|
||||||
this.$locale.baseText('nodeView.confirmMessage.receivedCopyPasteData.message', {
|
this.$locale.baseText('nodeView.confirmMessage.onClipboardPasteEvent.message', {
|
||||||
interpolate: { plainTextData },
|
interpolate: { plainTextData },
|
||||||
}),
|
}),
|
||||||
this.$locale.baseText('nodeView.confirmMessage.receivedCopyPasteData.headline'),
|
this.$locale.baseText('nodeView.confirmMessage.onClipboardPasteEvent.headline'),
|
||||||
{
|
{
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
confirmButtonText: this.$locale.baseText(
|
confirmButtonText: this.$locale.baseText(
|
||||||
'nodeView.confirmMessage.receivedCopyPasteData.confirmButtonText',
|
'nodeView.confirmMessage.onClipboardPasteEvent.confirmButtonText',
|
||||||
),
|
),
|
||||||
cancelButtonText: this.$locale.baseText(
|
cancelButtonText: this.$locale.baseText(
|
||||||
'nodeView.confirmMessage.receivedCopyPasteData.cancelButtonText',
|
'nodeView.confirmMessage.onClipboardPasteEvent.cancelButtonText',
|
||||||
),
|
),
|
||||||
dangerouslyUseHTMLString: true,
|
dangerouslyUseHTMLString: true,
|
||||||
},
|
},
|
||||||
|
|
|
@ -240,7 +240,7 @@ const openPricingPage = () => {
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
@import '@/styles/css-animation-helpers.scss';
|
@import '@/styles/variables';
|
||||||
|
|
||||||
.center > div {
|
.center > div {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
|
@ -91,7 +91,6 @@ import { EnterpriseEditionFeature, INVITE_USER_MODAL_KEY, VIEWS } from '@/consta
|
||||||
|
|
||||||
import type { IUser, IUserListAction } from '@/Interface';
|
import type { IUser, IUserListAction } from '@/Interface';
|
||||||
import { useToast } from '@/composables/useToast';
|
import { useToast } from '@/composables/useToast';
|
||||||
import { copyPaste } from '@/mixins/copyPaste';
|
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
import { useUIStore } from '@/stores/ui.store';
|
||||||
import { useSettingsStore } from '@/stores/settings.store';
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
import { useUsersStore } from '@/stores/users.store';
|
import { useUsersStore } from '@/stores/users.store';
|
||||||
|
@ -99,12 +98,15 @@ import { useUsageStore } from '@/stores/usage.store';
|
||||||
import { useSSOStore } from '@/stores/sso.store';
|
import { useSSOStore } from '@/stores/sso.store';
|
||||||
import { hasPermission } from '@/rbac/permissions';
|
import { hasPermission } from '@/rbac/permissions';
|
||||||
import { ROLE } from '@/utils/userUtils';
|
import { ROLE } from '@/utils/userUtils';
|
||||||
|
import { useClipboard } from '@/composables/useClipboard';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'SettingsUsersView',
|
name: 'SettingsUsersView',
|
||||||
mixins: [copyPaste],
|
|
||||||
setup() {
|
setup() {
|
||||||
|
const clipboard = useClipboard();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
clipboard,
|
||||||
...useToast(),
|
...useToast(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -222,7 +224,7 @@ export default defineComponent({
|
||||||
async onCopyInviteLink(userId: string) {
|
async onCopyInviteLink(userId: string) {
|
||||||
const user = this.usersStore.getUserById(userId);
|
const user = this.usersStore.getUserById(userId);
|
||||||
if (user?.inviteAcceptUrl) {
|
if (user?.inviteAcceptUrl) {
|
||||||
this.copyToClipboard(user.inviteAcceptUrl);
|
void this.clipboard.copy(user.inviteAcceptUrl);
|
||||||
|
|
||||||
this.showToast({
|
this.showToast({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
|
@ -235,7 +237,7 @@ export default defineComponent({
|
||||||
const user = this.usersStore.getUserById(userId);
|
const user = this.usersStore.getUserById(userId);
|
||||||
if (user) {
|
if (user) {
|
||||||
const url = await this.usersStore.getUserPasswordResetLink(user);
|
const url = await this.usersStore.getUserPasswordResetLink(user);
|
||||||
this.copyToClipboard(url.link);
|
void this.clipboard.copy(url.link);
|
||||||
|
|
||||||
this.showToast({
|
this.showToast({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
|
|
|
@ -1035,9 +1035,6 @@ importers:
|
||||||
codemirror-lang-n8n-expression:
|
codemirror-lang-n8n-expression:
|
||||||
specifier: ^0.2.0
|
specifier: ^0.2.0
|
||||||
version: 0.2.0(@codemirror/state@6.3.3)(@codemirror/view@6.22.3)(@lezer/common@1.1.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:
|
dateformat:
|
||||||
specifier: ^3.0.3
|
specifier: ^3.0.3
|
||||||
version: 3.0.3
|
version: 3.0.3
|
||||||
|
@ -13362,12 +13359,6 @@ packages:
|
||||||
is-plain-object: 5.0.0
|
is-plain-object: 5.0.0
|
||||||
dev: true
|
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:
|
/core-js-compat@3.32.0:
|
||||||
resolution: {integrity: sha512-7a9a3D1k4UCVKnLhrgALyFcP7YCsLOQIxPd0dKjf/6GuPcgyiGP70ewWdCGrSK7evyhymi0qO4EqCmSJofDeYw==}
|
resolution: {integrity: sha512-7a9a3D1k4UCVKnLhrgALyFcP7YCsLOQIxPd0dKjf/6GuPcgyiGP70ewWdCGrSK7evyhymi0qO4EqCmSJofDeYw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -24525,10 +24516,6 @@ packages:
|
||||||
resolution: {integrity: sha512-vXk8htr8mIl3hc2s2mDkaPTBfqmqZA2o0x7eXbxUibdrpEIPdpM0L9hH/RvEvlgSM+ZTgS34sGipk5+VrLJCLA==}
|
resolution: {integrity: sha512-vXk8htr8mIl3hc2s2mDkaPTBfqmqZA2o0x7eXbxUibdrpEIPdpM0L9hH/RvEvlgSM+ZTgS34sGipk5+VrLJCLA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/toggle-selection@1.0.6:
|
|
||||||
resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/toidentifier@1.0.1:
|
/toidentifier@1.0.1:
|
||||||
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
|
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
|
||||||
engines: {node: '>=0.6'}
|
engines: {node: '>=0.6'}
|
||||||
|
|
Loading…
Reference in a new issue