large scale refactoring
This commit is contained in:
17
examples_all/notes/index.html
Normal file
17
examples_all/notes/index.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
</head>
|
||||
<script src="./index.js" type="module"></script>
|
||||
<link rel="stylesheet" type="text/css" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="sidebar">
|
||||
<h3 id="createNoteButton">+ Create Note</h3>
|
||||
<div class="notelist"></div>
|
||||
</div>
|
||||
<div class="main">
|
||||
<h1 id="headline"></h1>
|
||||
<div id="editor" contenteditable="true"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
132
examples_all/notes/index.js
Normal file
132
examples_all/notes/index.js
Normal file
@@ -0,0 +1,132 @@
|
||||
/* eslint-env browser */
|
||||
|
||||
import { createYdbClient } from '../../YdbClient/index.js'
|
||||
import Y from '../../src/Y.dist.js'
|
||||
import * as ydb from '../../YdbClient/YdbClient.js'
|
||||
import DomBinding from '../../bindings/DomBinding/DomBinding.js'
|
||||
|
||||
const uuidv4 = () => 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
|
||||
const r = Math.random() * 16 | 0
|
||||
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16)
|
||||
})
|
||||
|
||||
createYdbClient('ws://localhost:8899/ws').then(ydbclient => {
|
||||
const y = ydbclient.getY('notelist')
|
||||
let ynotelist = y.define('notelist', Y.Array)
|
||||
window.ynotelist = ynotelist
|
||||
const domNoteList = document.querySelector('.notelist')
|
||||
|
||||
// utils
|
||||
const addEventListener = (element, eventname, f) => element.addEventListener(eventname, f)
|
||||
|
||||
// create note button
|
||||
const createNoteButton = event => {
|
||||
ynotelist.insert(0, [{
|
||||
guid: uuidv4(),
|
||||
title: 'Note #' + ynotelist.length
|
||||
}])
|
||||
}
|
||||
addEventListener(document.querySelector('#createNoteButton'), 'click', createNoteButton)
|
||||
window.createNote = createNoteButton
|
||||
window.createNotes = n => {
|
||||
y.transact(() => {
|
||||
for (let i = 0; i < n; i++) {
|
||||
createNoteButton()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// clear note list function
|
||||
window.clearNotes = () => ynotelist.delete(0, ynotelist.length)
|
||||
|
||||
// update editor and editor title
|
||||
let domBinding = null
|
||||
const updateEditor = () => {
|
||||
domNoteList.querySelectorAll('a').forEach(a => a.classList.remove('selected'))
|
||||
const domNote = document.querySelector('.notelist').querySelector(`[href="${location.hash}"]`)
|
||||
if (domNote !== null) {
|
||||
domNote.classList.add('selected')
|
||||
const note = ynotelist.toArray().find(note => note.guid === location.hash.slice(1))
|
||||
if (note !== undefined) {
|
||||
const ydoc = ydbclient.getY(note.guid)
|
||||
const ycontent = ydoc.define('content', Y.XmlFragment)
|
||||
if (domBinding !== null) {
|
||||
domBinding.destroy()
|
||||
}
|
||||
domBinding = new DomBinding(ycontent, document.querySelector('#editor'))
|
||||
document.querySelector('#headline').innerText = note.title
|
||||
document.querySelector('#editor').focus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// listen to url-hash changes
|
||||
addEventListener(window, 'hashchange', updateEditor)
|
||||
updateEditor()
|
||||
|
||||
const styleSyncedState = (div, noteSyncedState) => {
|
||||
let classes = []
|
||||
if (noteSyncedState.persisted) {
|
||||
classes.push('persisted')
|
||||
} else {
|
||||
if (noteSyncedState.upsynced) {
|
||||
classes.push('upsynced')
|
||||
} else {
|
||||
classes.push('noupsynced')
|
||||
}
|
||||
if (noteSyncedState.downsynced) {
|
||||
classes.push('downsynced')
|
||||
} else {
|
||||
classes.push('nodownsynced')
|
||||
}
|
||||
}
|
||||
div.setAttribute('class', classes.join(' '))
|
||||
}
|
||||
|
||||
ydbclient.on('syncstate', event => event.updated.forEach((state, room) => {
|
||||
const a = document.querySelector(`[href="#${room}"]`)
|
||||
if (a !== null) {
|
||||
styleSyncedState(a.firstChild, state)
|
||||
}
|
||||
}))
|
||||
|
||||
// render note list
|
||||
const renderNoteList = (elementList, insertRef = domNoteList.firstChild) => {
|
||||
const fragment = document.createDocumentFragment()
|
||||
const addNow = elementList.splice(0, 100)
|
||||
addNow.forEach(note => {
|
||||
const a = document.createElement('a')
|
||||
const div = document.createElement('div')
|
||||
a.insertBefore(div, null)
|
||||
a.setAttribute('href', '#' + note.guid)
|
||||
div.innerText = note.title
|
||||
styleSyncedState(div, ydbclient.getRoomState(note.guid))
|
||||
fragment.insertBefore(a, null)
|
||||
})
|
||||
if (domBinding == null) {
|
||||
updateEditor()
|
||||
}
|
||||
domNoteList.insertBefore(fragment, insertRef)
|
||||
if (elementList.length > 0) {
|
||||
setTimeout(() => renderNoteList(elementList, insertRef), 100)
|
||||
}
|
||||
}
|
||||
{
|
||||
const notelist = ynotelist.toArray()
|
||||
if (notelist.length > 0) {
|
||||
renderNoteList(notelist)
|
||||
ydb.subscribeRooms(ydbclient, notelist.map(note => note.guid))
|
||||
}
|
||||
}
|
||||
ynotelist.observe(event => {
|
||||
const addedNotes = []
|
||||
event.addedElements.forEach(itemJson => itemJson._content.forEach(json => addedNotes.push(json)))
|
||||
renderNoteList(addedNotes.slice().reverse()) // renderNoteList modifies addedNotes, so first make a copy of it
|
||||
setTimeout(() => {
|
||||
ydb.subscribeRooms(ydbclient, addedNotes.map(note => note.guid))
|
||||
}, 200)
|
||||
if (domBinding === null) {
|
||||
updateEditor()
|
||||
}
|
||||
})
|
||||
})
|
||||
100
examples_all/notes/style.css
Normal file
100
examples_all/notes/style.css
Normal file
@@ -0,0 +1,100 @@
|
||||
.sidebar {
|
||||
height: 100%; /* Full-height: remove this if you want "auto" height */
|
||||
width: 180px; /* Set the width of the sidebar */
|
||||
position: fixed; /* Fixed Sidebar (stay in place on scroll) */
|
||||
z-index: 1; /* Stay on top */
|
||||
top: 0; /* Stay at the top */
|
||||
left: 0;
|
||||
background-color: #111; /* Black */
|
||||
overflow-x: hidden; /* Disable horizontal scroll */
|
||||
padding-top: 20px;
|
||||
color: #50abff;
|
||||
}
|
||||
|
||||
#createNoteButton {
|
||||
padding-left: .5em;
|
||||
padding-top: .5em;
|
||||
padding-bottom: .7em;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.notelist > a {
|
||||
padding: 6px 8px 6px 16px;
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
color: #818181;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.notelist > a.selected {
|
||||
border-style: outset;
|
||||
}
|
||||
|
||||
.notelist > a > div {
|
||||
position: relative;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
/* When you mouse over the navigation links, change their color */
|
||||
.sidebar a:hover {
|
||||
color: #f1f1f1;
|
||||
}
|
||||
|
||||
/* Style page content */
|
||||
.main {
|
||||
margin-left: 180px; /* Same as the width of the sidebar */
|
||||
padding: 0px 10px;
|
||||
}
|
||||
|
||||
/* On smaller screens, where height is less than 450px, change the style of the sidebar (less padding and a smaller font size) */
|
||||
@media screen and (max-height: 450px) {
|
||||
.sidebar {padding-top: 15px;}
|
||||
.sidebar a {font-size: 18px;}
|
||||
}
|
||||
|
||||
#editor {
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
[contenteditable]:focus {
|
||||
outline: 0px solid transparent;
|
||||
}
|
||||
|
||||
.persisted::before {
|
||||
content: "✔";
|
||||
color: green;
|
||||
position: absolute;
|
||||
right: -14px;
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
.upsynced::before {
|
||||
content: "↑";
|
||||
color: green;
|
||||
position: absolute;
|
||||
right: -14px;
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
.noupsynced::before {
|
||||
content: "↑";
|
||||
color: red;
|
||||
position: absolute;
|
||||
right: -14px;
|
||||
top: 0px;
|
||||
}
|
||||
.downsynced::after {
|
||||
content: "↓";
|
||||
color: green;
|
||||
position: absolute;
|
||||
right: -22px;
|
||||
top: 0px;
|
||||
}
|
||||
.nodownsynced::after {
|
||||
content: "↓";
|
||||
color: red;
|
||||
position: absolute;
|
||||
right: -22px;
|
||||
top: 0px;
|
||||
}
|
||||
Reference in New Issue
Block a user