authorgravatar for xq@random-projects.netFelix "xq" Queißner <xq@random-projects.net> 2019-10-20 11:49:28+02:00
committergravatar for xq@random-projects.netFelix "xq" Queißner <xq@random-projects.net> 2019-10-20 11:49:28+02:00
log5456eb11078a630afc21d52ebb515ac753764a84
treeaf18bb4cf2a7e996f452599ce13446335d6be72c
parente42d86b657c2fae093fb2545e8b4b85614a0c906

Starts to implement markdown parser. Implemented: strong, emphasis, strikethrough, underline, code blocks, ulist, olist, paragraphs, headings. Planned: Links, Images, internal references.


1 files changed, 283 insertions(+), 3 deletions(-)

lib/std/special/docs/main.js+283-3
......@@ -1315,9 +1315,289 @@
13151315 return markdown(firstLine);
13161316 }
13171317
1318 function markdown(mdText) {
1319 // TODO implement more
1320 return escapeHtml(mdText);
1318 function markdown(input) {
1319 const raw_lines = input.split('\n'); // zig allows no '\r', so we don't need to split on CR
1320 const lines = [];
1321
1322 // PHASE 1:
1323 // Dissect lines and determine the type for each line.
1324 // Also computes indentation level and removes unnecessary whitespace
1325
1326 var is_reading_code = false;
1327 var code_indent = 0;
1328 for (var line_no = 0; line_no < raw_lines.length; line_no++) {
1329 const raw_line = raw_lines[line_no];
1330
1331 const line = {
1332 indent: 0,
1333 raw_text: raw_line,
1334 text: raw_line.trim(),
1335 type: "p", // p, h1 … h6, code, ul, ol, blockquote, skip, empty
1336 };
1337
1338 if (!is_reading_code) {
1339 while ((line.indent < line.raw_text.length) && line.raw_text[line.indent] == ' ') {
1340 line.indent += 1;
1341 }
1342
1343 if (line.text.startsWith("######")) {
1344 line.type = "h6";
1345 line.text = line.text.substr(6);
1346 }
1347 else if (line.text.startsWith("#####")) {
1348 line.type = "h5";
1349 line.text = line.text.substr(5);
1350 }
1351 else if (line.text.startsWith("####")) {
1352 line.type = "h4";
1353 line.text = line.text.substr(4);
1354 }
1355 else if (line.text.startsWith("###")) {
1356 line.type = "h3";
1357 line.text = line.text.substr(3);
1358 }
1359 else if (line.text.startsWith("##")) {
1360 line.type = "h2";
1361 line.text = line.text.substr(2);
1362 }
1363 else if (line.text.startsWith("#")) {
1364 line.type = "h1";
1365 line.text = line.text.substr(1);
1366 }
1367 else if (line.text.startsWith("-")) {
1368 line.type = "ul";
1369 line.text = line.text.substr(1);
1370 }
1371 else if (line.text.match(/\d+\./)) {
1372 const match = line.match(/(\d+)\./);
1373 line.type = "ul";
1374 line.text = line.text.substr(match[0].length);
1375 line.ordered_number = Number(match[1].length);
1376 }
1377 else if (line.text == "```") {
1378 line.type = "skip";
1379 is_reading_code = true;
1380 code_indent = line.indent;
1381 }
1382 else if (line.text == "") {
1383 line.type = "empty";
1384 }
1385 }
1386 else {
1387 if (line.text == "```") {
1388 is_reading_code = false;
1389 line.type = "skip";
1390 } else {
1391 line.type = "code";
1392 line.text = line.raw_text.substr(code_indent); // remove the indent of the ``` from all the code block
1393 }
1394 }
1395
1396 if (line.type != "skip") {
1397 lines.push(line);
1398 }
1399 }
1400
1401 // PHASE 2:
1402 // Render HTML from markdown lines.
1403 // Look at each line and emit fitting HTML code
1404
1405 function markdownInlines(innerText, stopChar) {
1406
1407 // inline types:
1408 // **{INLINE}** : <strong>
1409 // __{INLINE}__ : <u>
1410 // ~~{INLINE}~~ : <s>
1411 // *{INLINE}* : <emph>
1412 // _{INLINE}_ : <emph>
1413 // `{TEXT}` : <code>
1414 // [{INLINE}]({URL}) : <a>
1415 // ![{TEXT}]({URL}) : <img>
1416 // [[std;format.fmt]] : <a> (inner link)
1417
1418 const formats = [
1419 {
1420 marker: "**",
1421 tag: "strong",
1422 },
1423 {
1424 marker: "~~",
1425 tag: "s",
1426 },
1427 {
1428 marker: "__",
1429 tag: "u",
1430 },
1431 {
1432 marker: "*",
1433 tag: "em",
1434 }
1435 ];
1436
1437 const stack = [];
1438
1439 var innerHTML = "";
1440 var currentRun = "";
1441
1442 function flushRun() {
1443 if (currentRun != "") {
1444 innerHTML += escapeHtml(currentRun);
1445 }
1446 currentRun = "";
1447 }
1448
1449 var parsing_code = false;
1450 var codetag = "";
1451 var in_code = false;
1452
1453 for (var i = 0; i < innerText.length; i++) {
1454
1455 if (parsing_code && in_code) {
1456 if (innerText.substr(i, codetag.length) == codetag) {
1457 // remove leading and trailing whitespace if string both starts and ends with one.
1458 if (currentRun[0] == " " && currentRun[currentRun.length - 1] == " ") {
1459 currentRun = currentRun.substr(1, currentRun.length - 2);
1460 }
1461 flushRun();
1462 i += codetag.length - 1;
1463 in_code = false;
1464 parsing_code = false;
1465 innerHTML += "</code>";
1466 codetag = "";
1467 } else {
1468 currentRun += innerText[i];
1469 }
1470 continue;
1471 }
1472
1473 if (innerText[i] == "`") {
1474 flushRun();
1475 if (!parsing_code) {
1476 innerHTML += "<code>";
1477 }
1478 parsing_code = true;
1479 codetag += "`";
1480 continue;
1481 }
1482
1483 if (parsing_code) {
1484 currentRun += innerText[i];
1485 in_code = true;
1486 } else {
1487 var any = false;
1488 for (var idx = (stack.length > 0 ? -1 : 0); idx < formats.length; idx++) {
1489 const fmt = idx >= 0 ? formats[idx] : stack[stack.length - 1];
1490 if (innerText.substr(i, fmt.marker.length) == fmt.marker) {
1491 flushRun();
1492 if (stack[stack.length - 1] == fmt) {
1493 stack.pop();
1494 innerHTML += "</" + fmt.tag + ">";
1495 } else {
1496 stack.push(fmt);
1497 innerHTML += "<" + fmt.tag + ">";
1498 }
1499 i += fmt.marker.length - 1;
1500 any = true;
1501 break;
1502 }
1503 }
1504 if (!any) {
1505 currentRun += innerText[i];
1506 }
1507 }
1508 }
1509 flushRun();
1510
1511 while (stack.length > 0) {
1512 const fmt = stack.pop();
1513 innerHTML += "</" + fmt.tag + ">";
1514 }
1515
1516 return innerHTML;
1517 }
1518
1519 var html = "";
1520 for (var line_no = 0; line_no < lines.length; line_no++) {
1521 const line = lines[line_no];
1522
1523 function previousLineIs(type) {
1524 if (line_no > 0) {
1525 return (lines[line_no - 1].type == type);
1526 } else {
1527 return false;
1528 }
1529 }
1530
1531 function nextLineIs(type) {
1532 if (line_no < (lines.length - 1)) {
1533 return (lines[line_no + 1].type == type);
1534 } else {
1535 return false;
1536 }
1537 }
1538
1539 function getPreviousLineIndent() {
1540 if (line_no > 0) {
1541 return lines[line_no - 1].indent;
1542 } else {
1543 return 0;
1544 }
1545 }
1546
1547 function getNextLineIndent() {
1548 if (line_no < (lines.length - 1)) {
1549 return lines[line_no + 1].indent;
1550 } else {
1551 return 0;
1552 }
1553 }
1554
1555 switch (line.type) {
1556 case "h1":
1557 case "h2":
1558 case "h3":
1559 case "h4":
1560 case "h5":
1561 case "h6":
1562 html += "<" + line.type + ">" + markdownInlines(line.text) + "</" + line.type + ">\n";
1563 break;
1564
1565 case "ul":
1566 case "ol":
1567 if (!previousLineIs("ul") || getPreviousLineIndent() < line.indent) {
1568 html += "<" + line.type + ">\n";
1569 }
1570
1571 html += "<li>" + markdownInlines(line.text) + "</li>\n";
1572
1573 if (!nextLineIs("ul") || getNextLineIndent() < line.indent) {
1574 html += "</" + line.type + ">\n";
1575 }
1576 break;
1577
1578 case "p":
1579 if (!previousLineIs("p")) {
1580 html += "<p>\n";
1581 }
1582 html += markdownInlines(line.text) + "\n";
1583 if (!nextLineIs("p")) {
1584 html += "</p>\n";
1585 }
1586 break;
1587
1588 case "code":
1589 if (!previousLineIs("code")) {
1590 html += "<pre><code>";
1591 }
1592 html += escapeHtml(line.text) + "\n";
1593 if (!nextLineIs("code")) {
1594 html += "</code></pre>\n";
1595 }
1596 break;
1597 }
1598 }
1599
1600 return html;
13211601 }
13221602
13231603 function activateSelectedResult() {