Skip to content

Commit

Permalink
Merge remote-tracking branch 'cloudforet-io/master' into feature-proj…
Browse files Browse the repository at this point in the history
…ect-alert-manager

Signed-off-by: NaYeong,Kim <[email protected]>
  • Loading branch information
skdud4659 committed Jan 9, 2025
2 parents d9ae42a + b03c42a commit 388b256
Show file tree
Hide file tree
Showing 108 changed files with 1,742 additions and 807 deletions.
6 changes: 3 additions & 3 deletions apps/storybook/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export default {
module.exports = {
root: false,
extends: ["custom"],
extends: ['custom'],
rules: {
// eslint-plugin-import rules
'import/order': [
Expand Down Expand Up @@ -49,4 +49,4 @@ export default {
typescript: {},
},
},
};
};
9 changes: 8 additions & 1 deletion apps/storybook/tailwind.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
const tailwindConfig = require('../../packages/mirinae/tailwind.config.cjs');

module.exports = tailwindConfig;
module.exports = {
content: [
'../../packages/mirinae/src/**/*.{js,ts,jsx,tsx,vue}',
],
theme: tailwindConfig.theme,
variants: tailwindConfig.variants,
plugins: tailwindConfig.plugins,
};
2 changes: 1 addition & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "web",
"version": "2.0.0-dev264",
"version": "2.0.0-dev278",
"private": true,
"description": "Cloudforet Console Web Application",
"author": "Cloudforet",
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
104 changes: 51 additions & 53 deletions apps/web/src/common/components/editor/TextEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,33 +14,32 @@ import type { AnyExtension } from '@tiptap/vue-2';
import { Editor, EditorContent } from '@tiptap/vue-2';
import { Markdown } from 'tiptap-markdown';
import { PTextarea } from '@cloudforet/mirinae';
import { createImageExtension } from '@/common/components/editor/extensions/image';
import { getAttachmentIds, setAttachmentsToContents } from '@/common/components/editor/extensions/image/helper';
import type { Attachment, ImageUploader } from '@/common/components/editor/extensions/image/type';
import type { ImageUploader } from '@/common/components/editor/extensions/image/type';
import MenuBar from '@/common/components/editor/MenuBar.vue';
import type { TextEditorContentsType } from '@/common/components/editor/type';
import { loadMonospaceFonts } from '@/styles/fonts';
interface Props {
value?: string;
imageUploader?: ImageUploader;
attachments?: Attachment[];
invalid?: boolean;
placeholder?: string;
contentType?: 'html'|'markdown';
contentsType?: TextEditorContentsType;
showUndoRedoButtons?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
value: '',
imageUploader: undefined,
attachments: () => [],
invalid: false,
placeholder: '',
contentType: 'html',
contentsType: 'html',
showUndoRedoButtons: true,
});
const emit = defineEmits<{(e: 'update:value', value: string): void;
(e: 'update:attachment-ids', attachmentIds: string[]): void;
}>();
loadMonospaceFonts();
Expand Down Expand Up @@ -68,13 +67,13 @@ const getExtensions = (): AnyExtension[] => {
];
// add extensions based on content type
if (props.contentType === 'html') {
if (props.contentsType === 'html') {
extensions.push(Color);
extensions.push(TextAlign.configure({
types: ['heading', 'paragraph'],
}));
}
if (props.contentType === 'markdown') {
if (props.contentsType === 'markdown') {
extensions.push(Markdown);
}
Expand All @@ -87,18 +86,17 @@ const getExtensions = (): AnyExtension[] => {
onMounted(() => {
editor.value = new Editor({
content: setAttachmentsToContents(props.value, props.attachments),
content: props.value,
extensions: getExtensions(),
onUpdate: () => {
let content = '';
if (!editor.value) return;
if (props.contentType === 'html') {
if (props.contentsType === 'html') {
content = editor.value?.getHTML() ?? '';
} else {
content = editor.value.storage.markdown.getMarkdown() ?? '';
}
emit('update:value', content);
emit('update:attachment-ids', getAttachmentIds(editor.value));
},
});
});
Expand All @@ -107,42 +105,50 @@ onBeforeUnmount(() => {
if (editor.value) editor.value.destroy();
});
watch([() => props.value, () => props.attachments], ([value, attachments], prev) => {
watch(() => props.value, (value) => {
if (!editor.value) return;
let isSame;
if (props.contentType === 'html') {
isSame = editor.value.getHTML() === value;
let contents: string;
if (props.contentsType === 'html') {
contents = editor.value?.getHTML() ?? '';
} else {
isSame = editor.value.storage.markdown.getMarkdown() === value;
contents = editor.value.storage.markdown.getMarkdown() ?? '';
}
if (isSame) return;
let newContents = value;
if (attachments !== prev[1]) newContents = setAttachmentsToContents(value, attachments);
editor.value.commands.setContent(newContents, false);
if (contents === value) return; // prevent infinite loop.
editor.value.commands.setContent(value, false);
});
</script>

<template>
<div v-if="editor"
class="text-editor"
:class="{invalid: props.invalid}"
>
<menu-bar :editor="editor"
:use-color="props.contentType === 'html'"
:use-text-align="props.contentType === 'html'"
:use-image="!!props.imageUploader"
:show-undo-redo-buttons="props.showUndoRedoButtons"
/>
<editor-content class="editor-content"
:editor="editor"
<div class="text-editor">
<p-textarea v-if="props.contentsType === 'plain'"
:value="props.value"
:placeholder="props.placeholder"
:invalid="props.invalid"
@update:value="emit('update:value', $event)"
/>
<div v-else-if="editor"
class="editor"
:class="{invalid: props.invalid}"
>
<menu-bar :editor="editor"
:use-color="props.contentsType === 'html'"
:use-text-align="props.contentsType === 'html'"
:use-image="!!props.imageUploader"
:show-undo-redo-buttons="props.showUndoRedoButtons"
/>
<editor-content class="editor-content"
:editor="editor"
/>
</div>
</div>
</template>

<style lang="postcss">
@import './text-editor-nodes.pcss';
.text-editor {
> .editor-content {
> .editor .editor-content {
.ProseMirror {
@mixin all-nodes-style;
min-height: inherit;
Expand All @@ -166,26 +172,18 @@ watch([() => props.value, () => props.attachments], ([value, attachments], prev)
.text-editor {
@apply bg-white border border-gray-200 rounded-lg;
min-height: 356px;
> .editor-content {
> .editor {
min-height: inherit;
padding: 0.75rem 1rem 1.125rem 1rem;
}
&:focus-within {
@apply border-secondary;
}
&.invalid {
@apply border-alert;
}
>.suggestion-list {
@apply absolute;
z-index: 10;
> .editor-content {
min-height: inherit;
padding: 0.75rem 1rem 1.125rem 1rem;
}
&:focus-within {
@apply border-secondary;
}
&.invalid {
@apply border-alert;
}
}
}
</style>
<style lang="postcss">
.mention {
@apply bg-violet-150 text-violet-600 rounded-md;
padding: 0 2px;
}
</style>
39 changes: 28 additions & 11 deletions apps/web/src/common/components/editor/TextEditorViewer.vue
Original file line number Diff line number Diff line change
@@ -1,45 +1,62 @@
<script setup lang="ts">
import { computed, toRef } from 'vue';
import {
computed, watch, nextTick, ref, toRef,
} from 'vue';
import DOMPurify from 'dompurify';
import { useMarkdown } from '@cloudforet/mirinae';
import { setAttachmentsToContents } from '@/common/components/editor/extensions/image/helper';
import type { Attachment } from '@/common/components/editor/extensions/image/type';
import type { TextEditorContentsType } from '@/common/components/editor/type';
import { loadMonospaceFonts } from '@/styles/fonts';
interface Props {
contents?: string;
attachments?: Attachment[];
showInBox?: boolean
contentType?: 'html'|'markdown';
contentsType?: TextEditorContentsType;
}
const props = withDefaults(defineProps<Props>(), {
contents: '',
attachments: () => [],
showInBox: false,
contentType: 'html',
contentsType: 'plain',
});
loadMonospaceFonts();
const { sanitizedHtml } = useMarkdown({
value: toRef(props, 'contents'),
inlineCodeClass: 'inline-code',
});
const refinedContents = computed(() => {
if (props.contentType === 'markdown') {
if (props.contentsType === 'markdown') {
return sanitizedHtml.value;
} if (props.contentsType === 'html') {
return DOMPurify.sanitize(props.contents);
}
const sanitized = DOMPurify.sanitize(props.contents);
return setAttachmentsToContents(sanitized, props.attachments);
// plain
return props.contents;
});
const htmlContainer = ref<null|HTMLElement>(null);
const addErrorHandlers = (container: HTMLElement) => {
container.querySelectorAll('img').forEach((img) => {
img.onerror = () => {
img.setAttribute('error', 'true');
};
});
};
watch([refinedContents, htmlContainer], async ([, container]) => {
if (!container) return;
await nextTick();
addErrorHandlers(container);
});
</script>

<template>
<!-- eslint-disable-next-line vue/no-v-html-->
<div class="text-editor-contents"
<div ref="htmlContainer"
class="text-editor-contents"
:class="{'contents-box': props.showInBox}"
v-html="refinedContents"
/>
Expand Down
45 changes: 0 additions & 45 deletions apps/web/src/common/components/editor/extensions/image/helper.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,2 @@
import type { Editor } from '@tiptap/vue-2';

import type { Attachment } from '@/common/components/editor/extensions/image/type';

// such as <p></p>
export const emptyHtmlRegExp = /<[^/>][^>]*><\/[^>]+>/;

export const getAttachmentIds = (editor: Editor): string[] => {
const contentsEl = editor.contentComponent?.$el;
if (!contentsEl) return [];
const imageElements = contentsEl.getElementsByTagName('img');
return Array.from(imageElements)
.reduce((results, imageElement) => {
const fileId = imageElement.getAttribute('file-id');
const src = imageElement.getAttribute('src');
if (fileId && src) {
results.push(fileId);
}

return results;
}, [] as string[]);
};

export const setAttachmentsToContents = (contents: string, attachments: Attachment[]): string => {
if (attachments.length === 0) return contents;

const contentsEl = document.createElement('div');
contentsEl.innerHTML = contents.trim();

const attachmentsMap = {};
attachments.forEach(({ fileId, downloadUrl }) => {
attachmentsMap[fileId] = downloadUrl;
});

const imageElements = contentsEl.getElementsByTagName('img');
Array.from(imageElements)
.forEach((imageElement) => {
const fileId = imageElement.getAttribute('file-id');
if (fileId && attachmentsMap[fileId]) {
imageElement.setAttribute('src', attachmentsMap[fileId]);
}
});

const newContents = contentsEl.innerHTML;
contentsEl.remove();
return newContents;
};
Loading

0 comments on commit 388b256

Please sign in to comment.