diff --git a/README.md b/README.md index 3abec47f..e6e6533c 100644 --- a/README.md +++ b/README.md @@ -5,15 +5,16 @@ Frontend проекта "Notion" команды scratch senior devs # Деплой * [YouNote](https://you-note.ru) -# API -* [swagger](https://you-note.ru/api/swagger/index.html) # Figma -* [figma](https://www.figma.com/design/34KaYrmEwdC9vuisY3BoID/YouNote?node-id=0%3A1&t=mdkq7otCB1sFFwJ2-1) +* [Figma](https://www.figma.com/design/34KaYrmEwdC9vuisY3BoID/YouNote?node-id=0%3A1&t=mdkq7otCB1sFFwJ2-1) # Реактивная библиотека * [ScReact](https://www.npmjs.com/package/@veglem/screact) +# UI Kit +* [UI Kit](https://www.npmjs.com/package/@veglem/ui-kit) + # Авторы * [Михалёв Ярослав](https://github.com/YarikMix) - _frontend_ * [Журмилов Вадим](https://github.com/veglem) - _frontend_ @@ -27,3 +28,7 @@ Frontend проекта "Notion" команды scratch senior devs # Backend * [Backend](https://github.com/go-park-mail-ru/2024_1_scratch_senior_devs) + +# Как запустить +* npm install +* npm run serve \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 1f365207..a8f55fdc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "@typescript-eslint/eslint-plugin": "^7.7.0", "@typescript-eslint/parser": "^7.7.0", "@veglem/screact": "^1.0.13", + "@veglem/ui-kit": "^1.0.8", "@webpack-cli/generators": "^3.0.7", "autoprefixer": "^10.4.19", "babel-loader": "^9.1.3", @@ -4087,6 +4088,12 @@ "integrity": "sha512-IQ+pAWnH/DAPCiM8O4SIJtR5tXVKl3p4WuotQmDjZOIA0rQRHiVEJ6wpsY+I07lgf6ftOyEGXXi12B2u4hihAg==", "dev": true }, + "node_modules/@veglem/ui-kit": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@veglem/ui-kit/-/ui-kit-1.0.8.tgz", + "integrity": "sha512-yxMP3RjjPY3GHIywubibl2xH3Tr0vUwGJl+dP0JVFWaulHJdFwyOMr6r+j7p0Ds2n+Z6jWpfFT//HeYnZGnzYg==", + "dev": true + }, "node_modules/@webassemblyjs/ast": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", diff --git a/package.json b/package.json index 90a28a7f..6688074a 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "serve": "webpack serve --node-env=development" }, "devDependencies": { + "@veglem/ui-kit": "^1.0.8", "@babel/cli": "^7.23.9", "@babel/core": "^7.24.0", "@babel/plugin-proposal-decorators": "^7.24.0", diff --git a/public/index.sass b/public/index.sass index 7c119857..db6e22f0 100644 --- a/public/index.sass +++ b/public/index.sass @@ -22,4 +22,5 @@ body top: 20px &.locked + max-height: 100vh overflow: hidden diff --git a/public/index.ts b/public/index.ts index b1d2046f..75c11e8a 100644 --- a/public/index.ts +++ b/public/index.ts @@ -2,6 +2,7 @@ import {renderDOM} from '@veglem/screact/dist/render'; import {ScReact} from '@veglem/screact'; import {App} from './src/App'; import './index.sass'; +import '@veglem/ui-kit/dist/main.css'; renderDOM('root', ScReact.createComponent(App, {})); diff --git a/public/src/assets/info_circle.svg b/public/src/assets/info_circle.svg new file mode 100644 index 00000000..3ea15d3d --- /dev/null +++ b/public/src/assets/info_circle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/src/assets/user.svg b/public/src/assets/user.svg new file mode 100644 index 00000000..82b9c811 --- /dev/null +++ b/public/src/assets/user.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/src/components/Button/Button.sass b/public/src/components/Button/Button.sass deleted file mode 100644 index a04128ad..00000000 --- a/public/src/components/Button/Button.sass +++ /dev/null @@ -1,25 +0,0 @@ -@import "/public/src/utils/variables.sass" - -.button - background-color: $primary-color - border-radius: 20px - padding: 8px 16px - border: none - outline: none - color: $white - text-align: center - cursor: pointer - transition: all 0.3s - - &.disabled - cursor: not-allowed - opacity: 0.8 - - &:hover:not(.disabled) - transform: scale(1.05) - - &:active:not(.disabled) - transform: scale(0.98) - - &.hidden - display: none diff --git a/public/src/components/Button/Button.tsx b/public/src/components/Button/Button.tsx deleted file mode 100644 index 63347874..00000000 --- a/public/src/components/Button/Button.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import {ScReact} from '@veglem/screact'; -import './Button.sass'; - -export class Button extends ScReact.Component{ - - handleClick = (e) => { - this.props.onClick && !this.props.disabled && this.props.onClick(e); - }; - - render() { - return ( - - ); - } -} diff --git a/public/src/components/DeleteNoteDialog/DeleteNoteDialog.tsx b/public/src/components/DeleteNoteDialog/DeleteNoteDialog.tsx index c9b502c7..956f55e5 100644 --- a/public/src/components/DeleteNoteDialog/DeleteNoteDialog.tsx +++ b/public/src/components/DeleteNoteDialog/DeleteNoteDialog.tsx @@ -1,7 +1,7 @@ import {ScReact} from '@veglem/screact'; -import {Button} from '../Button/Button'; import './DeleteNoteDialog.sass'; import {Img} from "../Image/Image"; +import {uiKit} from '@veglem/ui-kit/dist/ui'; export class DeleteNoteDialog extends ScReact.Component{ @@ -11,6 +11,8 @@ export class DeleteNoteDialog extends ScReact.Component{ }; render() { + const {Button} = uiKit + return (
diff --git a/public/src/components/DeleteTagDialog/DeleteTagDialog.tsx b/public/src/components/DeleteTagDialog/DeleteTagDialog.tsx index 4630fc40..f8ff3ec2 100644 --- a/public/src/components/DeleteTagDialog/DeleteTagDialog.tsx +++ b/public/src/components/DeleteTagDialog/DeleteTagDialog.tsx @@ -1,7 +1,7 @@ import {ScReact} from '@veglem/screact'; -import {Button} from '../Button/Button'; import './DeleteTagDialog.sass'; import {Img} from "../Image/Image"; +import {uiKit} from '@veglem/ui-kit/dist/ui'; export class DeleteTagDialog extends ScReact.Component{ @@ -11,6 +11,8 @@ export class DeleteTagDialog extends ScReact.Component{ }; render() { + const {Button} = uiKit + return (
diff --git a/public/src/components/Dropdown/Dropdown.tsx b/public/src/components/Dropdown/Dropdown.tsx index 8b1c78a5..1fbbaceb 100644 --- a/public/src/components/Dropdown/Dropdown.tsx +++ b/public/src/components/Dropdown/Dropdown.tsx @@ -2,7 +2,6 @@ import {ScReact} from '@veglem/screact'; import './Dropdown.sass'; import {Img} from '../Image/Image'; import {AppDispatcher} from '../../modules/dispatcher'; -import {AppNoteStore, NoteStoreActions} from '../../modules/stores/NoteStore'; import {AppNotesStore, NotesActions} from '../../modules/stores/NotesStore'; import {MAX_ATTACH_SIZE} from '../../utils/consts'; import {AppToasts} from '../../modules/toasts'; @@ -47,9 +46,6 @@ export class Dropdown extends ScReact.Component { }; handleOnClick = (id:string) => { - let tag = id; - let attr = null; - let content = []; if (id === "h1") { insertBlockPlugin('header', 'h1') diff --git a/public/src/components/Editor/Editor.ts b/public/src/components/Editor/Editor.ts index 8aec5255..2d17e672 100644 --- a/public/src/components/Editor/Editor.ts +++ b/public/src/components/Editor/Editor.ts @@ -35,6 +35,30 @@ export class Editor { this.tippyCallbacks = tippy; this.addPlugins(); + document.onpaste = (event) => { + const isInEditor = (node: Node) => { + if (node.nodeType === Node.ELEMENT_NODE && (node as HTMLElement).contentEditable === 'true' && !(node as HTMLElement).classList.contains("note-title")) { + return true + } else if (node.parentElement == null) { + return false + } else { + return isInEditor(node.parentElement); + } + } + + + + if (isInEditor(document.getSelection().anchorNode)) { + event.preventDefault(); + let paste = (event.clipboardData).getData("text"); + const selection = window.getSelection(); + if (!selection.rangeCount) return; + selection.deleteFromDocument(); + selection.getRangeAt(0).insertNode(document.createTextNode(paste)); + selection.collapseToEnd(); + } + } + this.editable = document.createElement('div'); this.editable.id = "note-editor-inner" this.editable.contentEditable = "true"; @@ -105,7 +129,7 @@ export class Editor { : selection.anchorNode.parentElement; scanTree(this.editable); - console.log(`cursor${AppUserStore.state.username}${AppNotesStore.socket_id?.toString().replaceAll('-','').toLowerCase()}`) + elem.dataset[`cursor${AppUserStore.state.username}${AppNotesStore.socket_id?.toString().replaceAll('-','').toLowerCase()}`] = `${getCaretPosition(elem)}`; // elem.scrollIntoView(); diff --git a/public/src/components/Editor/Plugin.ts b/public/src/components/Editor/Plugin.ts index c4a7723c..104d30bc 100644 --- a/public/src/components/Editor/Plugin.ts +++ b/public/src/components/Editor/Plugin.ts @@ -1,10 +1,11 @@ import {AppUserStore} from "../../modules/stores/UserStore"; import {parseNoteTitle, setCursorAtNodePosition, truncate} from "../../modules/utils"; import {AppNotesStore, NotesActions} from "../../modules/stores/NotesStore"; -import {AppNoteRequests} from "../../modules/api"; +import {AppNoteRequests, AppSharedNoteRequests} from '../../modules/api'; import {AppDispatcher} from "../../modules/dispatcher"; import {AppToasts} from "../../modules/toasts"; import {AppNoteStore, NoteStoreActions} from "../../modules/stores/NoteStore"; +import {AppRouter} from '../../modules/router'; interface EditorPlugin { pluginName: string; @@ -494,8 +495,6 @@ export const defaultPlugins: EditorPlugin[] = [ img.dataset.imgid = id; img.className = "img" - - if (id in AppNoteStore.state.cache) { img.src = AppNoteStore.state.cache[id] } else { @@ -503,7 +502,7 @@ export const defaultPlugins: EditorPlugin[] = [ img.src = url; AppDispatcher.dispatch(NoteStoreActions.PUT_TO_CACHE, {key: id, value: url}) }).catch(error => { - console.log(error) + }) } @@ -525,7 +524,7 @@ export const defaultPlugins: EditorPlugin[] = [ img.src = url; AppDispatcher.dispatch(NoteStoreActions.PUT_TO_CACHE, {key: id, value: url}) }).catch(error => { - console.log(error) + }) return img @@ -912,7 +911,7 @@ export const insertBlockPlugin = (pluginName: string, ...args: any) => { }); } const newNode = plugin.insertNode([], args); - console.log(newNode) + if (newNode) { (nodeToReplace as HTMLElement).replaceWith(newNode); document.getSelection().setPosition(newNode, 0); @@ -1051,6 +1050,8 @@ const RenderAttach = (attach_filename:string, attach_id:string) => { } const RenderSubNote = (subNoteId:string) => { + + const subNoteWrapper = document.createElement("button") subNoteWrapper.className = "subnote-wrapper" @@ -1072,8 +1073,9 @@ const RenderSubNote = (subNoteId:string) => { subNoteContainer.appendChild(subNoteTitle) const isOwner= AppNotesStore.state.selectedNote?.owner_id == AppUserStore.state.user_id + const isAuth = AppUserStore.state.isAuth - if (isOwner) { + if (isAuth && isOwner && pluginSettings.isEditable) { const deleteSubNoteBtnContainer = document.createElement("div") deleteSubNoteBtnContainer.className = "delete-subnote-btn-container" @@ -1095,7 +1097,6 @@ const RenderSubNote = (subNoteId:string) => { } } - deleteSubNoteBtnContainer.appendChild(deleteSubNoteBtn) subNoteContainer.appendChild(deleteSubNoteBtnContainer) } @@ -1104,35 +1105,58 @@ const RenderSubNote = (subNoteId:string) => { let loaded = false - if (subNoteId in AppNoteStore.state.cache) { - subNoteTitle.innerHTML = AppNoteStore.state.cache[subNoteId] - loaded = true - } else { - AppNoteRequests.Get(subNoteId, AppUserStore.state.JWT).then(result => { + // if (subNoteId in AppNoteStore.state.cache) { + // subNoteTitle.innerHTML = AppNoteStore.state.cache[subNoteId] + // loaded = true + // } else { + // + // const request = pluginSettings.isEditable ? AppNoteRequests.Get(subNoteId, AppUserStore.state.JWT) : AppSharedNoteRequests.Get(subNoteId) + // request.then(result => { + // if (result.data.title == null) { + // subNoteTitle.innerHTML = 'Подзаметка' + // } + // + // //subNoteWrapper.dataset.title = parseNoteTitle(result.data.title) + // subNoteTitle.innerHTML = parseNoteTitle(result.data.title) + // + // AppDispatcher.dispatch(NoteStoreActions.PUT_TO_CACHE, {key: subNoteId, value: parseNoteTitle(result.data.title)}) + // + // loaded = true + // + // }).catch((e) => { + // subNoteTitle.innerHTML = "Заметка не найдена" + // subNoteWrapper.dataset.deleted = "true" + // }); + // } + + const request = pluginSettings.isEditable ? AppNoteRequests.Get(subNoteId, AppUserStore.state.JWT) : AppSharedNoteRequests.Get(subNoteId) + request.then(result => { if (result.data.title == null) { subNoteTitle.innerHTML = 'Подзаметка' } - //subNoteWrapper.dataset.title = parseNoteTitle(result.data.title) subNoteTitle.innerHTML = parseNoteTitle(result.data.title) - AppDispatcher.dispatch(NoteStoreActions.PUT_TO_CACHE, {key: subNoteId, value: parseNoteTitle(result.data.title)}) - loaded = true }).catch((e) => { subNoteTitle.innerHTML = "Заметка не найдена" subNoteWrapper.dataset.deleted = "true" }); - } - if (pluginSettings.isEditable) { - subNoteWrapper.onclick = () => { - if (!subNoteWrapper.dataset.deleted && loaded) { + subNoteWrapper.onclick = () => { + if (!subNoteWrapper.dataset.deleted && loaded) { + if (isAuth) { AppDispatcher.dispatch(NotesActions.OPEN_NOTE, subNoteId) } else { - AppToasts.error("Заметка не найдена") + AppSharedNoteRequests.Get(subNoteId).then(result => { + AppRouter.openSharedNotePage(result) + }).catch((e) => { + AppToasts.error("Заметка не найдена") + }); } + } else { + AppToasts.error("Заметка не найдена") } } diff --git a/public/src/components/Editor/EditorWrapper.tsx b/public/src/components/EditorWrapper/EditorWrapper.tsx similarity index 95% rename from public/src/components/Editor/EditorWrapper.tsx rename to public/src/components/EditorWrapper/EditorWrapper.tsx index 8c127468..9b372798 100644 --- a/public/src/components/Editor/EditorWrapper.tsx +++ b/public/src/components/EditorWrapper/EditorWrapper.tsx @@ -1,13 +1,13 @@ import {Component} from "@veglem/screact/dist/component"; import {VDomNode} from "@veglem/screact/dist/vdom"; -import "./Editor.sass" -import {Editor} from "./Editor"; +import "../Editor/Editor.sass" +import {Editor} from "../Editor/Editor"; import {AppNoteStore, NoteStoreState} from "../../modules/stores/NoteStore"; import {Tippy} from "../Tippy/Tippy"; import {isEqual} from "@veglem/screact/dist/isEqual"; -import {Viewer} from "./Viewer"; +import {Viewer} from "../Editor/Viewer"; import {NoteType} from "../../utils/types"; -import {PluginProps} from "./Plugin"; +import {PluginProps} from "../Editor/Plugin"; import {parseNoteTitle} from "../../modules/utils"; window['mobileCheck'] = function() { @@ -61,7 +61,7 @@ export class EditorWrapper extends Component { updateState = (store:NoteStoreState) => { this.syncTitle(store.note.title) - if (!this.props.isOwner && this.props.note?.public) { + if (!this.props.isOwner && this.props.note?.public && !this.props.isEditable) { this.self.innerHTML = "" new Viewer( store.note.blocks, diff --git a/public/src/components/Header/Header.tsx b/public/src/components/Header/Header.tsx index 1342ff1c..e96ac6b4 100644 --- a/public/src/components/Header/Header.tsx +++ b/public/src/components/Header/Header.tsx @@ -1,6 +1,5 @@ import {ScReact} from '@veglem/screact'; import './Header.sass'; -import {Button} from '../Button/Button'; import {AppRouter} from '../../modules/router'; import {Logo} from '../Logo/logo'; import {Profile} from '../Profile/Profile'; @@ -8,6 +7,7 @@ import {AuthPage} from '../../pages/Auth'; import {AppUserStore, UserActions, UserStoreState} from '../../modules/stores/UserStore'; import {AppDispatcher} from '../../modules/dispatcher'; import {AppNotesStore, NotesStoreState} from "../../modules/stores/NotesStore"; +import {uiKit} from '@veglem/ui-kit/dist/ui'; export class Header extends ScReact.Component{ state = { @@ -44,6 +44,8 @@ export class Header extends ScReact.Component{ }; render() { + const {Button} = uiKit + return (