diff --git a/README.md b/README.md index d0743be..79795d9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # DayFlow -Персональный органайзер знаний: воркспейсы, карточки (заметки, ссылки, чеклисты), инструменты, трекинг обучения и прогресса. +Персональный органайзер знаний: воркспейсы, карточки (заметки, ссылки, чеклисты), роадмапы, инструменты, совместное редактирование, трекинг обучения и прогресса. ## Стек @@ -10,40 +10,84 @@ ## Возможности -- **Воркспейсы** — доски для тем/проектов с колонками и беклогом -- **Карточки** — три типа: заметка, ссылка (с превью), чеклист -- **Хаб (библиотека)** — все карточки пользователя без привязки к воркспейсу -- **Инструменты** — ссылки/ресурсы, привязанные к воркспейсу или хабу; поиск по названию и тегам +### Воркспейсы +- Доски для тем/проектов с колонками и беклогом +- Эмодзи-иконка для каждого воркспейса +- Два режима: **Доска** и **Роадмап** (табы) +- Цвет колонок (хедер + бордер) +- Скрытие выполненных карточек (кнопка-глазик, состояние в БД) +- Пин (закрепление воркспейсов) + +### Карточки +- Три типа: **заметка**, **ссылка**, **чеклист** +- Теги, статус обучения, порядок (drag & drop между колонками) +- **Конспект** — markdown-конспект в каждой карточке (все типы), редактор через модалку +- **AI-промпт** — генерация промпта для ИИ по содержимому карточки +- Модалка «Все конспекты» — просмотр и экспорт в `.md` + +### Роадмап +- Один роадмап на воркспейс — дерево узлов (до 4 уровней вложенности) +- Создание из вложенного текста (вставка + парсинг отступов) +- Добавление / редактирование / удаление / реордер узлов +- Прогресс-бар (done/total) +- LLM-промпт для генерации роадмапа и промпт по конкретному узлу + +### Совместное редактирование +- **Приглашения** — владелец генерирует invite-ссылку (`/invite/:token`), другие принимают +- **Участники** — список членов воркспейса, удаление участника (только владелец) +- **Блокировка редактирования** — один редактирует, остальные в read-only; heartbeat каждые 15с +- **Передача лока** — передать права редактирования другому участнику +- **Индикатор** — замочек + аватар редактирующего в хедере +- **Smart-синхронизация** — фоновый poll каждые 10с с точечным патчем изменившихся полей (без полного ре-рендера) + +### Хаб (библиотека) +- Все карточки пользователя без привязки к воркспейсу +- Пагинация, фильтры, сортировка + +### Инструменты +- Ссылки/ресурсы, привязанные к воркспейсу или хабу +- Иконка, описание, теги +- Поиск и фильтрация по названию и тегам + +### Обучение +- Три статуса: «повторить», «остались вопросы», «углубить» +- Вью с группировкой по воркспейсам, сворачиваемые секции + +### Прочее - **Теги** — фильтрация и навигация по тегам карточек -- **Обучение** — три статуса: «повторить», «остались вопросы», «углубить»; отдельные вью с группировкой по воркспейсам -- **Drag & Drop** — перетаскивание карточек между колонками - **Поиск** — по заголовкам и тегам (Ctrl+K — быстрое добавление) -- **Скрытие выполненных** — кнопка-глазик в колонке, состояние хранится в БД -- **Сворачивание секций** — в обучении и инструментах секции по воркспейсам сворачиваются +- **Drag & Drop** — перетаскивание карточек между колонками и беклогом - **Статистика** — публичная страница `/user/:id` с прогрессом по воркспейсам -- **7 тем оформления** — Light, Dark, Old Money, Nord, Solarized Dark, Full Moon, Old Money 2 -- **Авторизация** — email + пароль, httpOnly cookie сессия +- **Профиль** — смена аватара (URL) +- **7 тем оформления** — Light, Dark (Obsidian), Old Money, Old Money II, Nord, Solarized Dark, Full Moon; переключение light/dark с запоминанием предпочтений +- **Авторизация** — email + пароль (argon2), httpOnly cookie сессия +- **Rate limiting** — ограничение запросов по IP/пользователю ## Структура ``` ├── client/ # Vue 3 SPA │ ├── src/ -│ │ ├── components/ # card/, common/, workspace/, toolbox/ -│ │ ├── views/ # Home, Auth, Library, Workspace, Profile, -│ │ │ # UserStats, Tags, Tools, Learning -│ │ ├── composables/ # useCardActions, useCardForm, useInlineEdit -│ │ ├── stores/ # Pinia: auth, workspace, cards, theme +│ │ ├── components/ # card/, common/, workspace/, toolbox/, roadmap/ +│ │ ├── views/ # Home, Auth, Library, Workspace (Board + Roadmap), +│ │ │ # Profile, UserStats, Tags, Tools, Learning, Invite +│ │ ├── composables/ # useCardActions, useCardForm, useInlineEdit, +│ │ │ # useCardGrouping +│ │ ├── stores/ # Pinia: auth, workspace, roadmap, cards, theme │ │ ├── graphql/ # queries, mutations, types -│ │ └── lib/ # apollo, graphql-error, constants, utils +│ │ └── lib/ # apollo, graphql-error, constants, utils, +│ │ # patch-workspace, card-payload │ └── public/ ├── server/ # GraphQL API │ ├── src/ -│ │ ├── resolvers/ # auth, workspace, column, card, tool, user-stats +│ │ ├── resolvers/ # auth, workspace, column, card, tool, +│ │ │ # roadmap, user-stats │ │ ├── schema/ # schema.graphql -│ │ └── lib/ # prisma, auth, context, errors, constants +│ │ └── lib/ # prisma, auth, context, errors, constants, +│ │ # lock-middleware, workspace-access, rate-limiter, +│ │ # dataloaders │ └── prisma/ # schema.prisma -└── shared/ # Общие типы и ErrorCodes (dayflow-shared) +└── shared/ # Общие типы, ErrorCodes, лимиты (dayflow-shared) ``` ## Модели данных @@ -52,10 +96,13 @@ |--------|----------| | **User** | email, пароль (argon2), аватар | | **Session** | httpOnly cookie сессии | -| **Workspace** | доска с колонками, карточками и инструментами; пин | -| **Column** | колонка воркспейса; порядок, скрытие выполненных | -| **Card** | заметка / ссылка / чеклист; теги, статус обучения, порядок | +| **Workspace** | доска с колонками, карточками, инструментами; пин, иконка (эмодзи), invite-токен, editingBy (лок) | +| **WorkspaceMember** | участник воркспейса (userId, joinedAt) | +| **Column** | колонка воркспейса; порядок, скрытие выполненных, цвет | +| **Card** | заметка / ссылка / чеклист; теги, статус обучения, порядок, конспект (в payload) | | **Tool** | инструмент (ссылка + описание + иконка + теги) | +| **Roadmap** | роадмап воркспейса (один на воркспейс); title, sourceText | +| **RoadmapNode** | узел роадмапа; parent/children, order, done | ## Обработка ошибок diff --git a/TODO.md b/TODO.md index 32fe321..4cb49da 100644 --- a/TODO.md +++ b/TODO.md @@ -25,7 +25,6 @@ - загрузка фото из буфера в виде карточки (яндекс диск) - AI-саммари — автоматическое резюме по карточке - AI-саммари конкретно по ссылке пишет в конспект (карточка типа ссылка) кнопка получить краткую выжимку -- совместный доступ к воркспейсам - Новый вью (пространство как в миро) - Отображать превью ссылок (если тип ссылка) diff --git a/client/package-lock.json b/client/package-lock.json index 86a7c40..abc03ee 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -98,6 +98,7 @@ "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.14.0.tgz", "integrity": "sha512-0YQKKRIxiMlIou+SekQqdCo0ZTHxOcES+K8vKB53cIDpwABNR0P0yRzPgsbgcj3zRJniD93S/ontsnZsCLZrxQ==", "license": "MIT", + "peer": true, "dependencies": { "@graphql-typed-document-node/core": "^3.1.1", "@wry/caches": "^1.0.0", @@ -191,6 +192,7 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -4408,6 +4410,7 @@ "integrity": "sha512-5jsi0wpncvTD33Sh1UCgacK37FFwDn+EG7wCmEvs62fCvBL+n8/76cAYDok21NF6+jaVWIqKwCZyX7Vbu8eB3A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -4480,6 +4483,7 @@ "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/types": "8.54.0", @@ -5457,6 +5461,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5694,6 +5699,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -6491,6 +6497,7 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -7017,6 +7024,7 @@ "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.12.0.tgz", "integrity": "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==", "license": "MIT", + "peer": true, "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } @@ -7110,6 +7118,7 @@ "integrity": "sha512-yoLRW+KRlDmnnROdAu7sX77VNLC0bsFoZyGQJLy1cF+X/SkLg/fWkRGrEEYQK8o2cafJ2wmEaMqMEZB3U3DYDg==", "devOptional": true, "license": "MIT", + "peer": true, "engines": { "node": ">=20" }, @@ -7478,6 +7487,7 @@ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "dev": true, "license": "MIT", + "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -8855,6 +8865,7 @@ "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -9317,6 +9328,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -9435,6 +9447,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -9684,6 +9697,7 @@ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", @@ -9750,6 +9764,7 @@ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.27.tgz", "integrity": "sha512-aJ/UtoEyFySPBGarREmN4z6qNKpbEguYHMmXSiOGk69czc+zhs0NF6tEFrY8TZKAl8N/LYAkd4JHVd5E/AsSmw==", "license": "MIT", + "peer": true, "dependencies": { "@vue/compiler-dom": "3.5.27", "@vue/compiler-sfc": "3.5.27", @@ -9997,6 +10012,7 @@ "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10.0.0" }, diff --git a/client/src/components/card/CardItem.vue b/client/src/components/card/CardItem.vue index b4d3dd3..459198f 100644 --- a/client/src/components/card/CardItem.vue +++ b/client/src/components/card/CardItem.vue @@ -23,8 +23,10 @@ const props = withDefaults( isBacklog?: boolean; /** Отключить drag-курсор (для Tags view и т.п.) */ static?: boolean; + /** Режим только чтение (чужой лок) */ + readOnly?: boolean; }>(), - { isBacklog: false, static: false } + { isBacklog: false, static: false, readOnly: false } ); const emit = defineEmits<{ @@ -173,6 +175,7 @@ function copyAiPrompt() {