a proof of concept realtime collaborative text editor using atproto as a sync server jake.tngl.io/y-pds/
0

Configure Feed

Select the types of activity you want to include in your feed.

ypds / style.css
9.9 kB 629 lines
1@import url("./reset.css"); 2 3@import url("https://esm.sh/prosemirror-view/style/prosemirror.css"); 4@import url("https://esm.sh/prosemirror-menu/style/menu.css"); 5@import url("https://esm.sh/prosemirror-example-setup/style/style.css"); 6@import url("https://esm.sh/prosemirror-gapcursor/style/gapcursor.css"); 7 8html { 9 --font: 10 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, 11 "Open Sans", "Helvetica Neue", sans-serif; 12 font-family: var(--font); 13} 14 15input, 16select, 17textarea { 18 flex: 1; 19 width: 100%; 20 padding: 0.4rem 0.5rem; 21 border: 1px solid #ccc; 22 border-radius: 4px; 23 font-size: 0.875rem; 24 background: #fff; 25 transition: border-color 0.15s; 26 27 &:focus { 28 outline-offset: 1px; 29 outline: 2px solid rgba(from blue r g b / 0.5); 30 } 31} 32 33actor-typeahead { 34 flex: 1; 35} 36 37button, 38.ProseMirror-menuitem .share-menu-button { 39 padding: 0.4rem 0.75rem; 40 border: 1px solid #ccc; 41 border-radius: 4px; 42 background: #fff; 43 font-size: 0.875rem; 44 font-family: var(--font); 45 cursor: pointer; 46 transition: background 0.15s; 47 48 &:hover { 49 background: #f5f5f5; 50 } 51 52 &:active { 53 background: #eee; 54 } 55} 56 57label { 58 display: block; 59 flex: 1; 60 width: 100%; 61 font-size: 0.75rem; 62} 63 64html, 65body { 66 height: 100%; 67} 68 69body { 70 display: flex; 71 flex-direction: column; 72} 73 74#app { 75 flex: 1; 76 display: flex; 77 flex-direction: column; 78} 79 80.login { 81 margin: auto; 82 text-align: center; 83 display: flex; 84 flex-direction: column; 85 gap: 16px; 86} 87 88.login-form { 89 display: flex; 90 flex-direction: column; 91 gap: 8px; 92 text-align: left; 93} 94 95.login-blurb { 96 font-size: 0.75rem; 97} 98 99.loading { 100 display: flex; 101 width: 100%; 102 height: 100%; 103 justify-content: center; 104 align-items: center; 105 opacity: 0; 106 animation: 1s fade-in 1s forwards; 107} 108 109@keyframes fade-in { 110 from { 111 opacity: 0; 112 } 113 114 to { 115 opacity: 1; 116 } 117} 118 119.editor-container { 120 flex: 1; 121 display: flex; 122 flex-direction: column; 123 overflow: hidden; 124 min-height: 0; 125} 126 127#editor { 128 flex: 1; 129 min-height: 0; 130 overflow-y: auto; 131 padding: 0; 132 font-family: ui-serif, Georgia, serif; 133 font-size: 1rem; 134 line-height: 1.6; 135} 136 137.ProseMirror { 138 min-height: 100%; 139 outline: none; 140 padding: 1rem; 141 margin-trim: block; 142 143 > :first-child, 144 > :first-child p { 145 margin-top: 0; 146 } 147 148 > :last-child, 149 > :last-child p { 150 margin-bottom: 0; 151 } 152} 153 154.ProseMirror-menubar-wrapper { 155 height: 100%; 156} 157 158.ProseMirror-menubar { 159 display: flex; 160 padding-right: 0; 161} 162 163.ProseMirror-menuitem { 164 align-self: center; 165} 166 167.ProseMirror-menuitem:has(.toolbar-right) { 168 margin-left: auto; 169 padding: 4px; 170} 171 172.toolbar-right { 173 display: flex; 174 align-items: center; 175 gap: 4px; 176} 177 178dialog { 179 overflow: visible; 180 place-self: center; 181 width: 100%; 182 max-width: 720px; 183 border: none; 184 border-radius: 10px; 185 box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12); 186 187 &::backdrop { 188 background: rgba(0, 0, 0, 0.25); 189 } 190 191 header { 192 display: flex; 193 justify-content: space-between; 194 align-items: center; 195 border-bottom: 1px solid #dddddd; 196 } 197 198 > * { 199 padding: 12px; 200 } 201} 202 203.icon-button { 204 all: unset; 205 cursor: pointer; 206 display: flex; 207 align-items: center; 208 justify-content: center; 209 padding: 0.25rem; 210 border-radius: 4px; 211 color: #888; 212 213 &:hover { 214 color: #333; 215 background: #f0f0f0; 216 } 217} 218 219#editors { 220 ul { 221 list-style: none; 222 } 223 224 &:empty { 225 display: none; 226 } 227 228 > li { 229 display: flex; 230 align-items: center; 231 gap: 0.5rem; 232 padding: 0.25rem 0; 233 234 .avatar { 235 width: 32px; 236 height: 32px; 237 border-radius: 50%; 238 } 239 240 .member-info { 241 flex: 1; 242 display: flex; 243 flex-direction: column; 244 min-width: 0; 245 overflow: hidden; 246 247 strong { 248 font-size: 0.875rem; 249 text-overflow: ellipsis; 250 overflow: hidden; 251 white-space: nowrap; 252 } 253 254 small { 255 font-size: 0.75rem; 256 color: #888; 257 text-overflow: ellipsis; 258 overflow: hidden; 259 white-space: nowrap; 260 } 261 } 262 } 263} 264 265.copy-link { 266 display: flex; 267 align-items: center; 268 background: #f5f5f5; 269 border-radius: 4px; 270 271 input { 272 all: unset; 273 flex: 1; 274 color: #666; 275 padding: 0.4rem 0.5rem; 276 min-width: 0; 277 overflow: hidden; 278 text-overflow: ellipsis; 279 white-space: nowrap; 280 pointer-events: none; 281 } 282 283 .icon-button { 284 flex-shrink: 0; 285 } 286} 287 288.share-footer { 289 display: flex; 290 flex-direction: column; 291 gap: 12px; 292} 293 294#add-member { 295 display: flex; 296 gap: 0.5rem; 297 align-items: flex-end; 298} 299 300.readonly-banner { 301 position: fixed; 302 bottom: 1rem; 303 left: 50%; 304 transform: translateX(-50%); 305 background: #f8f8f8; 306 border: 1px solid #ddd; 307 border-radius: 8px; 308 padding: 0.5rem 1rem; 309 font-size: 0.8rem; 310 color: #666; 311 box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); 312 z-index: 10; 313 white-space: nowrap; 314 animation: 0.3s fade-in; 315} 316 317.collab-cursor { 318 position: relative; 319 border-left: 2px solid var(--color); 320 margin-left: -1px; 321 pointer-events: none; 322} 323 324.collab-cursor::after { 325 content: attr(data-name); 326 position: absolute; 327 top: -1.4em; 328 left: -2px; 329 background: var(--color); 330 color: white; 331 font-size: 0.7rem; 332 font-family: sans-serif; 333 padding: 0 4px; 334 border-radius: 3px 3px 3px 0; 335 white-space: nowrap; 336 pointer-events: none; 337} 338 339/* ── Devtools panel ── */ 340 341.devtools-resize-handle { 342 height: 6px; 343 margin-top: -3px; 344 margin-bottom: -3px; 345 position: relative; 346 z-index: 10; 347 cursor: ns-resize; 348 flex-shrink: 0; 349} 350 351.devtools-panel { 352 display: flex; 353 flex-direction: column; 354 border-top: 1px solid #ddd; 355 background: #fff; 356 font-family: ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, Consolas, 357 "DejaVu Sans Mono", monospace; 358 font-size: 12px; 359} 360 361.devtools-header { 362 display: flex; 363 align-items: center; 364 gap: 4px; 365 background: #f5f5f5; 366 border-bottom: 1px solid #e0e0e0; 367 height: 32px; 368 padding: 0 6px; 369 flex-shrink: 0; 370} 371 372.devtools-tabs { 373 display: flex; 374 height: 100%; 375 margin-right: 4px; 376 377 button { 378 all: unset; 379 padding: 0 10px; 380 font-size: 11px; 381 font-family: var(--font); 382 cursor: pointer; 383 color: #666; 384 border-bottom: 2px solid transparent; 385 height: 100%; 386 387 &:hover { 388 color: #333; 389 background: rgba(0, 0, 0, 0.05); 390 } 391 392 &.active { 393 color: #1a73e8; 394 border-bottom-color: #1a73e8; 395 } 396 } 397} 398 399.devtools-pause { 400 display: flex; 401 align-items: center; 402 gap: 4px; 403 font-size: 11px; 404 font-family: var(--font); 405 padding: 3px 6px !important; 406} 407 408.devtools-pause, 409.devtools-close { 410 padding: 4px !important; 411 color: #888; 412} 413 414.devtools-body { 415 flex: 1; 416 display: flex; 417 overflow: hidden; 418 min-height: 0; 419} 420 421.devtools-tab-content { 422 flex: 1; 423 display: flex; 424 flex-direction: column; 425 overflow: hidden; 426 min-height: 0; 427} 428 429.devtools-subtabs { 430 display: flex; 431 flex-shrink: 0; 432 border-bottom: 1px solid #e8e8e8; 433 padding: 0 4px; 434 overflow-x: auto; 435 436 button { 437 all: unset; 438 padding: 3px 10px; 439 font-size: 11px; 440 font-family: var(--font); 441 cursor: pointer; 442 color: #888; 443 border-bottom: 2px solid transparent; 444 white-space: nowrap; 445 446 &:hover { color: #333; } 447 &.active { color: #1a73e8; border-bottom-color: #1a73e8; } 448 } 449} 450 451.devtools-body-inner { 452 flex: 1; 453 display: flex; 454 overflow: hidden; 455 min-width: 0; 456 min-height: 0; 457 458 &:not(.has-detail) { 459 overflow-y: auto; 460 } 461 462 &.has-detail > .devtools-timeline { 463 width: 340px; 464 flex-shrink: 0; 465 border-right: 1px solid #e8e8e8; 466 } 467} 468 469.devtools-empty { 470 color: #999; 471 text-align: center; 472 padding: 24px; 473 font-family: var(--font); 474} 475 476/* Timeline tab */ 477 478.devtools-timeline { 479 flex: 1; 480 overflow-y: auto; 481 padding: 6px; 482 display: flex; 483 flex-direction: column; 484 gap: 4px; 485} 486 487.devtools-entry { 488 border: 1px solid #eee; 489 border-radius: 5px; 490 padding: 7px 9px; 491 background: #fafafa; 492 cursor: pointer; 493 494 &:hover { 495 background: #f0f4ff; 496 border-color: #c8d8ff; 497 } 498 499 &.active { 500 background: #e8f0fe; 501 border-color: #4285f4; 502 } 503} 504 505.devtools-entry-meta { 506 display: flex; 507 align-items: baseline; 508 gap: 8px; 509 margin-bottom: 3px; 510} 511 512.devtools-entry-index { 513 font-size: 10px; 514 font-weight: 700; 515 color: #bbb; 516 min-width: 14px; 517} 518 519.devtools-entry-author { 520 font-weight: 600; 521 font-size: 11px; 522 font-family: var(--font); 523} 524 525.devtools-entry-time { 526 font-size: 10px; 527 color: #aaa; 528 margin-left: auto; 529} 530 531.devtools-entry-uri { 532 font-size: 10px; 533 color: #1a73e8; 534 margin-bottom: 5px; 535 overflow: hidden; 536 text-overflow: ellipsis; 537 white-space: nowrap; 538} 539 540.devtools-entry-json { 541 font-size: 10px; 542 color: #555; 543 margin: 0; 544 white-space: pre; 545 line-height: 1.5; 546 overflow: hidden; 547 max-height: 60px; 548} 549 550/* Detail panel */ 551 552.devtools-detail { 553 flex: 1; 554 overflow-y: auto; 555 padding: 10px; 556 min-width: 0; 557} 558 559.devtools-detail-meta { 560 font-size: 11px; 561 color: #888; 562 margin-bottom: 10px; 563 font-family: var(--font); 564} 565 566.devtools-detail-label { 567 font-size: 10px; 568 font-weight: 600; 569 color: #aaa; 570 text-transform: uppercase; 571 letter-spacing: 0.05em; 572 margin: 10px 0 4px; 573} 574 575.devtools-ops { 576 width: 100%; 577 border-collapse: collapse; 578 font-size: 11px; 579 580 th { 581 text-align: left; 582 color: #aaa; 583 font-weight: 600; 584 font-size: 10px; 585 text-transform: uppercase; 586 letter-spacing: 0.04em; 587 padding: 2px 8px 4px 0; 588 border-bottom: 1px solid #eee; 589 } 590 591 td { 592 padding: 3px 8px 3px 0; 593 vertical-align: top; 594 border-bottom: 1px solid #f5f5f5; 595 } 596} 597 598.devtools-op-num { 599 color: #888; 600 white-space: nowrap; 601} 602 603.devtools-op-desc { 604 color: #333; 605 word-break: break-word; 606} 607 608/* Doc Record tab */ 609 610.devtools-doc { 611 padding: 10px; 612 overflow-y: auto; 613 flex: 1; 614} 615 616.devtools-doc-uri { 617 font-size: 11px; 618 color: #1a73e8; 619 margin-bottom: 8px; 620 word-break: break-all; 621} 622 623.devtools-doc-json { 624 font-size: 12px; 625 color: #333; 626 margin: 0; 627 white-space: pre; 628 line-height: 1.5; 629}