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
5.1 kB 322 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 { 120 flex: 1; 121 overflow-y: auto; 122 padding: 0; 123 font-family: ui-serif, Georgia, serif; 124 font-size: 1rem; 125 line-height: 1.6; 126} 127 128.ProseMirror { 129 min-height: 100%; 130 outline: none; 131 padding: 1rem; 132 margin-trim: block; 133 134 > :first-child, 135 > :first-child p { 136 margin-top: 0; 137 } 138 139 > :last-child, 140 > :last-child p { 141 margin-bottom: 0; 142 } 143} 144 145.ProseMirror-menubar-wrapper { 146 height: 100%; 147} 148 149.ProseMirror-menubar { 150 display: flex; 151 padding-right: 0; 152} 153 154.ProseMirror-menuitem { 155 align-self: center; 156} 157 158.ProseMirror-menuitem:has(.share-menu-button) { 159 margin-left: auto; 160 padding: 4px; 161} 162 163dialog { 164 overflow: visible; 165 place-self: center; 166 width: 100%; 167 max-width: 720px; 168 border: none; 169 border-radius: 10px; 170 box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12); 171 172 &::backdrop { 173 background: rgba(0, 0, 0, 0.25); 174 } 175 176 header { 177 display: flex; 178 justify-content: space-between; 179 align-items: center; 180 border-bottom: 1px solid #dddddd; 181 } 182 183 > * { 184 padding: 12px; 185 } 186} 187 188.icon-button { 189 all: unset; 190 cursor: pointer; 191 display: flex; 192 align-items: center; 193 justify-content: center; 194 padding: 0.25rem; 195 border-radius: 4px; 196 color: #888; 197 198 &:hover { 199 color: #333; 200 background: #f0f0f0; 201 } 202} 203 204#editors { 205 ul { 206 list-style: none; 207 } 208 209 &:empty { 210 display: none; 211 } 212 213 > li { 214 display: flex; 215 align-items: center; 216 gap: 0.5rem; 217 padding: 0.25rem 0; 218 219 .avatar { 220 width: 32px; 221 height: 32px; 222 border-radius: 50%; 223 } 224 225 .member-info { 226 flex: 1; 227 display: flex; 228 flex-direction: column; 229 min-width: 0; 230 overflow: hidden; 231 232 strong { 233 font-size: 0.875rem; 234 text-overflow: ellipsis; 235 overflow: hidden; 236 white-space: nowrap; 237 } 238 239 small { 240 font-size: 0.75rem; 241 color: #888; 242 text-overflow: ellipsis; 243 overflow: hidden; 244 white-space: nowrap; 245 } 246 } 247 } 248} 249 250.copy-link { 251 display: flex; 252 align-items: center; 253 background: #f5f5f5; 254 border-radius: 4px; 255 256 input { 257 all: unset; 258 flex: 1; 259 color: #666; 260 padding: 0.4rem 0.5rem; 261 min-width: 0; 262 overflow: hidden; 263 text-overflow: ellipsis; 264 white-space: nowrap; 265 pointer-events: none; 266 } 267 268 .icon-button { 269 flex-shrink: 0; 270 } 271} 272 273.share-footer { 274 display: flex; 275 flex-direction: column; 276 gap: 12px; 277} 278 279#add-member { 280 display: flex; 281 gap: 0.5rem; 282 align-items: flex-end; 283} 284 285.readonly-banner { 286 position: fixed; 287 bottom: 1rem; 288 left: 50%; 289 transform: translateX(-50%); 290 background: #f8f8f8; 291 border: 1px solid #ddd; 292 border-radius: 8px; 293 padding: 0.5rem 1rem; 294 font-size: 0.8rem; 295 color: #666; 296 box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); 297 z-index: 10; 298 white-space: nowrap; 299 animation: 0.3s fade-in; 300} 301 302.collab-cursor { 303 position: relative; 304 border-left: 2px solid var(--color); 305 margin-left: -1px; 306 pointer-events: none; 307} 308 309.collab-cursor::after { 310 content: attr(data-name); 311 position: absolute; 312 top: -1.4em; 313 left: -2px; 314 background: var(--color); 315 color: white; 316 font-size: 0.7rem; 317 font-family: sans-serif; 318 padding: 0 4px; 319 border-radius: 3px 3px 3px 0; 320 white-space: nowrap; 321 pointer-events: none; 322}