| ... | @@ -1315,9 +1315,289 @@ | ... | @@ -1315,9 +1315,289 @@ |
| 1315 | return markdown(firstLine); | 1315 | return markdown(firstLine); |
| 1316 | } | 1316 | } |
| 1317 | | 1317 | |
| 1318 | function markdown(mdText) { | 1318 | function markdown(input) { |
| 1319 | // TODO implement more | 1319 | const raw_lines = input.split('\n'); // zig allows no '\r', so we don't need to split on CR |
| 1320 | return escapeHtml(mdText); | 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 | //  : <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; |
| 1321 | } | 1601 | } |
| 1322 | | 1602 | |
| 1323 | function activateSelectedResult() { | 1603 | function activateSelectedResult() { |