JSFiddle - React, Tailwind, and code Playground

by MegaScience

HTML

<form id="messageFormatter" name="messageFormatter" method="post" onsubmit="return submitPost()">
    <table>
        <tr>
            <th colspan="2">Format Messages with Dates</th>
        </tr>
        <tr>
            <td colspan="2"><p>Use this to format messages. Format is in the form <code>%L#"</code>, where <code>%</code> identifies the replacement, <code>L</code> is any letter for the format type, and <code>#</code> is any number between <code>1</code> and <code>100</code> matching the number adjacent to a date in the below list. For example, typing <code>Hello %F1!</code> with the date <code>July 1, 2021 at 6:00 AM PDT</code> in slot 1 will result in the message <code>Hello &lt;t:1625144400:F&gt;!</code>, which displays in English language in EDT as <code>Hello Thursday, July 1, 2021 9:00 AM!</code> For more information, <a href="https://c.r74n.com/discord/formatting">this website shows the formatting</a>. Note that the letters are case sensitive.</p></td>
        </tr>
        <tr>
            <th>Message Format:</th>
            <td>
                <textarea type="text" id="messageFormat" name="messageFormat" oninput="expandTextarea(this)" required></textarea>
            </td>
        </tr>
        <tr>
            <th>Dates/Times:</th>
            <td>
                <ol id="datesTimes">
                    <li><button type="button" tabindex="-1" onclick="addListItem()">+</button><span><input type="text"
                                name="date" placeholder="Example: July 11, 2021, 12:00 PM PDT"></span></li>
                </ol>
            </td>
        </tr>
        <tr>
            <td class="controls" colspan="2">
                <input type="submit" name="submit" value="Submit" />
            </td>
        </tr>
        <tr>
            <td colspan="2">
                <textarea type="text" id="messageOutput" name="messageOutput"
                    placeholder="Formatted message appears here." oninput="expandTextarea(this)"></textarea>
  ...

CSS

html, body {
  display: flex;
  justify-content: center;
  align-items: center;
  background-color: #ccccc4;
  min-height: 100%;
  margin: 0;
  font-family: arial, sans-serif;
  font-size: 14px;
}

th[colspan="2"] {
  padding: 5px 0;
  font-size: 24px;
}

th:not([colspan="2"]) {
  text-align: left;
  vertical-align: top;
  width: 14em;
}

tr th:first-child, tr td:first-child {
  padding-left: 10px;
}

tr th:last-child, tr td:last-child {
  padding-right: 10px;
}

form {
  /*max-width: 960px;*/
  max-width: 880px;
  margin: 5px auto;
  min-height: calc(100% - 35px);
  background-color: #FFF;
  border: 1px solid black;
  border-radius: 25px;
  padding-bottom: 25px;
}

table {
  width: 100%;
  border-collapse: collapse;
}

input, button, textarea {
  -moz-box-sizing: border-box;
  -webkit-box-sizing: border-box;
  box-sizing: border-box;
}

ol {
  margin: 0;
  padding-left: 0;
  list-style-position: outside;
}

input, textarea {
  width: 100%;
}

li span {
  display: block;
  overflow: hidden;
}

li button {
  float: right;
  padding: 0;
  width: 1.65em;
  height: 1.65em;
}

textarea {
  resize: vertical;
}

.controls input {
  display: inline-block;
  width: 100%;
}

JavaScript

//const regex = /\b%(\w)([0-9]{1,2})\b/g
const regex = /(^|\s)%([a-zA-Z])([0-9]{1,2})\b/g
const dateTimesE = document.getElementById('datesTimes')
const dateRowTemplate = document.getElementById('dateRow')
const messageFormatE = document.getElementById('messageFormat')
const messageOutputE = document.getElementById('messageOutput')

// https://stackoverflow.com/a/1353711
//const isValidDate = d => d instanceof Date && !isNaN(d)
const isInvalidDate = d => !(d instanceof Date && !isNaN(d))
const addListItem = () => dateTimesE.appendChild(dateRowTemplate.content.cloneNode(true))
const removeListItem = e => e.closest('li').remove()

function expandTextarea(e) {
  e.style.height = ''
  e.style.height = `${e.scrollHeight + 5}px`
}

function submitPost() {
  try {
    const messageFormat = messageFormatE.value
    //TODO: Add more trimming to ease validation.
    const dates = Array.from(document.getElementsByName('date')).map(e => new Date(e.value.replace(' at ', ' ')))
    //const dates = Array.from(document.getElementsByName('date')).map(e => e.value.trim()).filter(v => v.length > 0).map(v => new Date(v.replace(' at ', ' ')))
    //if (dates.some(isInvalidDate)) throw ['One or more inputted dates are invalid.', dates]
    const unixes = dates.map(d => Math.round(+d / 1000))
    const formattedMessage = messageFormat.replaceAll(regex, (m, o, timeType, unixIndex) => {
      //if (typeof unixes[--unixIndex] === 'undefined') return m
      if (isInvalidDate(dates[--unixIndex])) return m
      return `${o}<t:${unixes[unixIndex]}:${timeType}>`
    })
    messageOutputE.value = formattedMessage
    expandTextarea(messageOutputE)
  }
  catch (e) {
    if (Array.isArray(e)) {
      alert(e.shift())
      console.log(e)
    }
    else alert(e)
  }
  finally {
    return false
  }
}

addListItem()
addListItem()