Resource Toolbox Generator
Fill in resource details below, then generate HTML to paste directly into a Drupal body field.
How to use
- Click Add Resource for each resource you want to include.
- Fill in the fields. Fields marked * are required. Click the card header to collapse/expand it.
- Resources are grouped by Category — resources sharing the same category name appear under the same heading in the output. Heading order follows the order resources are listed here.
- Use ↑ ↓ to reorder resources. Use ✕ to remove one.
- Click Generate HTML. Fix any errors shown, then click Copy HTML.
- In Drupal, open the overview page, scroll to the Body field, and switch the editor to Source mode. Paste and save.
- Describe what the image shows — skip "Image of…" or "Picture of…"
- Keep it under ~125 characters
- Leave blank if the image is purely decorative, or if the title already fully communicates what it is (e.g. a book cover where the title is the book name)
Resources
0Generate HTML
When all resources are filled in, click Generate. You can re-generate after any edits.
⚠ Fix the following before generating:
Generated HTML — ready to paste into Thinkific
- Navigate to the overview page and click Edit.
- In the Body field toolbar, click the Source button (may appear as
</>orSourcedepending on your editor). - Select all existing content in the source editor and delete it, then paste your copied code.
- Click Save.
<style> and <script> tags. Your Body field must use the Full HTML text format — Basic HTML will strip these tags and break the layout and See More behavior. If you don't see a format selector, ask your Drupal admin to confirm your format is set to Full HTML.
tag is split to avoid ending the outer script block return `

All resources referenced from this course will be linked below for your convenience to view and download. Please use/adapt these resources to what works for you in your ministry context.
\t<` + `script> \t(function() { \t\tconst SELECTOR = "p.description-text, p[data-clamp]"; \t\tconst MAX_LINES = 2; \t\tconst LABEL_MORE = "See more"; \t\tconst LABEL_LESS = "See less"; \t\tconst LINK_CLASS = "see-more-link"; \t\tfunction getLineHeight(el) { \t\t\tconst lh = parseFloat(window.getComputedStyle(el).lineHeight); \t\t\treturn isNaN(lh) ? parseFloat(window.getComputedStyle(el).fontSize) * 1.2 : lh; \t\t} \t\tfunction binarySearchTruncation(p, fullText, link, maxHeight) { \t\t\tlet lo = 0, hi = fullText.length, best = ""; \t\t\tconst textNode = p.firstChild; \t\t\twhile (lo <= hi) { \t\t\t\tconst mid = Math.floor((lo + hi) / 2); \t\t\t\ttextNode.nodeValue = fullText.slice(0, mid) + "\\u2026 "; \t\t\t\tif (p.scrollHeight <= maxHeight) { best = fullText.slice(0, mid); lo = mid + 1; } \t\t\t\telse { hi = mid - 1; } \t\t\t} \t\t\treturn best.slice(0, -3).trim(); \t\t} \t\tfunction setupOne(p) { \t\t\tp.style.display = "block"; \t\t\tp.style.webkitLineClamp = "unset"; \t\t\tp.style.webkitBoxOrient = "unset"; \t\t\tp.style.lineClamp = "unset"; \t\t\tp.style.textOverflow = "clip"; \t\t\tconst fullText = p.textContent.trim(); \t\t\tconst textNode = document.createTextNode(""); \t\t\tconst link = document.createElement("a"); \t\t\tlink.href = "#"; link.className = LINK_CLASS; \t\t\tlink.setAttribute("aria-label", LABEL_MORE); \t\t\tlink.textContent = LABEL_MORE; \t\t\tp.textContent = ""; \t\t\tp.appendChild(textNode); \t\t\tp.appendChild(link); \t\t\tlet expanded = false; \t\t\tfunction render() { \t\t\t\tconst maxHeight = Math.round(getLineHeight(p) * MAX_LINES); \t\t\t\tp.style.maxHeight = "none"; p.style.overflow = "visible"; \t\t\t\ttextNode.nodeValue = fullText; \t\t\t\tif (expanded) { \t\t\t\t\ttextNode.nodeValue = fullText + " "; link.textContent = LABEL_LESS; link.style.display = ""; return; \t\t\t\t} \t\t\t\tif (p.scrollHeight <= maxHeight) { \t\t\t\t\tlink.style.display = "none"; \t\t\t\t} else { \t\t\t\t\tlink.style.display = ""; link.textContent = LABEL_MORE; \t\t\t\t\tp.style.maxHeight = maxHeight + "px"; p.style.overflow = "hidden"; \t\t\t\t\ttextNode.nodeValue = binarySearchTruncation(p, fullText, link, maxHeight) + "\\u2026 "; \t\t\t\t} \t\t\t} \t\t\tlink.addEventListener("click", function(e) { e.preventDefault(); expanded = !expanded; render(); }); \t\t\tnew ResizeObserver(render).observe(p); \t\t\trender(); \t\t} \t\tfunction processAll() { \t\t\tdocument.querySelectorAll(SELECTOR).forEach(function(p) { \t\t\t\tif (!p.querySelector("." + LINK_CLASS)) setupOne(p); \t\t\t}); \t\t} \t\twindow.initSeeMore = processAll; \t\tif (document.readyState === "loading") document.addEventListener("DOMContentLoaded", processAll); \t\telse processAll(); \t})(); <` + `/script>`; } // ───────────────────────────────────────────── // Copy to clipboard // ───────────────────────────────────────────── function copyHTML() { const text = document.getElementById('output-code').textContent; const btn = document.getElementById('copy-btn'); navigator.clipboard.writeText(text).then(() => { btn.textContent = '✓ Copied!'; btn.classList.add('done'); setTimeout(() => { btn.innerHTML = '📋 Copy HTML'; btn.classList.remove('done'); }, 2500); }).catch(() => { // Fallback for browsers without clipboard API const ta = document.createElement('textarea'); ta.value = text; ta.style.position = 'fixed'; ta.style.opacity = '0'; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); document.body.removeChild(ta); btn.textContent = '✓ Copied!'; btn.classList.add('done'); setTimeout(() => { btn.innerHTML = '📋 Copy HTML'; btn.classList.remove('done'); }, 2500); }); } // ───────────────────────────────────────────── // Utilities // ───────────────────────────────────────────── function escHtml(s) { if (!s) return ''; return s.replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"').replace(/'/g,'''); } function escAttr(s) { if (!s) return ''; return s.replace(/&/g,'&').replace(/"/g,'"').replace(//g,'>'); }
