PHP and Javascript implementations of a simple markdown parser
Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

playground.html 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8">
  5. <title>Markdown Test</title>
  6. <link rel="icon" href="data:;base64,iVBORw0KGgo=">
  7. <style type="text/css">
  8. /* Structural style */
  9. :root, body {
  10. width: 100%; height: 100%;
  11. padding: 0;
  12. margin: 0;
  13. }
  14. .pane-container {
  15. position: absolute;
  16. left: 0; top: 0;
  17. width: 100%; height: 100%;
  18. box-sizing: border-box;
  19. display: grid;
  20. grid-template-columns: auto 1fr;
  21. }
  22. .left-pane {
  23. grid-row: 1; grid-column: 1;
  24. resize: horizontal;
  25. overflow-x: auto;
  26. box-sizing: border-box;
  27. min-width: 40vw;
  28. max-width: 80vw;
  29. border-right: 4px solid var(--color-ui-divider);
  30. display: grid;
  31. grid-template-rows: 32px 1fr;
  32. grid-template-columns: 1fr;
  33. }
  34. .right-pane {
  35. grid-row: 1; grid-column: 2;
  36. overflow-x: auto;
  37. overflow-y: scroll;
  38. }
  39. .toolbar-pane {
  40. grid-row: 1; grid-column: 1;
  41. box-sizing: border-box;
  42. display: grid;
  43. grid-template-columns: 1fr 1fr;
  44. }
  45. .toolbar-left {
  46. grid-column: 1;
  47. }
  48. .toolbar-right {
  49. grid-column: 2;
  50. }
  51. .editor-pane {
  52. grid-row: 2; grid-column: 1;
  53. }
  54. #markdown-editor {
  55. box-sizing: border-box;
  56. position: relative;
  57. width: 100%;
  58. height: 100%;
  59. resize: none;
  60. }
  61. @media screen and (max-width: 800px) {
  62. .pane-container {
  63. grid-template-columns: 1fr;
  64. grid-template-rows: auto 1fr;
  65. column-gap: 0;
  66. }
  67. .left-pane {
  68. grid-row: 2; grid-column: 1;
  69. resize: none;
  70. overflow-x: hidden;
  71. min-width: none;
  72. max-width: none;
  73. width: auto !important; /* undo wide mode resizing */
  74. border-right: none;
  75. border-top: 4px solid var(--color-ui-divider);
  76. }
  77. .right-pane {
  78. grid-row: 1; grid-column: 1;
  79. resize: vertical;
  80. min-height: 40vw;
  81. max-height: 80vw;
  82. }
  83. }
  84. @media screen and (min-width: 801px) {
  85. .right-pane {
  86. height: auto !important; /* undo narrow mode resizing */
  87. }
  88. }
  89. /* UI aesthetic */
  90. :root {
  91. --color-editor-background: #00a;
  92. --color-editor-text: white;
  93. --color-toolbar-background: #ccc;
  94. --color-toolbar-text: #000;
  95. --color-ui-divider: #000;
  96. }
  97. @media (prefers-color-scheme: dark) {
  98. :root {
  99. --toolbar-background: #aaa;
  100. --color-ui-divider: #999;
  101. }
  102. }
  103. .toolbar-pane,
  104. dialog {
  105. font-family: sans-serif;
  106. }
  107. .toolbar-pane {
  108. background-color: var(--color-toolbar-background);
  109. color: var(--color-toolbar-text);
  110. border-bottom: 1px solid var(--color-ui-divider);
  111. padding: 4px 20px;
  112. }
  113. .toolbar-left {
  114. text-align: left;
  115. }
  116. .toolbar-right {
  117. text-align: right;
  118. }
  119. #markdown-editor {
  120. padding: 1em 2em;
  121. border: none;
  122. outline: none;
  123. background-color: var(--color-editor-background);
  124. color: var(--color-editor-text);
  125. }
  126. .preview-container {
  127. padding: 1em;
  128. margin: 0 auto;
  129. max-width: 600px;
  130. background-color: var(--color-background);
  131. color: var(--color-text);
  132. }
  133. dialog#readers-dialog {
  134. max-width: 400px;
  135. max-height: 400px;
  136. }
  137. .dialog-layout {
  138. position: relative;
  139. width: 100%; height: 100%;
  140. display: grid;
  141. grid-template-rows: 32px 368px; /* can't get overflow to work without explicit height :( */
  142. }
  143. .dialog-titlebar {
  144. grid-row: 1;
  145. display: grid;
  146. grid-template-columns: 1fr auto;
  147. }
  148. .dialog-title {
  149. grid-column: 1;
  150. font-weight: bold;
  151. }
  152. .dialog-close {
  153. grid-column: 2;
  154. text-align: right;
  155. }
  156. .dialog-content {
  157. grid-row: 2;
  158. overflow-y: scroll;
  159. }
  160. .reader-list {
  161. position: relative;
  162. width: 100%; height: 100%;
  163. }
  164. /* --- old ----------------------------------------- */
  165. .reader-header {
  166. margin-top: 0.25em;
  167. font-weight: bold;
  168. }
  169. .reader-check {
  170. display: inline-block;
  171. padding: 4px 8px;
  172. border: 1px dotted gray;
  173. border-radius: 8px;
  174. margin: 4px;
  175. }
  176. .calculated {
  177. background-color: var(--color-calculated-background);
  178. font-style: italic;
  179. }
  180. .spreadsheet-type-number,
  181. .spreadsheet-type-currency,
  182. .spreadsheet-type-percent {
  183. text-align: right;
  184. }
  185. </style>
  186. <style type="text/css">
  187. /* Document styling */
  188. /* From https://github.com/edwardtufte/tufte-css/blob/gh-pages/tufte.css */
  189. :root {
  190. --color-background: #fffff8;
  191. --color-text: #111;
  192. --color-text-secondary: #666;
  193. --color-table-header: #ddd;
  194. --color-table-border: black;
  195. --color-calculated-background: #eee;
  196. --color-highlight: #ff0;
  197. --color-highlight-text: #111;
  198. --color-rule: #111;
  199. }
  200. @media (prefers-color-scheme: dark) {
  201. :root {
  202. --color-background: #151515;
  203. --color-text: #ddd;
  204. --color-text-secondary: #999;
  205. --color-table-header: #333;
  206. --color-table-border: #ccc;
  207. --color-calculated-background: #444;
  208. --color-highlight: #88f;
  209. --color-highlight-text: #ddd;
  210. --color-rule: #ddd;
  211. }
  212. }
  213. html {
  214. font-size: 15px;
  215. }
  216. body {
  217. font-family: et-book, Palatino, "Palatino Linotype", "Palatino LT STD", "Book Antiqua", Georgia, serif;
  218. background-color: var(--color-background);
  219. color: var(--color-text);
  220. counter-reset: sidenote-counter;
  221. }
  222. h1 {
  223. font-weight: 400;
  224. margin-top: 4rem;
  225. margin-bottom: 1.5rem;
  226. font-size: 3.2rem;
  227. line-height: 1;
  228. }
  229. h2 {
  230. font-style: italic;
  231. font-weight: 400;
  232. margin-top: 2.1rem;
  233. margin-bottom: 1.4rem;
  234. font-size: 2.2rem;
  235. line-height: 1;
  236. }
  237. h3 {
  238. font-style: italic;
  239. font-weight: 400;
  240. font-size: 1.7rem;
  241. margin-top: 2rem;
  242. margin-bottom: 1.4rem;
  243. line-height: 1;
  244. }
  245. .subtext {
  246. font-size: 1rem;
  247. color: var(--color-text-secondary);
  248. margin-top: 0.25em;
  249. margin-bottom: 0.5em;
  250. }
  251. mark {
  252. background-color: var(--color-highlight);
  253. color: var(--color-highlight-text);
  254. }
  255. table {
  256. margin-top: 1em;
  257. margin-bottom: 1em;
  258. border-collapse: collapse;
  259. }
  260. td, th {
  261. padding: 0.4em 0.8em;
  262. font-size: 1rem;
  263. }
  264. th {
  265. background-color: var(--color-table-header);
  266. border-bottom: 2px solid var(--color-text);
  267. }
  268. tr:not(:last-child) td {
  269. border-bottom: 1px solid var(--color-text);
  270. }
  271. tr td:not(:last-child) {
  272. border-right: 1px solid var(--color-table-header);
  273. }
  274. hr {
  275. display: block;
  276. height: 1px;
  277. width: 55%;
  278. border: 0;
  279. border-top: 1px solid var(--color-rule);
  280. margin: 1em 0;
  281. padding: 0;
  282. }
  283. article {
  284. padding: 5rem 0rem;
  285. }
  286. section {
  287. padding-top: 1rem;
  288. padding-bottom: 1rem;
  289. }
  290. p,
  291. ol,
  292. ul {
  293. font-size: 1.4rem;
  294. line-height: 2rem;
  295. }
  296. p.subtitle {
  297. font-style: italic;
  298. margin-top: 1rem;
  299. margin-bottom: 1rem;
  300. font-size: 1.8rem;
  301. display: block;
  302. line-height: 1;
  303. }
  304. code, pre > code {
  305. font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
  306. font-size: 1.0rem;
  307. line-height: 1.42;
  308. -webkit-text-size-adjust: 100%; /* Prevent adjustments of font size after orientation changes in iOS. See https://github.com/edwardtufte/tufte-css/issues/81#issuecomment-261953409 */
  309. }
  310. pre > code {
  311. font-size: 0.9rem;
  312. width: 52.5%;
  313. margin-left: 2.5%;
  314. overflow-x: auto;
  315. display: block;
  316. }
  317. a:link,
  318. a:visited {
  319. color: inherit;
  320. text-underline-offset: 0.1em;
  321. text-decoration-thickness: 0.05em;
  322. }
  323. dt:not(:first-child),
  324. li:not(:first-child) {
  325. margin-top: 0.25rem;
  326. }
  327. dt, dd {
  328. font-size: 1.2rem;
  329. }
  330. dt {
  331. font-weight: bold;
  332. }
  333. .footnotes, .footnotes li {
  334. font-size: 80%;
  335. }
  336. .footnotes li:target, sup:target a {
  337. background-color: var(--color-highlight);
  338. color: var(--color-highlight-text);
  339. }
  340. </style>
  341. <script src="js/markdown.js"></script>
  342. <script src="js/spreadsheet.js"></script>
  343. <script>
  344. const sampleMarkdown = `
  345. ## Block Formats {#top}
  346. A regular paragraph.
  347. -# Sub text
  348. * Unordered
  349. * Lists
  350. * Sub list
  351. 1. Ordered
  352. 2. Lists
  353. 6. Sub list
  354. A blockquote:
  355. > "The only thing we have to fear is fear itself—nameless, unreasoning,
  356. > unjustified terror which paralyzes needed efforts to convert retreat into
  357. > advance." - Franklin D. Roosevelt's First Inaugural Address
  358. Some definitions:
  359. word
  360. : a unit of meaning in a sentence
  361. sentence
  362. : a collection of words
  363. Code:
  364. \`\`\`javascript
  365. function foo() {
  366. return 'Hello world';
  367. }
  368. \`\`\`
  369. function bar() {
  370. return 'Indented code';
  371. }
  372. ### Heading, hash style
  373. Heading, underline style
  374. ---
  375. ### Modified Heading {style=color:red;}
  376. | Unit Price | Qty | Discount | Subtotal |
  377. | ---: | ---: | ---: | ---: |
  378. | $1.23 | 2 | 10% | =A*B*(1-C) FILL |
  379. | $4.99 | 6 | | |
  380. | $0.99 | 1 | 25% | |
  381. | Total | | | =SUM(D:D) |
  382. ---
  383. ## Inline Formats
  384. Normal, **double asterisk,** __double underscore,__ *asterisks,* _underscores,_
  385. ~~double tildes,~~ ~single tildes,~ ^carets,^ ==double equals==, \`\`double backticks\`\`,
  386. \`single backticks\`.
  387. Lorem ipsum dolor sit amet,[^1] consectetur adipiscing elit.[^abc] Sed sodales
  388. in odio eget porta. Proin et erat sit amet erat semper mollis vitae ut
  389. turpis.[^foo] Ut ipsum dui, maximus sit amet suscipit at, dictum id nulla.[^bar]
  390. Aenean efficitur rhoncus nulla non fermentum.
  391. [^1]: Donec ut felis volutpat, gravida ipsum scelerisque, accumsan est.
  392. [^abc]: Cras dictum rutrum quam.
  393. [^foo]: Donec maximus bibendum lorem.
  394. [^bar]: Praesent consectetur tristique leo. Morbi nec nisi sit amet quam
  395. imperdiet vehicula eu feugiat tortor.
  396. The HTML on the WWW is often full of JS and CSS.
  397. *[HTML]: Hypertext Markup Language
  398. *[WWW]: World Wide Web
  399. *[JS]: JavaScript
  400. *[CSS]: Cascading Style Sheets
  401. Click [here](#top) to return to the top. Referenced link to [Google][google].
  402. ![alt text](https://picsum.photos/100/75) ![alt text](https://picsum.photos/101/75 "With a title")
  403. ![][testimage]
  404. [testimage]: https://picsum.photos/102/75
  405. [google]: https://google.com "I prefer Duck Duck Go"
  406. Some verbatim <span style="color:green;">HTML</span>. A script tag
  407. will be ignored. <scr` + `ipt></sc` + `ript>
  408. `.trim();
  409. </script>
  410. <script>
  411. const blockReaderClasses = {
  412. 'Heading (underline)': MDUnderlinedHeadingReader,
  413. 'Heading (hash)': MDHashHeadingReader,
  414. 'Subtext': MDSubtextReader,
  415. 'Block quote': MDBlockQuoteReader,
  416. 'Unordered list': MDUnorderedListReader,
  417. 'Ordered list': MDOrderedListReader,
  418. 'Code block (fenced)': MDFencedCodeBlockReader,
  419. 'Code block (indented)': MDIndentedCodeBlockReader,
  420. 'Horizontal rule': MDHorizontalRuleReader,
  421. 'Table': MDTableReader,
  422. 'Definition list': MDDefinitionListReader,
  423. 'Paragraph': MDParagraphReader,
  424. 'Spreadsheets': MDSpreadsheetReader,
  425. };
  426. const inlineReaderClasses = {
  427. 'Emphasis': MDEmphasisReader,
  428. 'Strong': MDStrongReader,
  429. 'Strikethrough': MDStrikethroughReader,
  430. 'Underline': MDUnderlineReader,
  431. 'Highlight': MDHighlightReader,
  432. 'Links': MDLinkReader,
  433. 'Referenced links': MDReferencedLinkReader,
  434. 'Images': MDImageReader,
  435. 'Referenced images': MDReferencedImageReader,
  436. 'Code span': MDCodeSpanReader,
  437. 'Subscript': MDSubscriptReader,
  438. 'Superscript': MDSuperscriptReader,
  439. 'Footnotes': MDFootnoteReader,
  440. 'Abbrevations': MDAbbreviationReader,
  441. 'HTML tags': MDHTMLTagReader,
  442. 'Modifiers': MDModifierReader,
  443. 'Line breaks': MDLineBreakReader,
  444. };
  445. var activeReaders = [];
  446. var parser = null;
  447. function markdownEditorNode() { return document.getElementById('markdown-editor'); }
  448. function previewNode() { return document.getElementById('preview-container'); }
  449. function codeJSNode() { return document.getElementById('code-js'); }
  450. function codePHPNode() { return document.getElementById('code-php'); }
  451. function readersButtonNode() { return document.getElementById('readers-button'); }
  452. function readersDialogNode() { return document.getElementById('readers-dialog'); }
  453. function readerListNode() { return document.getElementById('reader-list'); }
  454. function onDocumentLoad() {
  455. if (markdownEditorNode().value === '') {
  456. markdownEditorNode().value = sampleMarkdown;
  457. }
  458. activeReaders = [ ...Markdown.allReaders, new MDSpreadsheetReader() ];
  459. parser = new Markdown(activeReaders);
  460. markdownEditorNode().addEventListener('input', handleMarkdownChange);
  461. populateReaderCheckboxes();
  462. readersButtonNode().addEventListener('click', handleReadersButtonClicked);
  463. codeJSNode().addEventListener('change', handleMarkdownChange);
  464. codePHPNode().addEventListener('change', handleMarkdownChange);
  465. readersDialogNode().getElementsByClassName("dialog-title")[0].innerHTML = "Readers";
  466. readersDialogNode().getElementsByClassName("dialog-titlebar")[0].getElementsByTagName("button")[0].addEventListener('click', () => {
  467. readersDialogNode().close();
  468. });
  469. setTimeout(handleMarkdownChange, 0);
  470. }
  471. function updatePreviewLocally() {
  472. let markdown = markdownEditorNode().value;
  473. let html = parser.toHTML(markdown, 'foo-');
  474. previewNode().innerHTML = html;
  475. }
  476. var debounceTimer = null;
  477. var needsUpdate = false;
  478. function debouncedSendPreviewRequest() {
  479. if (debounceTimer) {
  480. needsUpdate = true;
  481. } else {
  482. debounceTimer = setInterval(() => {
  483. if (needsUpdate) {
  484. needsUpdate = false;
  485. sendPreviewRequest();
  486. } else {
  487. clearTimeout(debounceTimer);
  488. debounceTimer = null;
  489. }
  490. }, 500);
  491. sendPreviewRequest();
  492. }
  493. }
  494. function sendPreviewRequest() {
  495. const markdown = markdownEditorNode().value;
  496. const readers = activeReaders.map((r) => r.constructor.name);
  497. const request = new XMLHttpRequest();
  498. var formData = encodeForm({
  499. 'markdown': markdown,
  500. 'readers': readers,
  501. });
  502. request.open('POST', 'playgroundapi.php', true);
  503. request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
  504. request.onreadystatechange = () => {
  505. if (request.readyState == 4 && request.status == 200) {
  506. previewNode().innerHTML = request.responseText;
  507. }
  508. };
  509. request.send(formData);
  510. }
  511. function encodeForm(values) {
  512. var pairs = [];
  513. for (const [key, value] of Object.entries(values)) {
  514. if (value instanceof Array) {
  515. for (const v of value) {
  516. pairs.push(`${encodeURIComponent(key + '[]')}=${encodeURIComponent(v)}`);
  517. }
  518. } else {
  519. pairs.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
  520. }
  521. }
  522. return pairs.join('&');
  523. }
  524. function populateReaderCheckboxes() {
  525. const container = document.getElementById('reader-list');
  526. var header = document.createElement('div');
  527. header.classList.add('reader-header');
  528. header.append(document.createTextNode('Block Readers'));
  529. container.append(header);
  530. const blockContainer = document.createElement('div');
  531. container.append(blockContainer);
  532. for (const name of Object.keys(blockReaderClasses).sort()) {
  533. const readerClass = blockReaderClasses[name];
  534. const checkbox = makeReaderCheckbox(name, readerClass);
  535. blockContainer.append(checkbox);
  536. }
  537. header = document.createElement('div');
  538. header.classList.add('reader-header');
  539. header.append(document.createTextNode('Inline Readers'));
  540. container.append(header);
  541. const inlineContainer = document.createElement('div');
  542. container.append(inlineContainer);
  543. for (const name of Object.keys(inlineReaderClasses).sort()) {
  544. const readerClass = inlineReaderClasses[name];
  545. const checkbox = makeReaderCheckbox(name, readerClass);
  546. inlineContainer.append(checkbox);
  547. }
  548. }
  549. function makeReaderCheckbox(name, readerClass) {
  550. var isSelected = false;
  551. for (const elem of activeReaders) {
  552. if (elem.constructor === readerClass) {
  553. isSelected = true;
  554. break;
  555. }
  556. }
  557. const label = document.createElement('label');
  558. label.classList.add('reader-check');
  559. const check = document.createElement('input');
  560. check.type = 'checkbox';
  561. check.checked = isSelected;
  562. check.addEventListener('change', () => {
  563. handleCheckChanged(readerClass, check);
  564. });
  565. check.id = `reader-${readerClass.name}`;
  566. label.append(check);
  567. label.append(document.createTextNode(name));
  568. return label;
  569. }
  570. function handleMarkdownChange() {
  571. if (codePHPNode().checked) {
  572. debouncedSendPreviewRequest();
  573. } else if (codeJSNode().checked) {
  574. updatePreviewLocally();
  575. }
  576. }
  577. function handleReadersButtonClicked() {
  578. const dlg = readersDialogNode();
  579. if (dlg.open) {
  580. dlg.close();
  581. } else {
  582. dlg.showModal();
  583. }
  584. }
  585. function handleCheckChanged(readerClass, check) {
  586. if (check.checked) {
  587. activeReaders.push(new readerClass());
  588. } else {
  589. activeReaders = activeReaders.filter((reader) => reader.constructor.name !== readerClass.name);
  590. }
  591. parser = new Markdown(activeReaders);
  592. handleMarkdownChange();
  593. }
  594. document.addEventListener('DOMContentLoaded', onDocumentLoad);
  595. </script>
  596. </head>
  597. <body>
  598. <div class="pane-container">
  599. <div class="left-pane pane">
  600. <div class="toolbar-pane">
  601. <div class="toolbar-left">
  602. <button id="readers-button">Readers</button>
  603. </div>
  604. <div class="toolbar-right">
  605. <label>
  606. <input type="radio" id="code-js" name="code" value="js" checked>
  607. Javascript
  608. </label>
  609. <label>
  610. <input type="radio" id="code-php" name="code" value="php">
  611. PHP
  612. </label>
  613. </div>
  614. </div>
  615. <div class="editor-pane">
  616. <textarea id="markdown-editor"></textarea>
  617. </div>
  618. </div>
  619. <div class="right-pane pane">
  620. <div class="preview-container" id="preview-container">
  621. <h1>Preview Here</h1>
  622. <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed turpis. Integer tincidunt eu nibh.</p>
  623. <p>Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies velit. Nam eget dui et libero pharetra dapibus.</p>
  624. <p>Vestibulum in vulputate velit. Cras egestas, urna quis dignissim convallis, arcu felis sagittis diam, non fermentum massa justo ac neque. In ornare do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
  625. <p>Duis phasellus maecenas vestibulum mollis facilisis. Nulla sit amet erat. Suspendisse in justo eu odio suscipit rhoncus. Praesent id massa for ante varius ultrices. Cras sed leo at felis sagittis cursus. Integer turpis est, vulputate ut, elementum ac, condimentum eget, diam.</p>
  626. <p>Aliquam nec enim. Nulla tempus sem et risus. Proin mi. Etiam sit amet orci eget eros faucibus tincidunt. Duis volutpat nunc vel velit. Suspendisse potenti. Integer sollicitudin dictum est. Donec ut dolor. Nam malesuada magna.</p>
  627. <p>Sed non lectus. Vivamus consectetuer purus vitae massa. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies velit. Nam eget dui et libero pharetra dapibus</p>
  628. </div>
  629. </div>
  630. </div>
  631. <dialog id="readers-dialog">
  632. <div class="dialog-layout">
  633. <div class="dialog-titlebar">
  634. <div class="dialog-title"></div>
  635. <div class="dialog-close"><button>✕</button></div>
  636. </div>
  637. <div class="dialog-content">
  638. <div class="reader-list" id="reader-list"></div>
  639. </div>
  640. </div>
  641. </dialog>
  642. </body>
  643. </html>