feat(editor): Add Workflow Stickies (Notes) (#3154)
* N8N-3029 Add Node Type for Wokrflow Stickies/Notes * N8N-3029 Update Content, Update Aliasses * N8N-3030 Created N8N Sticky Component in Design System * N8N-3030 Fixed Code spaccing Sticky Component * N8N-3030 Fixed Code spaccing StickyStories Component * N8N-3030 Fixed Code spaccing Markdown Component * N8N-3030 Added Sticky Colors Pallete into Storybook, Update Color Variables for Sticky Component * N8N-3030 Added Unfocus Event * N8N-3030 Update Default Placeholder, Markdown Styles, Fixed Edit State, Added Text to EditState, Fixed Height of Area, Turned off Resize of textarea * N8N-3030 Update Sticky Overflow, Update Hover States, Updated Markdown Overflow * N8N-3030, N8N-3031 - Add Resize to Sticky, Created N8n-Resize component * N8N-3031 Fixed Importing Components in Editor-ui * N8N-3031 Fixed Resize Component, Fixed Gradient * N8N-3030, N8N-3031 Update Note Description * N8N-3032 Hotfix Building Storybook * N8N-3032 - Select Behaviour, Changes in Resize Component, Emit on Width/Height/Top/Left Change * N8N-3032 Update Resize Component to emmit left/top, Update Dynamic Resize on Selected Background * N8N-3032 Updated / Dragging vs Resizing, prevent open Modal for stickies * N8N-3032 Added ID props to n8n-sticky // dynamic id for multi resizing in NodeView * N8N-3033 Add dynamic size Tooltip on Sticky * N8N-3033 Updated Z-index for Sticky Component * N8N-3033 Updated N8N-Resize Component, Fixed SelectedBackround for Sticky Component * N8N-3033 Refactor * N8N-3033 Focus/Defocus on TextArea * N8N-3033 Fixed Resizing on NW Point * N8N-3030 Save content in vuex on input change * N8N-3033 Fixed Resizer, Save Width and Height in Vue * N8N-3033 Hide Sticky Footer on small height/width * N8N-3033 Fixed Resizer * N8N-3033 Dynamic Z-index for Stickies * N8N-3033 Dynamic Z-index for Stickies * N8N-3033 Removed static z-index for select sticky class * N8N-3034 Added Telemetry * N8N-3030 Formatter * N8N-3030 Format code * N8N-3030 Fixed Selecting Stickies * N8N-3033 Fixed Notifications * N8N-3030 Added new paddings for Default Stickies * N8N-3033 Prevent Scrolling NodeView when Sticky is in Edit mode and Mouse is Over the TextArea * N8N-3030 Prevent double clicking to switch state of Sticky component in Edit Mode * N8N-3033 Fixed Z-index of Stickies * N8N-3033 Prevent delete node when in EditMode * N8N-3030 Prevent Delete Button to delete the Sticky while in Edit Mode * N8N-3030 Change EditMode (emit) on keyboard shortucts, update Markdown Links & Images, Added new props * N8N-3030 Sticky Component - No padding when hiding footer text * N8N-3033 Fix Resizing enter into Edit Mode * N8N-3033 Selecting different nodes - exit the edit mode * N8N-3033 Auto Select Text in text-area by default - Sticky Component * N8N-3033 Prevent Default behaviour for CTRL + X, CTRL + A when Sticky is Active && inEditMode * N8N-3033 Refactor Resizer, Refactor Sticky, Update zIndex inEditMode * N8N-3033 Updated Default Text // Node-base, Storybook * N8N-3033 Add Resizing in EditMode - Components update * N8N-3033 Fixed Footer - Show/Hide on Resize in EditMode * N8N-3033 Fix ActiveSticky on Init * N8N-3033 Refactor Sticky in Vuex, Fixed Init Sticky Tweaks, Prevent Modal Openning, Save on Keyboard shortcuts * Stickies - Update Note node with new props * N8N-3030 Updated Default Note text, Update the Markdown Link * N8N-3030 CMD-C does not copy the text fix * N8N-3030 Fix Max Zoom / Zoom out shortcuts disabled in editState * N8N-3030 Z-index fixed during Edit Mode typing * N8N-3030 Prevent Autoselect Text in Stickies if the text is not default * N8N-3030 Fixed ReadOnly Bugs / Prevent showing Tooltip, Resizing * N8N-3030 Added Sticky Creator Button * N8N-3030 Update Icon / Sticky Creator Button * N8N-3033 Update Sticky Icon / StickyCreator Button * update package lock * 🔩 update note props * 🚿 clean props * 🔧 linting * 🔧 fix spacing * remove resize component * remove resize component * ✂ clean up sticky * revert back to height width * revert back to height/width * replace zindex property * replace default text property * use i18n to translate * update package lock * move resize * clean up how height/width are set * fix resize for sticky to support left/top * clean up resize * fix lasso/highlight bug * remove unused props * fix zoom to fit * fix padding for demo view * fix readonly * remove iseditable, use active state * clean up keyboard events * chang button size, no edit on insert * scale resizing correctly * make active on resize * fix select on resize/move * use outline icon * allow for multiple line breaks * fix multi line bug * fix edit mode outline * keep edit open as one resizes * respect multiple spaces * fix scrolling bug * clean up hover impl * clean up references to note * disable for rename * fix drifting while drag * fix mouse cursor on resize * fix sticky min height * refactor resize into component * fix pulling too far bug * fix delete/cut all bug * fix padding bottom * fix active change on resize * add transition to button * Fix sticky markdown click * add solid fa icon * update node graph, telemetry event * add snapping * change alt text * update package lock * fix bug in button hover * add back transition * clean up resize * add grid size as param * remove breaks * clean up markdown * lint fixes * fix spacing * clean up markdown colors * clean up classes in resize * clean up resize * update sticky story * fix spacing * clean up classes * revert change * revert change * revert change * clean up sticky component * remove unused component * remove unnessary data * remove unnessary data * clean up actions * clean up sticky size * clean up unnessary border style * fix bug * replace sticky note name * update description * remove support for multi spaces * update tracking name * update telemetry reqs * fix enter bug * update alt text * update sticky notes doc url * fix readonly bug * update class name * update quote marks Co-authored-by: SchnapsterDog <olivertrajceski@yahoo.com>
This commit is contained in:
@@ -79,6 +79,7 @@
|
||||
"vue-loader": "^15.9.7",
|
||||
"vue-property-decorator": "^9.1.2",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"vue-typed-mixins": "^0.2.0",
|
||||
"vue2-boring-avatars": "0.3.4",
|
||||
"xss": "^1.0.10"
|
||||
}
|
||||
|
||||
@@ -69,7 +69,6 @@ export default {
|
||||
default: false,
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
},
|
||||
round: {
|
||||
type: Boolean,
|
||||
|
||||
@@ -26,7 +26,6 @@ export default {
|
||||
},
|
||||
props: {
|
||||
icon: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
size: {
|
||||
|
||||
@@ -40,7 +40,6 @@ export default {
|
||||
default: false,
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
theme: {
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="!loading" ref="editor" :class="$style.markdown" v-html="htmlContent" />
|
||||
<div
|
||||
v-if="!loading"
|
||||
ref="editor"
|
||||
:class="$style[theme]" v-html="htmlContent"
|
||||
/>
|
||||
<div v-else :class="$style.markdown">
|
||||
<div v-for="(block, index) in loadingBlocks"
|
||||
:key="index">
|
||||
@@ -59,6 +63,9 @@ export default {
|
||||
content: {
|
||||
type: String,
|
||||
},
|
||||
withMultiBreaks: {
|
||||
type: Boolean,
|
||||
},
|
||||
images: {
|
||||
type: Array,
|
||||
},
|
||||
@@ -75,6 +82,10 @@ export default {
|
||||
return 3;
|
||||
},
|
||||
},
|
||||
theme: {
|
||||
type: String,
|
||||
default: 'markdown',
|
||||
},
|
||||
options: {
|
||||
type: Object,
|
||||
default() {
|
||||
@@ -106,7 +117,11 @@ export default {
|
||||
}
|
||||
|
||||
const fileIdRegex = new RegExp('fileId:([0-9]+)');
|
||||
const html = this.md.render(escapeMarkdown(this.content));
|
||||
let contentToRender = this.content;
|
||||
if (this.withMultiBreaks) {
|
||||
contentToRender = contentToRender.replaceAll('\n\n', '\n \n');
|
||||
}
|
||||
const html = this.md.render(escapeMarkdown(contentToRender));
|
||||
const safeHtml = xss(html, {
|
||||
onTagAttr: (tag, name, value, isWhiteAttr) => {
|
||||
if (tag === 'img' && name === 'src') {
|
||||
@@ -214,6 +229,67 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
.sticky {
|
||||
color: var(--color-text-dark);
|
||||
|
||||
h1, h2, h3, h4 {
|
||||
margin-bottom: var(--spacing-2xs);
|
||||
font-weight: var(--font-weight-bold);
|
||||
line-height: var(--font-line-height-loose);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 36px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
h3, h4, h5, h6 {
|
||||
font-size: var(--font-size-m);
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: var(--spacing-2xs);
|
||||
font-size: var(--font-size-s);
|
||||
font-weight: var(--font-weight-regular);
|
||||
line-height: var(--font-line-height-loose);
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
margin-bottom: var(--spacing-2xs);
|
||||
padding-left: var(--spacing-m);
|
||||
|
||||
li {
|
||||
margin-top: 0.25em;
|
||||
font-size: var(--font-size-s);
|
||||
font-weight: var(--font-weight-regular);
|
||||
line-height: var(--font-line-height-regular);
|
||||
}
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: var(--color-background-base);
|
||||
padding: 0 var(--spacing-4xs);
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
||||
pre > code,li > code, p > code {
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
||||
a {
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
.spacer {
|
||||
margin: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
238
packages/design-system/src/components/N8nSticky/Resize.vue
Normal file
238
packages/design-system/src/components/N8nSticky/Resize.vue
Normal file
@@ -0,0 +1,238 @@
|
||||
<template>
|
||||
<div :class="$style.resize">
|
||||
<div v-if="isResizingEnabled" @mousedown="resizerMove" data-dir="right" :class="[$style.resizer, $style.right]" />
|
||||
<div v-if="isResizingEnabled" @mousedown="resizerMove" data-dir="left" :class="[$style.resizer, $style.left]" />
|
||||
<div v-if="isResizingEnabled" @mousedown="resizerMove" data-dir="top" :class="[$style.resizer, $style.top]" />
|
||||
<div v-if="isResizingEnabled" @mousedown="resizerMove" data-dir="bottom" :class="[$style.resizer, $style.bottom]" />
|
||||
<div v-if="isResizingEnabled" @mousedown="resizerMove" data-dir="top-left" :class="[$style.resizer, $style.topLeft]" />
|
||||
<div v-if="isResizingEnabled" @mousedown="resizerMove" data-dir="top-right" :class="[$style.resizer, $style.topRight]" />
|
||||
<div v-if="isResizingEnabled" @mousedown="resizerMove" data-dir="bottom-left" :class="[$style.resizer, $style.bottomLeft]" />
|
||||
<div v-if="isResizingEnabled" @mousedown="resizerMove" data-dir="bottom-right" :class="[$style.resizer, $style.bottomRight]" />
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
const cursorMap = {
|
||||
right: 'ew-resize',
|
||||
top: 'ns-resize',
|
||||
bottom: 'ns-resize',
|
||||
left: 'ew-resize',
|
||||
'top-left': 'nw-resize',
|
||||
'top-right' : 'ne-resize',
|
||||
'bottom-left': 'sw-resize',
|
||||
'bottom-right': 'se-resize',
|
||||
};
|
||||
|
||||
function closestNumber(value: number, divisor: number): number {
|
||||
let q = parseInt(value / divisor);
|
||||
let n1 = divisor * q;
|
||||
|
||||
let n2 = (value * divisor) > 0 ?
|
||||
(divisor * (q + 1)) : (divisor * (q - 1));
|
||||
|
||||
if (Math.abs(value - n1) < Math.abs(value - n2))
|
||||
return n1;
|
||||
|
||||
return n2;
|
||||
}
|
||||
|
||||
function getSize(delta, min, virtual, gridSize): number {
|
||||
const target = closestNumber(virtual, gridSize);
|
||||
if (target >= min && virtual > 0) {
|
||||
return target;
|
||||
}
|
||||
|
||||
return min;
|
||||
};
|
||||
|
||||
export default {
|
||||
name: 'n8n-resize',
|
||||
props: {
|
||||
isResizingEnabled: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
},
|
||||
minHeight: {
|
||||
type: Number,
|
||||
},
|
||||
minWidth: {
|
||||
type: Number,
|
||||
},
|
||||
scale: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
gridSize: {
|
||||
type: Number,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dir: '',
|
||||
dHeight: 0,
|
||||
dWidth: 0,
|
||||
vHeight: 0,
|
||||
vWidth: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
resizerMove(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const targetResizer = e.target;
|
||||
this.dir = targetResizer.dataset.dir;
|
||||
document.body.style.cursor = cursorMap[this.dir];
|
||||
|
||||
this.x = e.pageX;
|
||||
this.y = e.pageY;
|
||||
this.dWidth = 0;
|
||||
this.dHeight = 0;
|
||||
this.vHeight = this.height;
|
||||
this.vWidth = this.width;
|
||||
|
||||
window.addEventListener('mousemove', this.mouseMove);
|
||||
window.addEventListener('mouseup', this.mouseUp);
|
||||
this.$emit('resizestart');
|
||||
},
|
||||
mouseMove(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
let dWidth = 0;
|
||||
let dHeight = 0;
|
||||
let top = false;
|
||||
let left = false;
|
||||
|
||||
if (this.dir.includes('right')) {
|
||||
dWidth = e.pageX - this.x;
|
||||
}
|
||||
if (this.dir.includes('left')) {
|
||||
dWidth = this.x - e.pageX;
|
||||
left = true;
|
||||
}
|
||||
if (this.dir.includes('top')) {
|
||||
dHeight = this.y - e.pageY;
|
||||
top = true;
|
||||
}
|
||||
if (this.dir.includes('bottom')) {
|
||||
dHeight = e.pageY - this.y;
|
||||
}
|
||||
|
||||
const deltaWidth = (dWidth - this.dWidth) / this.scale;
|
||||
const deltaHeight = (dHeight - this.dHeight) / this.scale;
|
||||
|
||||
this.vHeight = this.vHeight + deltaHeight;
|
||||
this.vWidth = this.vWidth + deltaWidth;
|
||||
const height = getSize(deltaHeight, this.minHeight, this.vHeight, this.gridSize);
|
||||
const width = getSize(deltaWidth, this.minWidth, this.vWidth, this.gridSize);
|
||||
|
||||
const dX = left && width !== this.width ? -1 * (width - this.width) : 0;
|
||||
const dY = top && height !== this.height ? -1 * (height - this.height): 0;
|
||||
|
||||
this.$emit('resize', { height, width, dX, dY });
|
||||
this.dHeight = dHeight;
|
||||
this.dWidth = dWidth;
|
||||
},
|
||||
mouseUp(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.$emit('resizeend');
|
||||
window.removeEventListener('mousemove', this.mouseMove);
|
||||
window.removeEventListener('mouseup', this.mouseUp);
|
||||
document.body.style.cursor = 'unset';
|
||||
this.dir = '';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.resize {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.resizer {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.right {
|
||||
width: 12px;
|
||||
height: 100%;
|
||||
top: -2px;
|
||||
right: -2px;
|
||||
cursor: ew-resize;
|
||||
}
|
||||
|
||||
.top {
|
||||
width: 100%;
|
||||
height: 12px;
|
||||
top: -2px;
|
||||
left: -2px;
|
||||
cursor: ns-resize;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
width: 100%;
|
||||
height: 12px;
|
||||
bottom: -2px;
|
||||
left: -2px;
|
||||
cursor: ns-resize;
|
||||
}
|
||||
|
||||
.left {
|
||||
width: 12px;
|
||||
height: 100%;
|
||||
top: -2px;
|
||||
left: -2px;
|
||||
cursor: ew-resize;
|
||||
}
|
||||
|
||||
.topLeft {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
top: -3px;
|
||||
left: -3px;
|
||||
cursor: nw-resize;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.topRight {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
top: -3px;
|
||||
right: -3px;
|
||||
cursor: ne-resize;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.bottomLeft {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
bottom: -3px;
|
||||
left: -3px;
|
||||
cursor: sw-resize;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.bottomRight {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
bottom: -3px;
|
||||
right: -3px;
|
||||
cursor: se-resize;
|
||||
z-index: 3;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,67 @@
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import N8nSticky from './Sticky.vue';
|
||||
|
||||
export default {
|
||||
title: 'Atoms/Sticky',
|
||||
component: N8nSticky,
|
||||
argTypes: {
|
||||
content: {
|
||||
control: {
|
||||
control: 'text',
|
||||
},
|
||||
},
|
||||
height: {
|
||||
control: {
|
||||
control: 'number',
|
||||
},
|
||||
},
|
||||
minHeight: {
|
||||
control: {
|
||||
control: 'number',
|
||||
},
|
||||
},
|
||||
minWidth: {
|
||||
control: {
|
||||
control: 'number',
|
||||
},
|
||||
},
|
||||
readOnly: {
|
||||
control: {
|
||||
control: 'Boolean',
|
||||
},
|
||||
},
|
||||
width: {
|
||||
control: {
|
||||
control: 'number',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const methods = {
|
||||
onInput: action('input'),
|
||||
onResize: action('resize'),
|
||||
onResizeEnd: action('resizeend'),
|
||||
onResizeStart: action('resizestart'),
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: {
|
||||
N8nSticky,
|
||||
},
|
||||
template:
|
||||
'<n8n-sticky v-bind="$props" @resize="onResize" @resizeend="onResizeEnd" @resizeStart="onResizeStart" @input="onInput"></n8n-sticky>',
|
||||
methods,
|
||||
});
|
||||
|
||||
export const Sticky = Template.bind({});
|
||||
Sticky.args = {
|
||||
height: 160,
|
||||
width: 150,
|
||||
content: `## I'm a note \n**Double click** to edit me. [Guide](https://docs.n8n.io/workflows/sticky-notes/)`,
|
||||
defaultText: `## I'm a note \n**Double click** to edit me. [Guide](https://docs.n8n.io/workflows/sticky-notes/)`,
|
||||
minHeight: 80,
|
||||
minWidth: 150,
|
||||
readOnly: false,
|
||||
};
|
||||
253
packages/design-system/src/components/N8nSticky/Sticky.vue
Normal file
253
packages/design-system/src/components/N8nSticky/Sticky.vue
Normal file
@@ -0,0 +1,253 @@
|
||||
<template>
|
||||
<div
|
||||
:class="{[$style.sticky]: true, [$style.clickable]: !isResizing}"
|
||||
:style="styles"
|
||||
@keydown.prevent
|
||||
>
|
||||
<resize
|
||||
:isResizingEnabled="!readOnly"
|
||||
:height="height"
|
||||
:width="width"
|
||||
:minHeight="minHeight"
|
||||
:minWidth="minWidth"
|
||||
:scale="scale"
|
||||
:gridSize="gridSize"
|
||||
@resizeend="onResizeEnd"
|
||||
@resize="onResize"
|
||||
@resizestart="onResizeStart"
|
||||
>
|
||||
<template>
|
||||
<div
|
||||
v-show="!editMode"
|
||||
:class="$style.wrapper"
|
||||
@dblclick.stop="onDoubleClick"
|
||||
>
|
||||
<n8n-markdown
|
||||
theme="sticky"
|
||||
:content="content"
|
||||
:withMultiBreaks="true"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-show="editMode"
|
||||
@click.stop
|
||||
@mousedown.stop
|
||||
@mouseup.stop
|
||||
@keydown.esc="onInputBlur"
|
||||
@keydown.stop
|
||||
@wheel.stop
|
||||
class="sticky-textarea"
|
||||
:class="{'full-height': !shouldShowFooter}"
|
||||
>
|
||||
<n8n-input
|
||||
:value="content"
|
||||
type="textarea"
|
||||
:rows="5"
|
||||
@blur="onInputBlur"
|
||||
@input="onInput"
|
||||
ref="input"
|
||||
/>
|
||||
|
||||
</div>
|
||||
<div v-if="editMode && shouldShowFooter" :class="$style.footer">
|
||||
<n8n-text
|
||||
size="xsmall"
|
||||
aligh="right"
|
||||
>
|
||||
<span v-html="t('sticky.markdownHint')"></span>
|
||||
</n8n-text>
|
||||
</div>
|
||||
</template>
|
||||
</resize>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import N8nInput from '../N8nInput';
|
||||
import N8nMarkdown from '../N8nMarkdown';
|
||||
import Resize from './Resize';
|
||||
import N8nText from '../N8nText';
|
||||
import Locale from '../../mixins/locale';
|
||||
import mixins from 'vue-typed-mixins';
|
||||
|
||||
export default mixins(Locale).extend({
|
||||
name: 'n8n-sticky',
|
||||
props: {
|
||||
content: {
|
||||
type: String,
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
default: 180,
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
default: 240,
|
||||
},
|
||||
minHeight: {
|
||||
type: Number,
|
||||
default: 80,
|
||||
},
|
||||
minWidth: {
|
||||
type: Number,
|
||||
default: 150,
|
||||
},
|
||||
scale: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
gridSize: {
|
||||
type: Number,
|
||||
default: 20,
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: '0',
|
||||
},
|
||||
defaultText: {
|
||||
type: String,
|
||||
},
|
||||
editMode: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
N8nInput,
|
||||
N8nMarkdown,
|
||||
Resize,
|
||||
N8nText,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isResizing: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
resHeight(): number {
|
||||
if (this.height < this.minHeight) {
|
||||
return this.minHeight;
|
||||
}
|
||||
return this.height;
|
||||
},
|
||||
resWidth(): number {
|
||||
if (this.width < this.minWidth) {
|
||||
return this.minWidth;
|
||||
}
|
||||
return this.width;
|
||||
},
|
||||
styles() {
|
||||
return {
|
||||
height: this.resHeight + 'px',
|
||||
width: this.resWidth + 'px',
|
||||
};
|
||||
},
|
||||
shouldShowFooter() {
|
||||
return this.resHeight > 100 && this.resWidth > 155;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onDoubleClick() {
|
||||
if (!this.readOnly) {
|
||||
this.$emit('edit', true);
|
||||
}
|
||||
},
|
||||
onInputBlur(value) {
|
||||
if (!this.isResizing) {
|
||||
this.$emit('edit', false);
|
||||
}
|
||||
},
|
||||
onInput(value: string) {
|
||||
this.$emit('input', value);
|
||||
},
|
||||
onResize(values) {
|
||||
this.$emit('resize', values);
|
||||
},
|
||||
onResizeEnd(resizeEnd) {
|
||||
this.isResizing = false;
|
||||
this.$emit('resizeend', resizeEnd);
|
||||
},
|
||||
onResizeStart() {
|
||||
this.isResizing = true;
|
||||
this.$emit('resizestart');
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
editMode(newMode, prevMode) {
|
||||
setTimeout(() => {
|
||||
if (newMode && !prevMode && this.$refs.input && this.$refs.input.$refs && this.$refs.input.$refs.textarea) {
|
||||
const textarea = this.$refs.input.$refs.textarea;
|
||||
if (this.defaultText === this.content) {
|
||||
textarea.select();
|
||||
}
|
||||
textarea.focus();
|
||||
}
|
||||
}, 100);
|
||||
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.sticky {
|
||||
position: absolute;
|
||||
background-color: var(--color-sticky-default-background);
|
||||
border: 1px solid var(--color-sticky-default-border);
|
||||
border-radius: var(--border-radius-base);
|
||||
}
|
||||
|
||||
.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
padding: var(--spacing-2xs) var(--spacing-xs) 0;
|
||||
overflow: hidden;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
width: 100%;
|
||||
height: 24px;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
position: absolute;
|
||||
background: linear-gradient(180deg, var(--color-sticky-default-background), #fff5d600 0.01%, var(--color-sticky-default-background));
|
||||
border-radius: var(--border-radius-base);
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: var(--spacing-5xs) var(--spacing-2xs) 0 var(--spacing-2xs);
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
.sticky-textarea {
|
||||
height: calc(100% - var(--spacing-l));
|
||||
padding: var(--spacing-2xs) var(--spacing-2xs) 0 var(--spacing-2xs);
|
||||
cursor: default;
|
||||
|
||||
.el-textarea {
|
||||
height: 100%;
|
||||
|
||||
.el-textarea__inner {
|
||||
height: 100%;
|
||||
resize: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.full-height {
|
||||
height: calc(100% - var(--spacing-2xs));
|
||||
}
|
||||
</style>
|
||||
3
packages/design-system/src/components/N8nSticky/index.js
Normal file
3
packages/design-system/src/components/N8nSticky/index.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import Sticky from './Sticky.vue';
|
||||
|
||||
export default Sticky;
|
||||
@@ -57,6 +57,7 @@ import N8nOption from './N8nOption';
|
||||
import N8nRadioButtons from './N8nRadioButtons';
|
||||
import N8nSelect from './N8nSelect';
|
||||
import N8nSpinner from './N8nSpinner';
|
||||
import N8nSticky from './N8nSticky';
|
||||
import N8nSquareButton from './N8nSquareButton';
|
||||
import N8nTags from './N8nTags';
|
||||
import N8nTabs from './N8nTabs';
|
||||
@@ -93,6 +94,7 @@ export {
|
||||
N8nRadioButtons,
|
||||
N8nSelect,
|
||||
N8nSpinner,
|
||||
N8nSticky,
|
||||
N8nSquareButton,
|
||||
N8nTabs,
|
||||
N8nTags,
|
||||
|
||||
@@ -16,4 +16,5 @@ export default {
|
||||
config.minimum > 1 ? 's' : ''
|
||||
}`),
|
||||
"formInput.validator.defaultPasswordRequirements": "8+ characters, at least 1 number and 1 capital letter",
|
||||
"sticky.markdownHint": `You can style with <a href="https://docs.n8n.io/workflows/sticky-notes/" target="_blank">Markdown</a>`,
|
||||
};
|
||||
|
||||
@@ -166,3 +166,17 @@ import ColorCircles from './ColorCircles.vue';
|
||||
}}
|
||||
</Story>
|
||||
</Canvas>
|
||||
|
||||
## Sticky
|
||||
|
||||
<Canvas>
|
||||
<Story name="sticky">
|
||||
{{
|
||||
template: `<color-circles :colors="['--color-sticky-default-background', '--color-sticky-default-border']" />`,
|
||||
components: {
|
||||
ColorCircles,
|
||||
},
|
||||
}}
|
||||
</Story>
|
||||
</Canvas>
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ export const escapeMarkdown = (html: string | undefined): string => {
|
||||
return '';
|
||||
}
|
||||
const escaped = html.replace(/</g, "<").replace(/>/g, ">");
|
||||
|
||||
// unescape greater than quotes at start of line
|
||||
const withQuotes = escaped.replace(/^((\s)*(>)+)+\s*/gm, (matches) => {
|
||||
return matches.replace(/>/g, '>');
|
||||
|
||||
@@ -70,13 +70,6 @@
|
||||
var(--color-secondary-l)
|
||||
);
|
||||
|
||||
--color-secondary-tint-1-l: 92%;
|
||||
--color-secondary-tint-1: hsl(
|
||||
var(--color-secondary-h),
|
||||
var(--color-secondary-s),
|
||||
var(--color-secondary-tint-1-l)
|
||||
);
|
||||
|
||||
--color-success-h: 150.4;
|
||||
--color-success-s: 60%;
|
||||
--color-success-l: 40.4%;
|
||||
@@ -340,6 +333,24 @@
|
||||
--color-json-line: #bfcbd9;
|
||||
--color-json-highlight: #E2E5EE;
|
||||
|
||||
--color-sticky-default-background-h: 46;
|
||||
--color-sticky-default-background-s: 100%;
|
||||
--color-sticky-default-background-l: 92%;
|
||||
--color-sticky-default-background: hsl(
|
||||
var(--color-sticky-default-background-h),
|
||||
var(--color-sticky-default-background-s),
|
||||
var(--color-sticky-default-background-l)
|
||||
);
|
||||
|
||||
--color-sticky-default-border-h: 43;
|
||||
--color-sticky-default-border-s: 75%;
|
||||
--color-sticky-default-border-l: 80%;
|
||||
--color-sticky-default-border: hsl(
|
||||
var(--color-sticky-default-border-h),
|
||||
var(--color-sticky-default-border-s),
|
||||
var(--color-sticky-default-border-l)
|
||||
);
|
||||
|
||||
--border-radius-xlarge: 12px;
|
||||
--border-radius-large: 8px;
|
||||
--border-radius-base: 4px;
|
||||
|
||||
Reference in New Issue
Block a user