chore: merged main branch into beta branch

This commit is contained in:
bdbch 2024-06-26 23:53:44 +02:00
commit 8c017d88f6
16 changed files with 5054 additions and 7560 deletions

View File

@ -8,6 +8,7 @@
--gray-3: rgba(61, 37, 20, 0.12);
--gray-4: rgba(53, 38, 28, 0.30);
--gray-5: rgba(28, 25, 23, 0.60);
--green: #22C55E;
--purple: #6A00F5;
--purple-contrast: #5800CC;
--purple-light: rgba(88, 5, 255, 0.05);
@ -33,7 +34,7 @@ html {
}
body {
min-height: 10rem;
min-height: 25rem;
margin: 0;
}
@ -202,7 +203,7 @@ form {
display: flex;
flex-direction: row;
font-size: 0.75rem;
gap: 0.5rem;
gap: 0.25rem;
line-height: 1.15;
min-height: 1.75rem;
padding: 0.25rem 0.5rem;
@ -260,6 +261,16 @@ hr {
width: 100%;
}
kbd {
background-color: var(--gray-2);
border: 1px solid var(--gray-2);
border-radius: 0.25rem;
font-size: 0.6rem;
line-height: 1.15;
padding: 0.1rem 0.25rem;
text-transform: uppercase;
}
/* Layout and structure */
#app,
.container {
@ -275,10 +286,16 @@ hr {
.control-group {
align-items: flex-start;
background-color: var(--white);
display: flex;
flex-direction: column;
gap: 1rem;
margin: 1.5rem;
padding: 1.5rem;
.sticky {
position: sticky;
top: 0;
}
}
[data-node-view-wrapper] > .control-group {
@ -352,4 +369,4 @@ hr {
font-weight: 700;
line-height: 1.15;
}
}
}

View File

@ -0,0 +1,200 @@
import CharacterCount from '@tiptap/extension-character-count'
import Collaboration from '@tiptap/extension-collaboration'
import CollaborationCursor from '@tiptap/extension-collaboration-cursor'
import Highlight from '@tiptap/extension-highlight'
import TaskItem from '@tiptap/extension-task-item'
import TaskList from '@tiptap/extension-task-list'
import { EditorContent, useEditor } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
import React, { useCallback, useEffect, useState } from 'react'
const colors = [
'#958DF1',
'#F98181',
'#FBBC88',
'#FAF594',
'#70CFF8',
'#94FADB',
'#B9F18D',
'#C3E2C2',
'#EAECCC',
'#AFC8AD',
'#EEC759',
'#9BB8CD',
'#FF90BC',
'#FFC0D9',
'#DC8686',
'#7ED7C1',
'#F3EEEA',
'#89B9AD',
'#D0BFFF',
'#FFF8C9',
'#CBFFA9',
'#9BABB8',
'#E3F4F4',
]
const names = [
'Lea Thompson',
'Cyndi Lauper',
'Tom Cruise',
'Madonna',
'Jerry Hall',
'Joan Collins',
'Winona Ryder',
'Christina Applegate',
'Alyssa Milano',
'Molly Ringwald',
'Ally Sheedy',
'Debbie Harry',
'Olivia Newton-John',
'Elton John',
'Michael J. Fox',
'Axl Rose',
'Emilio Estevez',
'Ralph Macchio',
'Rob Lowe',
'Jennifer Grey',
'Mickey Rourke',
'John Cusack',
'Matthew Broderick',
'Justine Bateman',
'Lisa Bonet',
]
const defaultContent = `
<p>Hi 👋, this is a collaborative document.</p>
<p>Feel free to edit and collaborate in real-time!</p>
`
const getRandomElement = list => list[Math.floor(Math.random() * list.length)]
const getRandomColor = () => getRandomElement(colors)
const getRandomName = () => getRandomElement(names)
const getInitialUser = () => {
return (
{
name: getRandomName(),
color: getRandomColor(),
}
)
}
const Editor = ({ ydoc, provider, room }) => {
const [status, setStatus] = useState('connecting')
const [currentUser, setCurrentUser] = useState(getInitialUser)
const editor = useEditor({
onCreate: ({ editor: currentEditor }) => {
provider.on('synced', () => {
if (currentEditor.isEmpty) {
currentEditor.commands.setContent(defaultContent)
}
})
},
extensions: [
StarterKit.configure({
history: false,
}),
Highlight,
TaskList,
TaskItem,
CharacterCount.configure({
limit: 10000,
}),
Collaboration.configure({
document: ydoc,
}),
CollaborationCursor.configure({
provider,
}),
],
})
useEffect(() => {
// Update status changes
const statusHandler = event => {
setStatus(event.status)
}
provider.on('status', statusHandler)
return () => {
provider.off('status', statusHandler)
}
}, [provider])
// Save current user to localStorage and emit to editor
useEffect(() => {
if (editor && currentUser) {
localStorage.setItem('currentUser', JSON.stringify(currentUser))
editor.chain().focus().updateUser(currentUser).run()
}
}, [editor, currentUser])
const setName = useCallback(() => {
const name = (window.prompt('Name', currentUser.name) || '').trim().substring(0, 32)
if (name) {
return setCurrentUser({ ...currentUser, name })
}
}, [currentUser])
if (!editor) {
return null
}
return (
<div className="column-half">
<div className="control-group">
<div className="button-group">
<button
onClick={() => editor.chain().focus().toggleBold().run()}
className={editor.isActive('bold') ? 'is-active' : ''}
>
Bold
</button>
<button
onClick={() => editor.chain().focus().toggleItalic().run()}
className={editor.isActive('italic') ? 'is-active' : ''}
>
Italic
</button>
<button
onClick={() => editor.chain().focus().toggleStrike().run()}
className={editor.isActive('strike') ? 'is-active' : ''}
>
Strike
</button>
<button
onClick={() => editor.chain().focus().toggleBulletList().run()}
className={editor.isActive('bulletList') ? 'is-active' : ''}
>
Bullet list
</button>
<button
onClick={() => editor.chain().focus().toggleCode().run()}
className={editor.isActive('code') ? 'is-active' : ''}
>
Code
</button>
</div>
</div>
<EditorContent editor={editor} className="main-group" />
<div className="collab-status-group" data-state={status === 'connected' ? 'online' : 'offline'}>
<label>
{status === 'connected'
? `${editor.storage.collaborationCursor.users.length} user${
editor.storage.collaborationCursor.users.length === 1 ? '' : 's'
} online in ${room}`
: 'offline'}
</label>
<button style={{ '--color': currentUser.color }} onClick={setName}> {currentUser.name}</button>
</div>
</div>
)
}
export default Editor

View File

@ -0,0 +1,39 @@
import './styles.scss'
import { TiptapCollabProvider } from '@hocuspocus/provider'
import * as Y from 'yjs'
import Editor from './Editor.jsx'
const appId = '7j9y6m10'
const room = `room.${new Date()
.getFullYear()
.toString()
.slice(-2)}${new Date().getMonth() + 1}${new Date().getDate()}`
// ydoc and provider for Editor A
const ydocA = new Y.Doc()
const providerA = new TiptapCollabProvider({
appId,
name: room,
document: ydocA,
})
// ydoc and provider for Editor B
const ydocB = new Y.Doc()
const providerB = new TiptapCollabProvider({
appId,
name: room,
document: ydocB,
})
const App = () => {
return (
<div className="col-group">
<Editor provider={providerA} ydoc={ydocA} room={room} />
<Editor provider={providerB} ydoc={ydocB} room={room} />
</div>
)
}
export default App

View File

@ -0,0 +1,7 @@
context('/src/Demos/CollaborationSplitPane/React/', () => {
beforeEach(() => {
cy.visit('/src/Demos/CollaborationSplitPane/React/')
})
// TODO: Write tests
})

View File

@ -0,0 +1,280 @@
/* Basic editor styles */
.tiptap {
:first-child {
margin-top: 0;
}
/* List styles */
ul,
ol {
padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
}
/* Heading styles */
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
}
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code {
background-color: var(--purple-light);
border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
}
pre {
background: var(--black);
border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code {
background: none;
color: inherit;
font-size: 0.8rem;
padding: 0;
}
}
blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem;
}
hr {
border: none;
border-top: 1px solid var(--gray-2);
margin: 2rem 0;
}
/* Highlight specific styles */
mark {
background-color: #FAF594;
border-radius: 0.4rem;
box-decoration-break: clone;
padding: 0.1rem 0.3rem;
}
/* Task list specific styles */
ul[data-type="taskList"] {
list-style: none;
margin-left: 0;
padding: 0;
li {
align-items: flex-start;
display: flex;
> label {
flex: 0 0 auto;
margin-right: 0.5rem;
user-select: none;
}
> div {
flex: 1 1 auto;
}
}
input[type="checkbox"] {
cursor: pointer;
}
ul[data-type="taskList"] {
margin: 0;
}
}
p {
word-break: break-all;
}
/* Give a remote user a caret */
.collaboration-cursor__caret {
border-left: 1px solid #0d0d0d;
border-right: 1px solid #0d0d0d;
margin-left: -1px;
margin-right: -1px;
pointer-events: none;
position: relative;
word-break: normal;
}
/* Render the username above the caret */
.collaboration-cursor__label {
border-radius: 3px 3px 3px 0;
color: #0d0d0d;
font-size: 12px;
font-style: normal;
font-weight: 600;
left: -1px;
line-height: normal;
padding: 0.1rem 0.3rem;
position: absolute;
top: -1.4em;
user-select: none;
white-space: nowrap;
}
}
.col-group {
display: flex;
flex-direction: row;
height: 100vh;
@media (max-width: 540px) {
flex-direction: column;
}
}
/* Column-half */
body {
overflow: hidden;
}
.column-half {
display: flex;
flex-direction: column;
flex: 1;
overflow: auto;
&:last-child {
border-left: 1px solid var(--gray-3);
@media (max-width: 540px) {
border-left: none;
border-top: 1px solid var(--gray-3);
}
}
& > .main-group {
flex-grow: 1;
}
}
/* Collaboration status */
.collab-status-group {
align-items: center;
background-color: var(--white);
border-top: 1px solid var(--gray-3);
bottom: 0;
color: var(--gray-5);
display: flex;
flex-direction: row;
font-size: 0.75rem;
font-weight: 400;
gap: 1rem;
justify-content: space-between;
padding: 0.375rem 0.5rem 0.375rem 1rem;
position: sticky;
width: 100%;
z-index: 100;
button {
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
align-self: stretch;
background: none;
display: -webkit-box;
flex-shrink: 1;
font-size: 0.75rem;
max-width: 100%;
padding: 0.25rem 0.375rem;
overflow: hidden;
position: relative;
text-overflow: ellipsis;
white-space: nowrap;
&::before {
background-color: var(--color);
border-radius: 0.375rem;
content: "";
height: 100%;
left: 0;
opacity: 0.5;
position: absolute;
top: 0;
transition: all 0.2s cubic-bezier(0.65,0.05,0.36,1);
width: 100%;
z-index: -1;
}
&:hover::before {
opacity: 1;
}
}
label {
align-items: center;
display: flex;
flex-direction: row;
flex-shrink: 0;
gap: 0.375rem;
line-height: 1.1;
&::before {
border-radius: 50%;
content: " ";
height: 0.35rem;
width: 0.35rem;
}
}
&[data-state="online"] {
label {
&::before {
background-color: var(--green);
}
}
}
&[data-state="offline"] {
label {
&::before {
background-color: var(--red);
}
}
}
}

View File

@ -1,6 +1,6 @@
context('/src/Examples/CollaborativeEditing/React/', () => {
context('/src/Demos/SingleRoomCollab/React/', () => {
beforeEach(() => {
cy.visit('/src/Examples/CollaborativeEditing/React/')
cy.visit('/src/Demos/SingleRoomCollab/React/')
})
/* it('should show the current room with participants', () => {

View File

@ -5,11 +5,11 @@
}
/* List styles */
ul,
ul,
ol {
padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
@ -17,39 +17,39 @@
}
/* Heading styles */
h1,
h2,
h3,
h4,
h5,
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
}
h1,
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
@ -142,7 +142,7 @@
overflow-x: auto;
}
.resize-cursor {
&.resize-cursor {
cursor: ew-resize;
cursor: col-resize;
}

View File

@ -336,7 +336,7 @@ export default {
overflow-x: auto;
}
.resize-cursor {
&.resize-cursor {
cursor: ew-resize;
cursor: col-resize;
}

View File

@ -572,7 +572,7 @@ export default {
overflow-x: auto;
}
.resize-cursor {
&.resize-cursor {
cursor: ew-resize;
cursor: col-resize;
}

View File

@ -402,7 +402,7 @@ export default {
overflow-x: auto;
}
.resize-cursor {
&.resize-cursor {
cursor: ew-resize;
cursor: col-resize;
}

View File

@ -16,7 +16,7 @@ const editor = new Editor({
],
editorProps: {
attributes: {
class: 'prose dark:prose-invert prose-sm sm:prose-base lg:prose-lg xl:prose-2xl m-5 focus:outline-none',
class: 'prose prose-sm sm:prose-base lg:prose-lg xl:prose-2xl m-5 focus:outline-none',
},
},
content: `

View File

@ -57,7 +57,7 @@
overflow-x: auto;
}
.resize-cursor {
&.resize-cursor {
cursor: ew-resize;
cursor: col-resize;
}

View File

@ -183,7 +183,7 @@ export default {
overflow-x: auto;
}
.resize-cursor {
&.resize-cursor {
cursor: ew-resize;
cursor: col-resize;
}

11993
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -51,7 +51,7 @@ declare module '@tiptap/core' {
/**
* This method will add options to this extension
* @see https://tiptap.dev/guide/custom-extensions#settings
* @see https://tiptap.dev/docs/editor/guide/custom-extensions#settings
* @example
* addOptions() {
* return {
@ -66,7 +66,7 @@ declare module '@tiptap/core' {
/**
* The default storage this extension can save data to.
* @see https://tiptap.dev/guide/custom-extensions#storage
* @see https://tiptap.dev/docs/editor/guide/custom-extensions#storage
* @example
* defaultStorage: {
* prefetchedUsers: [],
@ -81,7 +81,7 @@ declare module '@tiptap/core' {
/**
* This function adds globalAttributes to specific nodes.
* @see https://tiptap.dev/guide/custom-extensions#global-attributes
* @see https://tiptap.dev/docs/editor/guide/custom-extensions#global-attributes
* @example
* addGlobalAttributes() {
* return [
@ -115,7 +115,7 @@ declare module '@tiptap/core' {
/**
* This function adds commands to the editor
* @see https://tiptap.dev/guide/custom-extensions#keyboard-shortcuts
* @see https://tiptap.dev/docs/editor/guide/custom-extensions#commands
* @example
* addCommands() {
* return {
@ -133,7 +133,7 @@ declare module '@tiptap/core' {
/**
* This function registers keyboard shortcuts.
* @see https://tiptap.dev/guide/custom-extensions#keyboard-shortcuts
* @see https://tiptap.dev/docs/editor/guide/custom-extensions#keyboard-shortcuts
* @example
* addKeyboardShortcuts() {
* return {
@ -153,7 +153,7 @@ declare module '@tiptap/core' {
/**
* This function adds input rules to the editor.
* @see https://tiptap.dev/guide/custom-extensions#input-rules
* @see https://tiptap.dev/docs/editor/guide/custom-extensions#input-rules
* @example
* addInputRules() {
* return [
@ -174,7 +174,7 @@ declare module '@tiptap/core' {
/**
* This function adds paste rules to the editor.
* @see https://tiptap.dev/guide/custom-extensions#paste-rules
* @see https://tiptap.dev/docs/editor/guide/custom-extensions#paste-rules
* @example
* addPasteRules() {
* return [
@ -195,7 +195,7 @@ declare module '@tiptap/core' {
/**
* This function adds Prosemirror plugins to the editor
* @see https://tiptap.dev/guide/custom-extensions#prosemirror-plugins
* @see https://tiptap.dev/docs/editor/guide/custom-extensions#prosemirror-plugins
* @example
* addProseMirrorPlugins() {
* return [