~tychi/code-mirror-shield

d14fedb957ab03ba0baf884cc8f67f00263fcbe0 — Tyler Childs 7 months ago ea288b3
feature: add recovery subroutine
5 files changed, 403 insertions(+), 26 deletions(-)

M public/editor.bundle.js
M public/editor.js
A public/hello-universe.html
A public/hello-world.html
M server.js
M public/editor.bundle.js => public/editor.bundle.js +24 -2
@@ 17876,6 17876,7 @@ function createEditor(selector, flags = {
        every: 5
    });
    onPublish($, flags);
    onRecover($, flags);
}
const config1 = {
    extensions: [


@@ 17891,8 17892,8 @@ function mount1($, flags) {
            if (editors[target.id]) return;
            target.innerHTML = `
				<nav class="action-bar">
					<button data-reset data-id="${target.id}">
						Reset
					<button data-recover data-id="${target.id}">
						Recover
					</button>
					<button data-publish data-id="${target.id}">
						Publish


@@ 17917,6 17918,9 @@ function mount1($, flags) {
                state,
                view
            };
            if (!copy.value) {
                recover(target.id, $);
            }
        });
    });
}


@@ 17932,6 17936,12 @@ function onPublish($, _flags) {
        publish(id, $);
    });
}
function onRecover($, _flags) {
    $.on('click', '[data-recover]', (event)=>{
        const { id  } = event.target.dataset;
        recover(id, $);
    });
}
async function upload(mode, pathname, $) {
    const currentState = $.read();
    const { value  } = currentState[pathname] || {


@@ 17967,6 17977,18 @@ function persist(target, $, _flags) {
        });
    };
}
async function recover(pathname, $) {
    const value = await fetch(`/public/${pathname}`).then((res)=>res.text()
    );
    const { view  } = editors[pathname];
    view.dispatch({
        changes: {
            from: 0,
            to: view.state.doc.toString().length,
            insert: value
        }
    });
}
function each($, save) {
    return [
        ...document.querySelectorAll($.selector)

M public/editor.js => public/editor.js +29 -2
@@ 26,6 26,7 @@ export default function createEditor(selector, flags = {}) {
  mount($, flags)
  onAutosave($, { every: 5 })
  onPublish($, flags)
  onRecover($, flags)
}

const config = {


@@ 45,8 46,8 @@ function mount($, flags) {

			target.innerHTML = `
				<nav class="action-bar">
					<button data-reset data-id="${target.id}">
						Reset
					<button data-recover data-id="${target.id}">
						Recover
					</button>
					<button data-publish data-id="${target.id}">
						Publish


@@ 74,6 75,10 @@ function mount($, flags) {
        state,
        view,
      }

			if(!copy.value) {
				recover(target.id, $)
			}
    })
  })
}


@@ 91,6 96,13 @@ function onPublish($, _flags) {
	})
}

function onRecover($, _flags) {
	$.on('click', '[data-recover]', (event) => {
		const { id } = event.target.dataset
		recover(id, $)
	})
}

async function upload(mode, pathname, $) {
	const currentState = $.read()
	const { value } = currentState[pathname] || {}


@@ 125,6 137,21 @@ function persist(target, $, _flags) {
	}
}

async function recover(pathname, $) {
	const value = await fetch(`/public/${pathname}`)
		.then(res => res.text())

	const { view } = editors[pathname]

	view.dispatch({
		changes: {
			from: 0,
			to: view.state.doc.toString().length,
			insert: value
		}
	})
}

function each($, save) {
  return [...document.querySelectorAll($.selector)].map(save)
}

A public/hello-universe.html => public/hello-universe.html +316 -0
@@ 0,0 1,316 @@
<!doctype html>
<html lang="">
<head>
  <meta charset="utf-8">
  <title>Hello Universe</title>
  <meta name="description" content="">
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <!-- define critial styles inline the head of the document -->
  <style type="text/css">
    html {
      height: 100%;
      font-family: sans-serif;
      text-align: center;
    }

    body {
      color: cornsilk;
      background: radial-gradient(
        indigo,
        darkslateblue,
        darkslategrey,
        black
      ) no-repeat;
      overflow: hidden;
      height: 100%;
    }
  </style>
</head>

<body>
  <h1 style="margin: 0;">Hello Universe</h1>
  <!-- optionally set a zero state in between the tags -->
  <hello-universe>
    Application is loading...
  </hello-universe>

  <script type="module">
    import tag from 'https://thelanding.page/tag/tag.bundle.js'

    const {
      html,
      get,
      on,
      set,
      css
    } = tag('hello-universe', {
      // set an initial array of planets
      'planets': [
        {
          'id': 'mercury',
          'name': 'Mercury',
          'background': 'sienna'
        },
        {
          'id': 'venus',
          'name': 'Venus',
          'background': 'darkgoldenrod'
        },
        {
          'id': 'Earth',
          'name': 'Earth',
          'background': 'deepskyblue'
        },
        {
          'id': 'mars',
          'name': 'Mars',
          'background': 'orangered'
        },
        {
          'id': 'jupiter',
          'name': 'Jupiter',
          'background': 'lightsalmon'
        },
        {
          'id': 'saturn',
          'name': 'Saturn',
          'background': 'bisque'
        },
        {
          'id': 'neptune',
          'name': 'Neptune',
          'background': 'dodgerblue'
        },
        {
          'id': 'uranus',
          'name': 'Uranus',
          'background': 'darkturquoise'
        }
      ],
      // an incrementable integer for looping through planets
      'tick': 0,
      // a boolean value to control visibility of the admin view
      'showAdmin': false
    })

    // increase our tick twice a second (once every 500 milliseconds)
    setInterval(function tick() {
      const { tick } = get()
      set({ tick: tick + 1 })
    }, 500)

    html(() => {
      // pluck our current values from our current state
      const { planets, tick, showAdmin } = get()

      // get the current planet by modulo of current tick by # of planets
      const current = planets[tick % planets.length]

      // save html for displaying the current planet
      const view = `
        <button class="admin-toggle">
          Manage Planets
        </button>
        <planet style="--planet-background: ${current.background}">
          <greeting>Hello ${current.name}</greeting>
        </planet>
      `

      // save html for displaying the admin view
      const admin = `
        <button class="admin-toggle">
          View Planets
        </button>
        <admin>
          ${renderAdministrationTable(planets)}
          <button class="add-planet">Add Planet</button>
        </admin>
      `

      // determine which html is visible
      return showAdmin ? admin : view
    })

    // toggle the showAdmin variable on button click
    on('click', 'button.admin-toggle', () => {
      const showAdmin = ! get().showAdmin
      set({ showAdmin })
    })

    // create a random planet with the current tick as a seed
    on('click', 'button.add-planet', () => {
      const { tick } = get()
      set({
        id: `planet-${tick}`,
        name: `Planet ${tick}`,
        background: 'cornsilk'
      }, addPlanet)
    })

    // surgically add a new planet to our tag's state
    function addPlanet(state, payload) {
      return {
        ...state,
        planets: [
          ...state.planets,
          payload
        ]
      }
    }

    // update planet information when input values have been changed
    on('keyup', 'admin input', (event) => {
      const { value, dataset } = event.target
      const { id, attribute } = dataset

      set({
        id,
        attribute,
        value
      }, updatePlanet)
    })

    // return the index of the planet with a given id
    function getPlanetIndexById(id) {
      const index = get().planets
        .findIndex(x => x.id === id)

      return index
    }

    // surgically update planet information in our tag's state
    function updatePlanet(state, payload){
      const { id, attribute, value } = payload

      const index = getPlanetIndexById(id)
      const planet = state.planets[index]

      return {
        ...state,
        planets: [
          ...state.planets.slice(0, index),
          { ...planet, [attribute]: value },
          ...state.planets.slice(index + 1)
        ]
      }
    }

    // loop over the planets array to create a complex form to manage values
    function renderAdministrationTable(planets) {
      const rows = planets.map(x => `
        <input type="text" data-id="${x.id}"
          value="${x.id}" data-attribute="id" />
        <input type="text" data-id="${x.id}"
          value="${x.name}" data-attribute="name" />
        <input type="text" data-id="${x.id}"
          value="${x.background}" data-attribute="background" />
      `).join('')

      return `
        <admin-table>
          <cell-head>ID</cell-head>
          <cell-head>Name</cell-head>
          <cell-head>Background</cell-head>
        </admin-table>
        <admin-table>
          ${rows}
        </admin-table>
      `
    }

    // create scoped rulesets for all of our content
    css(`
      & {
        display: block;
        margin: 0 auto;
      }

      & button {
        background: rgba(255, 255, 255, .75);
        border: 1px solid;
        border-color:
          rgba(255, 255, 255, .5)
          rgba(0, 0, 0, .75)
          rgba(0, 0, 0, .75)
          rgba(0, 0, 0, .75);
        border-radius: 2px;
        font-size: 1rem;
        font-weight: bold;
        padding: .5rem;
      }

      & button:hover {
        background: rgba(255, 255, 255, .5);
        border-color:
          rgba(0, 0, 0, .75)
          rgba(255, 255, 255, .5)
          rgba(255, 255, 255, .5)
          rgba(255, 255, 255, .5);
        cursor: pointer;
      }

      & planet {
        background: var(--planet-background, cornsilk);
        border-radius: 100%;
        display: grid;
        grid-template-areas: 'greeting';
        height: 75vmin;
        margin: 0 auto;
        mix-blend-mode: lighten;
        position: relative;
        width: 75vmin;
      }

      & planet greeting {
        background: cornsilk;
        border-radius: 100%;
        color: darkslategray;
        grid-area: greeting;
        place-self: center;
        font-size: 5vmin;
        padding: 3rem;
      }

      & admin {
        background: rgba(0, 0, 0, .85);
        border-radius: 1rem;
        display: block;
        margin: 0 auto;
        max-width: 500px;
        padding: 2rem 1rem;
      }

      & admin-table {
        display: grid;
        grid-template-columns: repeat(3, minmax(80px, 1fr));
        gap: 10px;
        max-height: 40vh;
        overflow-y: auto;
        margin-bottom: 10px;
      }

      & cell-head {
        font-weight: bold;
      }

      & input {
        background: rgba(33, 33, 33, .85);
        border: 1px solid rgba(255, 255, 255, .2);
        border-radius: 2px;
        color: cornsilk;
        height: 1.5rem;
      }

      & .admin-toggle {
        margin: 1rem;
      }

      & .add-planet {
        grid-column: 1 / -1;
      }
    `)
  </script>
</body>

</html>
\ No newline at end of file

A public/hello-world.html => public/hello-world.html +14 -0
@@ 0,0 1,14 @@
<!doctype html>
<html lang="">
  <head>
    <meta charset="utf-8">
    <title>Tag</title>
  </head>
  <body>
    <hello-world></hello-world>
    <script type="module">
      import tag from 'https://thelanding.page/tag/tag.js'
      tag('hello-world').html(() => 'Hello World!')
    </script>
  </body>
</html>
\ No newline at end of file

M server.js => server.js +20 -22
@@ 55,28 55,26 @@ async function handleGet(request) {
function editor(request) {
	const { pathname } = new URL(request.url);

	return new Response(`
			<!doctype html>
			<html lang="en">
				<head>
					<meta charset="utf-8">
					<title>
						${pathname}
					</title>
				</head>
				<body>
					<main
						class="source-code"
						id="${pathname}"
					></main>
					<script type="module">
						import createEditor from '/public/editor.bundle.js'

						createEditor('.source-code')
					</script>
				</body>
			</html>
		`,
	return new Response(`<!doctype html>
<html lang="en">
<head>
	<meta charset="utf-8">
	<title>
		${pathname}
	</title>
</head>
<body>
	<main
		class="source-code"
		id="${pathname}"
	></main>
	<script type="module">
		import createEditor from '/public/editor.bundle.js'

		createEditor('.source-code')
	</script>
</body>
</html>`,
		{
			headers: {
				"content-type": getType('html'),