| ... | ... | @@ -1315,9 +1315,289 @@ |
| 1315 | 1315 | return markdown(firstLine); |
| 1316 | 1316 | } |
| 1317 | 1317 | |
| 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 | //  : <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 | 1603 | function activateSelectedResult() { |