)
}
}
)
(
}
{
)
)
(
)
(
(
{
}
)
(
)
}
)
)
{
(
(
)
)
}
)
(
}

Snippet Zone Code Highlighter

I wrote a naive syntax highlighter a few weeks ago. Here it is rendering the code for itself. :

This is not a perfect highlighter, but it was fun to create and I will definitely use it in the Snippet Zone Github repository once I get that set up.

There is some css that goes with the highlighter, it’s pretty boring, but here it is anyway:

  1. .hh {
  2.   white-space: pre-wrap;
  3.   font-family: monaco, monospace;
  4.   line-height: 1.5;
  5.   font-size: .8em;
  6.   background: rgb(3, 21, 36);
  7.   color: rgba(156, 221, 254, 1);
  8.   padding: 1em;
  9. }
  10.  
  11. .hh b {
  12.   font-weight: normal;
  13. }
  14.  
  15. .hh i {
  16.   color: #1ad6ae;
  17. }
  18.  
  19. .hh u {
  20.   color: rgb(255, 195, 252);
  21.   text-decoration: none;
  22. }
  23.  
  24. .num {
  25.   color: #b5cea8;
  26. }
  27.  
  28. .str, .str b  {
  29.   color: #ce9178;
  30. }
  31.  
  32. .par {
  33.   color: white;
  34.   font-weight: bold;
  35. }
  36.  
  37. .brk {
  38.   color: white;
  39.   font-weight: bold;
  40. }
  41.  
  42. .o {
  43.   color: rgb(0, 151, 221);
  44. }
  45.  
  46. .obj {
  47.   color: rgb(52, 206, 47);
  48.   font-weight: bold !important;
  49. }
  50.  
  51. .w {
  52.   color: #1ad6ae;
  53.   font-style: italic;
  54. }
  55.  
  56. .k {
  57.   color: aqua;
  58. }
  59.  
  60. .cmt {
  61.   color: gray !important;
  62. }
  63. .cmt b {
  64.   color: gray !important;
  65. }

I have no idea why I used such bad class names there 😛

// css // dom // javascript // meta // strings // ui

Mini-Markdown Alternate Version

  1. const tagRules = {
  2.   b: '\\*\\*',
  3.   i: '_',
  4.   strike: '~~',
  5.   code: '`'
  6. };
  7. tagRules.keys = Object.keys(tagRules);
  8.  
  9. const toTag = (str, delim, tag) => str.replace(
  10.   new RegExp(
  11.     `${delim}(.+)${delim}`, 'gm'),`<${tag}>$1</${tag}>`)
  12.  
  13. const makeTags = str => 
  14.   tagRules.keys.reduce((accum, val, i) => 
  15.     toTag(accum, tagRules[val], val), str)
  16.  
  17. const markConfig = { 
  18.   emptyLine: {
  19.     check: val => val.trim() === '',
  20.     process: () => '<br>'
  21.   },
  22.   header: {
  23.     calc: val => val.split`#`.length - 1,
  24.     check: (val, result) => result > 0,
  25.     process: (val, result) => `<h${result} style="margin-bottom:0">
  26.       ${val.replace(/#/g, '')}</h${result}>`
  27.   },
  28.   hr: {
  29.     check: val => val.trim() === '***',
  30.     process: () => '<hr>'
  31.   },
  32.   li: {
  33.     check: val => val.startsWith('- '), 
  34.     process: (val, result, lastMethod) => {
  35.       const open = lastMethod != 'li' ? '<ul>' : '';
  36.       return `${open}<li>${val}</li>`;
  37.     }
  38.   }
  39. };
  40.  
  41. markConfig.keys = Object.keys(markConfig);
  42. markConfig.tail = {
  43.   process: (val, result, lastMethod) => {
  44.     const close = lastMethod == 'li' ? '</ul>' : '';
  45.     return val + close;
  46.   }
  47. };
  48.  
  49. const mark = str => {
  50.   const lines = str.split`\n`;
  51.   let lastMethod;
  52.   const newStr = lines.map((line, i) => {
  53.     const method = markConfig.keys.find(key => {
  54.       const { calc = () => {}, check } = markConfig[key];
  55.       return check(line, calc(line))
  56.     }) || 'tail'
  57.  
  58.     const { calc = () => {} } = markConfig[method];
  59.     const newLine = markConfig[method].process(line, calc(line), lastMethod);
  60.     lastMethod = method;
  61.     return newLine;
  62.   })
  63.   return newStr.join`\n`;
  64. }
  65.  
  66. // try it out
  67.  
  68. const md = document.body.appendChild(document.createElement('div'));
  69. md.style.fontFamily = 'sans-serif';
  70. md.innerHTML = `# Mini Markdown Subset
  71.  
  72. This is a subset of markdown stuff
  73.  
  74. ## It includes
  75.  
  76. - headers
  77. - **bold styles**
  78. - _italic styles, <br> multiline fine..._
  79. - \`code style\`
  80.  
  81. Other than ~~strikethrough~~ that is pretty much it... oh and **hr** tags
  82. ***
  83. ***
  84. _here is some italic text with some bold text **nested** <br>within it etc..._
  85. `;
  86. md.innerHTML = makeTags(mark(md.innerHTML));

This is another version of a post from awhile back. I was curious about readability. This has no for loops and no if statements. Personally I find it less readable than the other version…

Here is the simpler original version for comparison… this one also has nested list support.

  1.   const isLi = val => val.match(/(^\s+)?- /)
  2.  
  3.   function mark(str) {
  4.     const lines = str.split`\n`;
  5.     let wasLi = false
  6.     let lastDepth = 0;
  7.     let depth;
  8.  
  9.   for (let i = 0; i < lines.length; i++) {
  10.     let curr = lines[i];
  11.     const line = curr.trim();
  12.     const hdr = line.split`#`.length - 1;
  13.  
  14.     if (line === '') {
  15.       lines[i] = '<br>';
  16.     } else if (hdr > 0) {
  17.       lines[i] = `<h${hdr} style="margin-bottom:0">
  18.         ${curr.replace(/#/g, '')}</h${hdr}>`;
  19.     } else if (line === '***') {
  20.       lines[i] = '<hr>';
  21.     } else if (isLi(curr)) {
  22.       depth = curr.split('-')[0].length + 1;
  23.  
  24.       lines[i] = '';
  25.       if (depth < lastDepth) {
  26.         const diff = (lastDepth - depth) / 2
  27.  
  28.         lines[i] += '</ul>'.repeat(diff);
  29.         lastDepth = depth 
  30.       }
  31.  
  32.       lines[i] += `${depth > lastDepth ? '<ul>' : ''}
  33.         <li>${curr.replace(/-\s+/, '')}</li>`;
  34.  
  35.       lastDepth = depth;
  36.  
  37.       wasLi = true;
  38.     } else if (wasLi) {
  39.       lines[i - 1] = '</ul>\n'.repeat(lastDepth)
  40.       wasLi = false
  41.     }
  42.   }
  43.  
  44.   return lines.join`\n`
  45.     .replace(/\*\*(.+)\*\*/gm, '<b>$1</b>')
  46.     .replace(/_(.+)_/gm, '<i>$1</i>')
  47.     .replace(/~~(.+)~~/gm, '<strike>$1</strike>')
  48.     .replace(/`(.+)`/gm, '<code>$1</code>');
  49. }
  50.  
  51. // try it out
  52.  
  53. const md = document.body.appendChild(document.createElement('div'));
  54. md.style.fontFamily = 'sans-serif';
  55. md.innerHTML = `
  56. #Snippet Zone
  57.  
  58. This snippet renders a subset of markdown. 
  59.  
  60. - **bold** and _italic text_
  61. - lists 
  62.   - this particulate
  63.     - snippet can render 
  64.     - nested
  65.   - lists
  66.  
  67. - _**~~strikethrough text~~**_
  68.  
  69. In a real project you will probably want to use
  70. something more complete like the great <a href="https://github.com/markedjs/marked" target="blank" rel="noopener">marked.js</a> library.
  71. `;
  72. md.innerHTML = mark(md.innerHTML);

ASCII Circles (Bresenham Fun)

  1. // some circles made of text
  2.  
  3. const cols = 50;
  4. const rows = 50;
  5. const sym = '.';
  6. const body = document.body;
  7.  
  8. Object.assign(body.style, {
  9.   userSelect: 'none',
  10.   fontFamily: 'Courier, monospace',
  11.   position: 'fixed', 
  12.   width: '100%',
  13.   height: '100%',
  14.   margin: 0
  15. });
  16.  
  17. const el = body.appendChild(
  18.   document.createElement('span')
  19. );
  20. el.innerHTML = sym;
  21.  
  22. Object.assign(el.style, {
  23.   position: 'absolute',
  24.   left: '50%',
  25.   top: '50%',
  26.   wordWrap: 'break-word',
  27.   cursor: 'pointer'
  28. });
  29.  
  30. const charSize = el.getBoundingClientRect().width;
  31. const elWidth = charSize * cols;
  32. el.style.display = 'block'
  33. el.style.width = `${elWidth}px`;
  34.  
  35. const info = body.appendChild(
  36.   document.createElement('div')
  37. );
  38.  
  39. Object.assign(info.style, {
  40.   background: '#fff',
  41.   padding: '4px',
  42.   position: 'absolute'
  43. })
  44. info.innerHTML = 'click/tap and hold for different fx';
  45.  
  46. function resize() {
  47.   const scl = Math.min(
  48.     1.23, 
  49.     Math.min(innerWidth, innerHeight) / elWidth * .93
  50.   );
  51.   el.style.transform = `translate(-50%, -50%) scale(${scl}, ${scl * .55})`
  52. }
  53. addEventListener('resize', resize);
  54. resize();
  55.  
  56. const size = cols * rows;
  57. const pix = sym.repeat(size);
  58. el.innerHTML = pix;
  59.  
  60. let cells = pix.split('')
  61. const blank = cells.concat();
  62.  
  63. function setSym(x, y, col) {
  64.   const idx = x + y * cols;
  65.   if (cells[idx] != null) {
  66.     cells[idx] = col;
  67.   }
  68.   return setSym
  69. }
  70.  
  71. const grad = '::;|0UU888NN';
  72. function circ(shooter) {
  73.   let x = Math.round(Math.random() * cols);
  74.   let y = Math.round(Math.random() * rows);
  75.   const rad = Math.round(
  76.     shooter ? Math.random() * 2 :
  77.       Math.random() * Math.random() * 13 + 1
  78.   );
  79.  
  80.   const sym = shooter ? '#' : grad.charAt(rad % grad.length);
  81.   let speed = rad / 10 + .1;
  82.   let dir = Math.random() * 2 - 1;
  83.   return () => {
  84.     drawCircle(x, y, rad, sym, shooter);
  85.     y += speed;
  86.     if (shooter) x += speed * 3 * dir;
  87.     if (y > rows + 10) y = -14;
  88.   }
  89. }
  90.  
  91. const circs = [];
  92. const NUM = 40;
  93. for (let i = 0; i < NUM; i++) {
  94.   circs.push(circ(Math.random() > 0.5))
  95. }
  96.  
  97. let down;
  98. document.addEventListener('mousedown', () => {
  99.   down = true;
  100. });
  101. document.addEventListener('mouseup', () => {
  102.   down = false;
  103. });
  104. document.addEventListener('touchstart', () => {
  105.   down = true;
  106. });
  107. document.addEventListener('touchend', () => {
  108.   down = false;
  109. });
  110.  
  111. let tweak;
  112. let tweakChoice;
  113. let tweakChance = 0.4;
  114. const clear = () => cells = blank.concat();
  115.  
  116. function draw() {
  117.   if (!down) {
  118.     tweak = false;
  119.     tweakChoice = Math.random();
  120.     clear();
  121.   } else {
  122.     if (tweakChoice < tweakChance) {
  123.       tweak = true;
  124.       clear();
  125.     }
  126.   }
  127.  
  128.   circs.forEach(circ => circ());
  129.   el.innerHTML = cells.join('');
  130. }
  131.  
  132. // 60fps is too fast, so use 30ms interval
  133. setInterval(draw, 30);
  134.  
  135. function hLine(xp, yp, w, col) {
  136.   for (let i = 0; i < w; i++) {
  137.     setSym(xp + i, yp, col);
  138.   }
  139.   return hLine;
  140. }
  141.  
  142. // bresenham circle
  143. function drawCircle(xp, yp, radius, sym = '@', isFilled) {
  144.   if (isFilled && tweak) sym = '';
  145.  
  146.   xp = parseInt(xp, 10);
  147.   yp = parseInt(yp, 10);
  148.   radius = parseInt(radius, 10);
  149.   let balance = -radius,
  150.     xoff = 0,
  151.     yoff = radius;
  152.  
  153.   while (xoff <= yoff) {
  154.     const p0 = xp - xoff;
  155.     const p1 = xp + xoff;
  156.  
  157.     const p2 = yp + yoff;
  158.     const p3 = yp - yoff;
  159.     const p4 = yp + xoff;
  160.     const p5 = xp + yoff;
  161.     const p6 = xp - yoff;
  162.     const p7 = yp - xoff;
  163.  
  164.     if (isFilled) {
  165.       const w0 = xoff + xoff;
  166.       const w1 = yoff + yoff;
  167.  
  168.       hLine
  169.         (p0, yp + yoff, w0, sym)
  170.         (p0, yp - yoff, w0, sym)
  171.         (p6, yp + xoff, w1, sym)
  172.         (p6, yp - xoff, w1, sym);
  173.  
  174.     } else {
  175.       setSym
  176.         (p1, p2, sym)
  177.         (p0, p2, sym)
  178.         (p0, p3, sym)
  179.         (p1, p3, sym)
  180.         (p5, p4, sym)
  181.         (p6, p4, sym)
  182.         (p6, p7, sym)
  183.         (p5, p7, sym);
  184.     }
  185.  
  186.     // never been able to find the original 
  187.     // source for the below condition 
  188.     // more info here: https://actionsnippet.com/?p=492
  189.     if ((balance += xoff++ + xoff) >= 0) {
  190.       balance -= --yoff + yoff;
  191.     }
  192.   }
  193. }

This is a bit of a longer snippet that uses the Bresenham circle drawing algorithm to draw some circles with text. I recommend looking at it with the fullscreen button.

I like to do this:

  1. hLine
  2.   (p0, yp + yoff, w0, sym)
  3.   (p0, yp - yoff, w0, sym)
  4.   (p6, yp + xoff, w1, sym)
  5.   (p6, yp - xoff, w1, sym);

Make a function return itself, so that if you need to call it many times without having to repeat the function name. Because this isn’t a common style it is generally frowned upon, I’m always tempted to use it at work for some reason… maybe next April fools.

30% Chance Math.random()

  1. // 30%
  2. if (Math.random() < .3) {
  3.   // 30% chance this will run
  4.   console.log('30%');
  5. } else {
  6.   console.log('nope');
  7. }

I do this all the time when creative coding. I usually use a seeded random number generator instead of Math.random. It’s very useful to have something happen X% of the time.

Let’s run this a few times and look at the results. Click/tap try it out.

  1. for (let i = 0; i < 111; i++) {
  2.   if (Math.random() < .3) {
  3.     console.log('30%');
  4.   } else {
  5.     console.log('nope');
  6.   }
  7. }

Scroll through the results of the console window.

Cistercian Numerals (semi-golfed)

  1. N = -1
  2. S = 30
  3. H = 90
  4. h = 45
  5. d = document
  6. b = d.body
  7. m = _ => d.createElement(_)
  8. a = _ => b.appendChild(_)
  9.  
  10. with(a(
  11.     Object.assign(
  12.       m`canvas`,
  13.       { width: 80, height: 120 }
  14.     )
  15.   ).getContext`2d`
  16. ) { 
  17.   a(m`br`)
  18.  
  19.   L = (a, b, C, d) => {
  20.     moveTo(a, b)
  21.     lineTo(C, d)
  22.   }
  23.  
  24.   M = _ => L(0, 0, 0, H)
  25.   O = _ => L(0, 0, S, 0)
  26.   W = _ => L(0, S, S, S)
  27.   T = _ => L(0, 0, S, S)
  28.   F = _ => L(0, S, S, 0)
  29.   X = _ => L(S, 0, S, S)
  30.  
  31.   D = [
  32.     _ => {},
  33.     O, W, T, F, 
  34.     [O,F],
  35.     X,
  36.     [O,X],
  37.     [W,X],
  38.     [O,W,X]
  39.   ]
  40.  
  41.   n = s => {
  42.     [...s].reverse().map((l, i) => {
  43.       save()
  44.       translate(S+9, h+9)
  45.       scale(N**i, N**~~(i/2))
  46.       translate(0, -h)
  47.       M() ;[D[l]].flat().map(x => x && x())
  48.       restore()
  49.     })
  50.   }
  51.  
  52.   a(m`input`).oninput = e => {
  53.     clearRect(0, 0, 80, 120)
  54.     beginPath()
  55.     n(~~e.target.value+'')
  56.     stroke()
  57.   }
  58. }

Type any number from 0-9999 into the input field and see the corresponding Cistercian Numeral. This snippet is partially golfed, I left the canvas commands intact to make things a bit easier to understand.

Simpler SVG Version

This one hardcodes all numbers 0-9 as paths, unlike the canvas version which only defines 1,2,3,4 and 6 as paths and then combines them to create 5,7,8 and 9.

  1. h='innerHTML'
  2. C='children'
  3.  
  4. document.body[h]=`
  5. <svg id=G width=99 viewBox="0 0 80 120" 
  6.   style="stroke:black;fill:none;overflow:visible">
  7.   <g transform="translate(30,2)">
  8.     <path d="M0 0L 30 0"/>
  9.     <path d="M0 30L 30 30"/>
  10.     <path d="M0 0L 30 30"/>
  11.     <path d="M0 30L30 0"/>
  12.     <path d="M0 30L30 0 0 0"/>
  13.     <path d="M30 0L30 30"/>
  14.     <path d="M30 30L30 0 0 0"/>
  15.     <path d="M0 30L30 30 30 0"/>
  16.     <path d="M0 30L30 30 30 0 0 0"/>
  17.   </g>
  18.   <g transform=translate(30,2)scale(-1,1)></g>
  19.   <g transform=translate(30,92)scale(1,-1)></g>
  20.   <g transform=translate(30,92)scale(-1,-1)></g>
  21.   <path id=m d="M 30 2 L 30 92"/>
  22. </svg>
  23. <style>path:not(#m){opacity:0}</style><br>
  24. <input id=I>`
  25.  
  26. c=G[C]
  27. p=c[0][h] 
  28.  
  29. n = s => 
  30.   [...s].reverse().map((l, i) => 
  31.    l-1>-1 && (c[i][C][l-1].style.opacity=1))
  32.  
  33. I.oninput = e => {
  34.   for(i=0;i<4;i++)c[i][h]=p
  35.   n(~~e.target.value+'')
  36. }
snippet.zone ~ 2021-24 /// {s/z}