diff --git a/PInNote/1.0.0/PinNote.js b/PInNote/1.0.0/PinNote.js new file mode 100644 index 000000000..ad8213a89 --- /dev/null +++ b/PInNote/1.0.0/PinNote.js @@ -0,0 +1,345 @@ +// Script: PinNote +// By: Keith Curtis +// Contact: https://app.roll20.net/users/162065/keithcurtis +var API_Meta = API_Meta||{}; //eslint-disable-line no-var +API_Meta.PinNote={offset:Number.MAX_SAFE_INTEGER,lineCount:-1}; +{try{throw new Error('');}catch(e){API_Meta.PinNote.offset=(parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/,'$1'),10)-6);}} + + +(() => { + 'use strict'; + + const version = '1.0.0'; //version number set here + log('-=> PinNote v' + version + ' is loaded.'); + //Changelog + //1.0.0 Debut + + + const SCRIPT_NAME = 'PinNote'; + + const isGMPlayer = (playerid) => playerIsGM(playerid); + + const getTemplate = (name) => { + if (typeof Supernotes_Templates === 'undefined') { + return null; + } + if (!name) return Supernotes_Templates.generic; + const key = name.toLowerCase(); + return Supernotes_Templates[key] || Supernotes_Templates.generic; + }; + + const sendGenericError = (msg, text) => { + if (typeof Supernotes_Templates === 'undefined') return; + + const t = Supernotes_Templates.generic; + sendChat( + SCRIPT_NAME, + t.boxcode + + t.titlecode + SCRIPT_NAME + + t.textcode + text + + '' + + t.footer + + '' + ); + }; + + /* ============================================================ + * HEADER COLOR ENFORCEMENT + * ============================================================ */ + + const enforceHeaderColor = (html, template) => { + if (!html) return html; + + const colorMatch = template.textcode.match(/color\s*:\s*([^;"]+)/i); + if (!colorMatch) return html; + + const colorValue = colorMatch[1].trim(); + + return html.replace( + /<(h[1-4])\b([^>]*)>/gi, + (match, tag, attrs) => { + + if (/style\s*=/i.test(attrs)) { + return `<${tag}${attrs.replace( + /style\s*=\s*["']([^"']*)["']/i, + (m, styleContent) => + `style="${styleContent}; color: ${colorValue};"` + )}>`; + } + + return `<${tag}${attrs} style="color: ${colorValue};">`; + } + ); + }; + + /* ============================================================ */ + + const parseArgs = (content) => { + const args = {}; + content.replace(/--([^|]+)\|([^\s]+)/gi, (_, k, v) => { + args[k.toLowerCase()] = v.toLowerCase(); + return ''; + }); + return args; + }; + + const extractHandoutSection = ({ handout, subLink, subLinkType }) => { + return new Promise((resolve) => { + + if (!handout) return resolve(null); + + if (!subLink) { + const field = subLinkType === 'headerGM' ? 'gmnotes' : 'notes'; + handout.get(field, (content) => resolve(content || null)); + return; + } + + if (!['headerplayer', 'headergm'].includes(subLinkType?.toLowerCase())) { + return resolve(null); + } + + const field = subLinkType.toLowerCase() === 'headergm' + ? 'gmnotes' + : 'notes'; + + handout.get(field, (content) => { + if (!content) return resolve(null); + + const headerRegex = /<(h[1-4])\b[^>]*>([\s\S]*?)<\/\1>/gi; + let match; + + while ((match = headerRegex.exec(content)) !== null) { + const tagName = match[1]; + const innerHTML = match[2]; + const stripped = innerHTML.replace(/<[^>]+>/g, ''); + + if (stripped === subLink) { + const level = parseInt(tagName[1], 10); + const startIndex = match.index; + + const remainder = content.slice(headerRegex.lastIndex); + + const stopRegex = new RegExp( + `]*>`, + 'i' + ); + + const stopMatch = stopRegex.exec(remainder); + + const endIndex = stopMatch + ? headerRegex.lastIndex + stopMatch.index + : content.length; + + return resolve(content.slice(startIndex, endIndex)); + } + } + + resolve(null); + }); + }); + }; + + const transformBlockquoteMode = (html) => { + + const blockRegex = /]*>([\s\S]*?)<\/blockquote>/gi; + + let match; + let lastIndex = 0; + let playerContent = ''; + let gmContent = ''; + let found = false; + + while ((match = blockRegex.exec(html)) !== null) { + found = true; + gmContent += html.slice(lastIndex, match.index); + playerContent += match[1]; + lastIndex = blockRegex.lastIndex; + } + + gmContent += html.slice(lastIndex); + + if (!found) { + return { player: '', gm: html }; + } + + return { player: playerContent, gm: gmContent }; + }; + + on('chat:message', async (msg) => { + if (msg.type !== 'api' || !msg.content.startsWith('!pinnote')) return; + + if (typeof Supernotes_Templates === 'undefined') { + sendChat(SCRIPT_NAME, `/w gm PinNote requires Supernotes_Templates to be loaded.`); + return; + } + + const args = parseArgs(msg.content); + const isGM = isGMPlayer(msg.playerid); + + if (!msg.selected || msg.selected.length === 0) + return sendGenericError(msg, 'No pin selected.'); + + const sel = msg.selected.find(s => s._type === 'pin'); + if (!sel) + return sendGenericError(msg, 'Selected object is not a pin.'); + + const pin = getObj('pin', sel._id); + if (!pin) + return sendGenericError(msg, 'Selected pin could not be resolved.'); + + const isSynced = + !pin.get('notesDesynced') && + !pin.get('gmNotesDesynced') && + !pin.get('imageDesynced'); + + const linkType = pin.get('linkType'); + + /* ============================================================ + * LINKED HANDOUT MODE + * ============================================================ */ + + if (isSynced && linkType === 'handout') { + + const handoutId = pin.get('link'); + const subLink = pin.get('subLink'); + const subLinkType = pin.get('subLinkType'); + const autoNotesType = pin.get('autoNotesType'); + + const handout = getObj('handout', handoutId); + if (!handout) + return sendGenericError(msg, 'Linked handout not found.'); + + let extracted = await extractHandoutSection({ + handout, + subLink, + subLinkType + }); + + if (!extracted) + return sendGenericError(msg, 'Requested section not found in handout.'); + + const template = getTemplate(args.template); + if (!template) return; + + const sender = pin.get('title') || SCRIPT_NAME; + const titleText = subLink || sender; + + if (subLink) { + const headerStripRegex = /^]*>[\s\S]*?<\/h[1-4]>/i; + extracted = extracted.replace(headerStripRegex, ''); + } + + let to = (args.to || 'pc').toLowerCase(); + if (!isGM) to = 'pc'; + + let whisperPrefix = ''; + const extractingGM = subLinkType?.toLowerCase() === 'headergm'; + + let visibleContent = extracted; + let gmBlock = ''; + + if (autoNotesType === 'blockquote') { + + const transformed = transformBlockquoteMode(extracted); + + visibleContent = enforceHeaderColor(transformed.player, template); + + if (transformed.gm && to !== 'pc') { + gmBlock = + `
` + + enforceHeaderColor(transformed.gm, template) + + `
`; + } + + } else { + visibleContent = enforceHeaderColor(visibleContent, template); + } + + if (extractingGM) { + whisperPrefix = '/w gm '; + } else if (to === 'gm') { + whisperPrefix = '/w gm '; + } else if (to === 'self') { + whisperPrefix = `/w "${msg.who}" `; + } + + const html = + template.boxcode + + template.titlecode + titleText + + template.textcode + + (visibleContent || '') + + gmBlock + + '' + + template.footer + + ''; + + sendChat(sender, whisperPrefix + html); + return; + } + + /* ============================================================ + * CUSTOM PIN MODE + * ============================================================ */ + + if ( + !pin.get('notesDesynced') && + !pin.get('gmNotesDesynced') && + !pin.get('imageDesynced') + ) { + return sendGenericError( + msg, + 'This pin is not desynced from its linked handout.' + ); + } + + const notes = (pin.get('notes') || '').trim(); + if (!notes) + return sendGenericError(msg, 'This pin has no notes to display.'); + + let to = (args.to || 'pc').toLowerCase(); + if (!isGM) to = 'pc'; + + let whisperPrefix = ''; + if (to === 'gm') whisperPrefix = '/w gm '; + else if (to === 'self') whisperPrefix = `/w "${msg.who}" `; + + const template = getTemplate(args.template); + if (!template) return; + + const sender = pin.get('title') || SCRIPT_NAME; + + let imageBlock = ''; + const tooltipImage = pin.get('tooltipImage'); + if (tooltipImage) { + imageBlock = + ``; + } + + const coloredNotes = enforceHeaderColor(notes, template); + + let gmBlock = ''; + if (isGM && to !== 'pc' && pin.get('gmNotes')) { + gmBlock = + `
` + + enforceHeaderColor(pin.get('gmNotes'), template) + + `
`; + } + + const html = + template.boxcode + + template.titlecode + sender + + template.textcode + + imageBlock + + coloredNotes + + gmBlock + + '' + + template.footer + + ''; + + sendChat(sender, whisperPrefix + html); + }); + +})(); + +{try{throw new Error('');}catch(e){API_Meta.PinNote.lineCount=(parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/,'$1'),10)-API_Meta.PinNote.offset);}} diff --git a/PInNote/PinNote.js b/PInNote/PinNote.js new file mode 100644 index 000000000..ad8213a89 --- /dev/null +++ b/PInNote/PinNote.js @@ -0,0 +1,345 @@ +// Script: PinNote +// By: Keith Curtis +// Contact: https://app.roll20.net/users/162065/keithcurtis +var API_Meta = API_Meta||{}; //eslint-disable-line no-var +API_Meta.PinNote={offset:Number.MAX_SAFE_INTEGER,lineCount:-1}; +{try{throw new Error('');}catch(e){API_Meta.PinNote.offset=(parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/,'$1'),10)-6);}} + + +(() => { + 'use strict'; + + const version = '1.0.0'; //version number set here + log('-=> PinNote v' + version + ' is loaded.'); + //Changelog + //1.0.0 Debut + + + const SCRIPT_NAME = 'PinNote'; + + const isGMPlayer = (playerid) => playerIsGM(playerid); + + const getTemplate = (name) => { + if (typeof Supernotes_Templates === 'undefined') { + return null; + } + if (!name) return Supernotes_Templates.generic; + const key = name.toLowerCase(); + return Supernotes_Templates[key] || Supernotes_Templates.generic; + }; + + const sendGenericError = (msg, text) => { + if (typeof Supernotes_Templates === 'undefined') return; + + const t = Supernotes_Templates.generic; + sendChat( + SCRIPT_NAME, + t.boxcode + + t.titlecode + SCRIPT_NAME + + t.textcode + text + + '' + + t.footer + + '' + ); + }; + + /* ============================================================ + * HEADER COLOR ENFORCEMENT + * ============================================================ */ + + const enforceHeaderColor = (html, template) => { + if (!html) return html; + + const colorMatch = template.textcode.match(/color\s*:\s*([^;"]+)/i); + if (!colorMatch) return html; + + const colorValue = colorMatch[1].trim(); + + return html.replace( + /<(h[1-4])\b([^>]*)>/gi, + (match, tag, attrs) => { + + if (/style\s*=/i.test(attrs)) { + return `<${tag}${attrs.replace( + /style\s*=\s*["']([^"']*)["']/i, + (m, styleContent) => + `style="${styleContent}; color: ${colorValue};"` + )}>`; + } + + return `<${tag}${attrs} style="color: ${colorValue};">`; + } + ); + }; + + /* ============================================================ */ + + const parseArgs = (content) => { + const args = {}; + content.replace(/--([^|]+)\|([^\s]+)/gi, (_, k, v) => { + args[k.toLowerCase()] = v.toLowerCase(); + return ''; + }); + return args; + }; + + const extractHandoutSection = ({ handout, subLink, subLinkType }) => { + return new Promise((resolve) => { + + if (!handout) return resolve(null); + + if (!subLink) { + const field = subLinkType === 'headerGM' ? 'gmnotes' : 'notes'; + handout.get(field, (content) => resolve(content || null)); + return; + } + + if (!['headerplayer', 'headergm'].includes(subLinkType?.toLowerCase())) { + return resolve(null); + } + + const field = subLinkType.toLowerCase() === 'headergm' + ? 'gmnotes' + : 'notes'; + + handout.get(field, (content) => { + if (!content) return resolve(null); + + const headerRegex = /<(h[1-4])\b[^>]*>([\s\S]*?)<\/\1>/gi; + let match; + + while ((match = headerRegex.exec(content)) !== null) { + const tagName = match[1]; + const innerHTML = match[2]; + const stripped = innerHTML.replace(/<[^>]+>/g, ''); + + if (stripped === subLink) { + const level = parseInt(tagName[1], 10); + const startIndex = match.index; + + const remainder = content.slice(headerRegex.lastIndex); + + const stopRegex = new RegExp( + `]*>`, + 'i' + ); + + const stopMatch = stopRegex.exec(remainder); + + const endIndex = stopMatch + ? headerRegex.lastIndex + stopMatch.index + : content.length; + + return resolve(content.slice(startIndex, endIndex)); + } + } + + resolve(null); + }); + }); + }; + + const transformBlockquoteMode = (html) => { + + const blockRegex = /]*>([\s\S]*?)<\/blockquote>/gi; + + let match; + let lastIndex = 0; + let playerContent = ''; + let gmContent = ''; + let found = false; + + while ((match = blockRegex.exec(html)) !== null) { + found = true; + gmContent += html.slice(lastIndex, match.index); + playerContent += match[1]; + lastIndex = blockRegex.lastIndex; + } + + gmContent += html.slice(lastIndex); + + if (!found) { + return { player: '', gm: html }; + } + + return { player: playerContent, gm: gmContent }; + }; + + on('chat:message', async (msg) => { + if (msg.type !== 'api' || !msg.content.startsWith('!pinnote')) return; + + if (typeof Supernotes_Templates === 'undefined') { + sendChat(SCRIPT_NAME, `/w gm PinNote requires Supernotes_Templates to be loaded.`); + return; + } + + const args = parseArgs(msg.content); + const isGM = isGMPlayer(msg.playerid); + + if (!msg.selected || msg.selected.length === 0) + return sendGenericError(msg, 'No pin selected.'); + + const sel = msg.selected.find(s => s._type === 'pin'); + if (!sel) + return sendGenericError(msg, 'Selected object is not a pin.'); + + const pin = getObj('pin', sel._id); + if (!pin) + return sendGenericError(msg, 'Selected pin could not be resolved.'); + + const isSynced = + !pin.get('notesDesynced') && + !pin.get('gmNotesDesynced') && + !pin.get('imageDesynced'); + + const linkType = pin.get('linkType'); + + /* ============================================================ + * LINKED HANDOUT MODE + * ============================================================ */ + + if (isSynced && linkType === 'handout') { + + const handoutId = pin.get('link'); + const subLink = pin.get('subLink'); + const subLinkType = pin.get('subLinkType'); + const autoNotesType = pin.get('autoNotesType'); + + const handout = getObj('handout', handoutId); + if (!handout) + return sendGenericError(msg, 'Linked handout not found.'); + + let extracted = await extractHandoutSection({ + handout, + subLink, + subLinkType + }); + + if (!extracted) + return sendGenericError(msg, 'Requested section not found in handout.'); + + const template = getTemplate(args.template); + if (!template) return; + + const sender = pin.get('title') || SCRIPT_NAME; + const titleText = subLink || sender; + + if (subLink) { + const headerStripRegex = /^]*>[\s\S]*?<\/h[1-4]>/i; + extracted = extracted.replace(headerStripRegex, ''); + } + + let to = (args.to || 'pc').toLowerCase(); + if (!isGM) to = 'pc'; + + let whisperPrefix = ''; + const extractingGM = subLinkType?.toLowerCase() === 'headergm'; + + let visibleContent = extracted; + let gmBlock = ''; + + if (autoNotesType === 'blockquote') { + + const transformed = transformBlockquoteMode(extracted); + + visibleContent = enforceHeaderColor(transformed.player, template); + + if (transformed.gm && to !== 'pc') { + gmBlock = + `
` + + enforceHeaderColor(transformed.gm, template) + + `
`; + } + + } else { + visibleContent = enforceHeaderColor(visibleContent, template); + } + + if (extractingGM) { + whisperPrefix = '/w gm '; + } else if (to === 'gm') { + whisperPrefix = '/w gm '; + } else if (to === 'self') { + whisperPrefix = `/w "${msg.who}" `; + } + + const html = + template.boxcode + + template.titlecode + titleText + + template.textcode + + (visibleContent || '') + + gmBlock + + '' + + template.footer + + ''; + + sendChat(sender, whisperPrefix + html); + return; + } + + /* ============================================================ + * CUSTOM PIN MODE + * ============================================================ */ + + if ( + !pin.get('notesDesynced') && + !pin.get('gmNotesDesynced') && + !pin.get('imageDesynced') + ) { + return sendGenericError( + msg, + 'This pin is not desynced from its linked handout.' + ); + } + + const notes = (pin.get('notes') || '').trim(); + if (!notes) + return sendGenericError(msg, 'This pin has no notes to display.'); + + let to = (args.to || 'pc').toLowerCase(); + if (!isGM) to = 'pc'; + + let whisperPrefix = ''; + if (to === 'gm') whisperPrefix = '/w gm '; + else if (to === 'self') whisperPrefix = `/w "${msg.who}" `; + + const template = getTemplate(args.template); + if (!template) return; + + const sender = pin.get('title') || SCRIPT_NAME; + + let imageBlock = ''; + const tooltipImage = pin.get('tooltipImage'); + if (tooltipImage) { + imageBlock = + ``; + } + + const coloredNotes = enforceHeaderColor(notes, template); + + let gmBlock = ''; + if (isGM && to !== 'pc' && pin.get('gmNotes')) { + gmBlock = + `
` + + enforceHeaderColor(pin.get('gmNotes'), template) + + `
`; + } + + const html = + template.boxcode + + template.titlecode + sender + + template.textcode + + imageBlock + + coloredNotes + + gmBlock + + '' + + template.footer + + ''; + + sendChat(sender, whisperPrefix + html); + }); + +})(); + +{try{throw new Error('');}catch(e){API_Meta.PinNote.lineCount=(parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/,'$1'),10)-API_Meta.PinNote.offset);}} diff --git a/PInNote/readme.md b/PInNote/readme.md new file mode 100644 index 000000000..fe52012fa --- /dev/null +++ b/PInNote/readme.md @@ -0,0 +1,83 @@ +# PinNote Script + +The **PinNote** script sends information from linked or custom map pins to chat using any Supernotes template. +You must have Supernotes installed. + +--- + +## Arguments + +Arguments are case-insensitive and use the format: + +``` +--key|value +``` + +--- + +### `--to|` + +Controls where the message is sent. + +#### `--to|pc` + +Sends a public message to chat. + +- GM notes are **never included**. + +--- + +#### `--to|gm` + +Whispers the message to the GM only. + +- GM notes **are included**. + +--- + +#### `--to|self` + +Whispers the message to the invoking player. + +- GM notes are included **only if the invoker is a GM**. + +--- + +If a non-GM runs the command, `--to` is ignored and treated as `pc`. + +--- + +### `--template|` (optional) + +Selects a Supernotes display template. + +- If omitted or invalid, the **generic** template is used silently. + +--- + +## Examples + +``` +!pinnote +!pinnote --to|gm +!pinnote --to|self --template|dark +!pinnote --template|wizard +``` + +--- + +## Requirements + +- Exactly **one map pin** must be selected. + - If none are selected, the script reports an error. + - If multiple are selected, only the first pin is used. + +- The pin may be: + - A **linked pin** (pulls data from its linked handout), or + - A **custom pin** (uses its Notes field). + +- A custom pin must contain notes. + - If the Notes field is empty, nothing is sent and an error is shown. + +- **Supernotes must be installed.** + - If missing, the script exits and notifies the GM. diff --git a/PInNote/script.json b/PInNote/script.json new file mode 100644 index 000000000..9c34fa4f3 --- /dev/null +++ b/PInNote/script.json @@ -0,0 +1,15 @@ +{ + "name": "PinNote", + "script": "PinNote.js", + "version": "1.0.0", + "description": "# PinNote\n\nPinNote sends information from linked or custom map pins to chat using any Supernotes template. Supernotes must be installed for this script to function.\n\n---\n\n## Arguments\n\nArguments are case-insensitive and use the format:\n\n```\n--key|value\n```\n\n---\n\n### --to|\n\nControls where the message is sent.\n\n#### --to|pc\n\nSends a public message to chat.\n\n- GM notes are never included.\n\n---\n\n#### --to|gm\n\nWhispers the message to the GM only.\n\n- GM notes are included.\n\n---\n\n#### --to|self\n\nWhispers the message to the invoking player.\n\n- GM notes are included only if the invoker is a GM.\n\n---\n\nIf a non-GM runs the command, --to is ignored and treated as pc.\n\n---\n\n### --template| (optional)\n\nSelects a Supernotes display template.\n\n- If omitted or invalid, the generic template is used silently.\n\n---\n\n## Examples\n\n```\n!pinnote\n!pinnote --to|gm\n!pinnote --to|self --template|dark\n!pinnote --template|wizard\n```\n\n---\n\n## Requirements\n\n- Exactly one map pin must be selected.\n - If none are selected, the script reports an error.\n - If multiple are selected, only the first pin is used.\n\n- The pin may be a linked pin or a custom pin.\n - If linked to a handout, the script pulls the relevant section from the handout.\n - If custom, the script uses the Notes field of the pin.\n\n- A custom pin must contain notes.\n - If the Notes field is empty, nothing is sent and an error is shown.\n\n- Supernotes must be installed.\n - If missing, the script exits and notifies the GM.\n\n---\n\nType **!pinnote** in chat to use the script.", + "authors": "Keith Curtis", + "roll20userid": "162065", + "dependencies": ["Supernotes"], + "modifies": { + "pin": "read", + "handout": "read" + }, + "conflicts": [], + "previousversions": ["1.0.0"] +} diff --git a/Supernotes/0.2.6/Supernotes.js b/Supernotes/0.2.6/Supernotes.js new file mode 100644 index 000000000..3b8bcec52 --- /dev/null +++ b/Supernotes/0.2.6/Supernotes.js @@ -0,0 +1,1507 @@ +var API_Meta = API_Meta||{}; +API_Meta.Supernotes={offset:Number.MAX_SAFE_INTEGER,lineCount:-1}; +{try{throw new Error('');}catch(e){API_Meta.Supernotes.offset=(parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/,'$1'),10)-3);}} + + +// Supernotes_Templates can be called by other scripts. At this point ScriptCards is the only One Click script that does this. +let Supernotes_Templates = { + generic: { + boxcode: `
`, + titlecode: `
`, + textcode: "
", + buttonwrapper: `
`, + buttonstyle: `style='display:inline-block; color:#ce0f69 !important; background-color: transparent;padding: 0px; border: none'`, + playerbuttonstyle: `style='display:inline-block; color:#ce0f69; background-color: transparent;padding: 0px; border: none;'`, + buttondivider: ' | ', + handoutbuttonstyle: `style='display:inline-block; color:#ce0f69; background-color: transparent;padding: 0px; border: none;'`, + whisperStyle: `'background-color:#2b2130; color:#fbfcf0; display:block; border-width: 1px; border-style: solid; border-color:#a3a681; padding:5px'`, + whisperbuttonstyle: `style='display:inline-block; color:#bbb; background-color: transparent;padding: 0px; border: none'`, + footer: "" + }, + + dark: { + boxcode: `
`, + titlecode: `
`, + textcode: "
", + buttonwrapper: `
`, + buttonstyle: `style='display:inline-block; color:#a980bd; background-color: transparent;padding: 0px; border: none'`, + playerbuttonstyle: `style='display:inline-block; color:#a980bd; background-color: transparent;padding: 0px; border: none;'`, + buttondivider: ' | ', + handoutbuttonstyle: `style='display:inline-block; color:#a980bd; background-color: transparent;padding: 0px; border: none;'`, + whisperStyle: `'background-color:#2b2130; color:#fbfcf0; display:block; border-width: 1px; border-style: solid; border-color:#a3a681; padding:5px'`, + whisperbuttonstyle: `style='display:inline-block; color:#bbb; background-color: transparent;padding: 0px; border: none'`, + footer: "" + }, + + + roll20dark: { + boxcode: `
`, + titlecode: `
`, + textcode: "
", + buttonwrapper: `
`, + buttonstyle: `style='display:inline-block; color:#a980bd; font-weight:bold; background-color: transparent;padding: 0px; border: none'`, + playerbuttonstyle: `style='display:inline-block; color:#fff; font-weight:bolder; background-color: #e7339d;border-radius: 4px; margin:4px; padding: 2px 6px 2px 6px; border: none; font-family:"proxima nova", sans-serif; ;'`, + buttondivider: '', + handoutbuttonstyle: `style='display:inline-block; color:#fff; font-weight:bolder; background-color: #e7339d;border-radius: 4px; margin:4px; padding: 2px 6px 2px 6px; border: none;font-family:"nunito black", nunito;'`, + whisperStyle: `'background-color:#f9cce7; color:#111; display:block; padding:5px; margin-top:20px;'`, + whisperbuttonstyle: `style='display:inline-block; color:#702c91; font-weight:bold; background-color: transparent;padding: 0px; border: none'`, + footer: "" + }, + + roll20light: { + boxcode: `
`, + titlecode: `
`, + textcode: "
", + buttonwrapper: `
`, + buttonstyle: `style='display:inline-block; color:#702c91; font-weight:bold; background-color: transparent;padding: 0px; border: none'`, + playerbuttonstyle: `style='display:inline-block; color:#fff; font-weight:bolder; background-color: #e7339d;border-radius: 4px; margin:4px; padding: 2px 6px 2px 6px; border: none; font-family:"proxima nova", sans-serif; ;'`, + buttondivider: '', + handoutbuttonstyle: `style='display:inline-block; color:#fff; font-weight:bolder; background-color: #e7339d;border-radius: 4px; margin:4px; padding: 2px 6px 2px 6px; border: none; font-family:"Nunito Black", nunito;'`, + whisperStyle: `'background-color:#f9cce7; color:#111; display:block; padding:5px; margin-top:20px;'`, + whisperbuttonstyle: `style='display:inline-block; color:#702c91; font-weight:bold; background-color: transparent;padding: 0px; border: none'`, + footer: "" + }, + + + lcars: { + boxcode: `
`, + titlecode: `
`, + textcode: "
", + buttonwrapper: `
`, + buttonstyle: `style='display:inline-block; color:#cc6060; background-color: transparent;padding: 0px; border: none'`, + playerbuttonstyle: `style='display:inline-block; border:none; color:black; background-color: #cc6060; border-radius: 10px 0px 0px 10px; padding: 2px 4px 2px 4px;margin-top: 12px; font-size: 10px; font-family: Tahoma, sans-serif; font-stretch: condensed !important; text-transform: uppercase;'`, + buttondivider: '', + handoutbuttonstyle: `style='display:inline-block; border:none; color:black; background-color: #cc6060; border-radius: 0px 10px 10px 0px; padding: 2px 4px 2px 4px;margin-top: 12px; margin-left:4px; font-size: 10px; font-family: Tahoma, sans-serif; font-stretch: condensed !important; text-transform: uppercase;'`, + whisperStyle: `'border-radius: 10px 0px 0px 10px; color:#ffae21; border-color: #ffae21; display:block; border-width: 0px 0px 5px 15px; border-style: solid; padding:5px'`, + whisperbuttonstyle: `style='display:inline-block; color:#cc6060; background-color: transparent;padding: 0px; border: none'`, + footer: "" + }, + + faraway: { + boxcode: `
`, + titlecode: `
`, + textcode: "
", + buttonwrapper: `
`, + buttonstyle: `style='display:inline-block; color:#13f2fc; background-color: transparent;padding: 0px; border: none'`, + playerbuttonstyle: `style='display:inline-block; color:#13f2fc; font-weight:normal; background-color: transparent;padding: 0px; border: none;'`, + buttondivider: ``, + handoutbuttonstyle: `style='display:inline-block; color:#13f2fc; font-weight:normal; background-color: transparent; padding: 0px; border: none;'`, + whisperStyle: `'background-color:transparent; color:#feda4a; display:block; border-width: 8px; border-style: solid; border-radius:5px; border-color:#feda4a; padding:15px; margin-top:10px;'`, + whisperbuttonstyle: `style='display:inline-block; color:#13f2fc; background-color: transparent;padding: 0px; border: none'`, + footer: "" + }, + + strange: { + boxcode: `
`, + titlecode: `
`, + textcode: "
", + buttonwrapper: `
`, + buttonstyle: `style='display:inline-block; color:#ff1515; background-color: transparent;padding: 0px; border: none'`, + playerbuttonstyle: `style='display:inline-block; color:#ff1515; font-family: "Goblin One"; font-weight:normal; font-size: 10px; background-color: transparent;padding: 0px; border: none;'`, + buttondivider: ``, + handoutbuttonstyle: `style='display:inline-block; color:#ff1515; font-family: "Goblin One"; font-weight:normal; font-size: 10px; background-color: transparent; padding: 0px; border: none;'`, + whisperStyle: `'background-color:##4f0606; color:#ff1515; display:block; border: 1px solid #000; box-shadow: 0 0 5px #ff1515; padding:5px; margin-top:10px'`, + whisperbuttonstyle: `style='display:inline-block; color:#bbb; background-color: transparent;padding: 0px; border: none'`, + footer: "" + }, + + gothic: { + boxcode: `
`, + titlecode: `
`, + textcode: `
`, + buttonwrapper: `
`, + buttonstyle: `style='display:inline-block; color:#ccc; background-color: transparent;padding: 0px; border: none'`, + playerbuttonstyle: `style='display:inline-block; color:#ccc; font-size:12px; font-weight:normal; background-color: transparent;padding: 0px; border: none;'`, + buttondivider: ``, + handoutbuttonstyle: `style='display:inline-block; color:#ccc; font-size:12px; font-weight:normal; background-color: transparent; padding: 0px; border: none;'`, + whisperStyle: `'background-color:#2b2130; color:#ddd; display:block; border-width: 1px; border-style: solid; border-color:#a3a681; padding:5px'`, + whisperbuttonstyle: `style='display:inline-block; color:#aaa; background-color: transparent;padding: 0px; border: none'`, + footer: "" + }, + + western: { + boxcode: `
`, + titlecode: `
`, + textcode: `
`, + buttonwrapper: `
`, + buttonstyle: `style='display:inline-block; color:#000; background-color: transparent;padding: 0px; border: none'`, + playerbuttonstyle: `style='display:inline-block; color:#7e2d40; background-color: transparent;padding: 0px; border: none'`, + buttondivider: ``, + handoutbuttonstyle: `style='display:inline-block; color:#7e2d40; background-color: transparent;padding: 0px; border: none'`, + whisperStyle: `'background-color:#382d1d; color:#ebcfa9; font-style: italic; display:block; border-width: 1px; border-style: solid; border-color:#a3a681; padding:5px; margin-top:5px'`, + whisperbuttonstyle: `style='display:inline-block; color:#fabe69; background-color: transparent;padding: 0px; border: none'`, + footer: "" + }, + + dragon: { + boxcode: `
`, + titlecode: `
`, + textcode: `
`, + buttonwrapper: `
`, + buttonstyle: `style='display:inline-block; color:#0e3365; background-color: transparent;padding: 0px; border: none'`, + playerbuttonstyle: `style='display:inline-block; color: #0e3365; font-size:14px; background-color: transparent;padding: 0px; border: none'`, + buttondivider: "  •  ", //``, + handoutbuttonstyle: `style='display:inline-block; color: #0e3365; font-size:14px; background-color: transparent;padding: 0px; border: none'`, + whisperStyle: `'display:block; border-width: 5px 0px 5px 0px; border-style: solid; border-color:#58170D; padding:5px; margin-top:9px;'`, + whisperbuttonstyle: `style='display:inline-block; color:#0e3365; background-color: transparent;padding: 0px; border: none'`, + footer: "" + }, + + + + wizard: { + boxcode: `
`, + titlecode: `
`, + textcode: `
`, + buttonwrapper: `
`, + buttonstyle: `style='display:inline-block; color:#58170D; background-color: transparent;padding: 0px; border: none'`, + playerbuttonstyle: `style='display:inline-block; color: #000; font-size:12px; background-color: transparent;padding: 0px; border: none'`, + buttondivider: "  •  ", //``, + handoutbuttonstyle: `style='display:inline-block; color: #000; font-size:12px; background-color: transparent;padding: 0px; border: none'`, + whisperStyle: `'background-color:#E0E5C1; color:#000; display:block; border-width: 1px; border-width: 1px 0px 1px 0px; border-style: solid; border-color:#58170D; padding:5px'`, + whisperbuttonstyle: `style='display:inline-block; color:#58170D; background-color: transparent;padding: 0px; border: none'`, + footer: "" + }, + +path: { + boxcode: `
`, + titlecode: `
`, + textcode: `
`, + buttonwrapper: `
`, + buttonstyle: `style='display:inline-block; color:#5e0000; font-weight:bold; background-color: transparent; padding: 0px; border: none'`, + playerbuttonstyle: `style='display:inline-block; color: #eee; font-size:12px; background-color: #5e0000; padding: 0px 4px 0px 4px; border-style:solid; border-width: 2px 4px 2px 4px; border-color: #d9c484; text-transformation: all-caps; font-family: "gin", impact, "Arial Bold Condensed", sans-serif;'`, + buttondivider: "    ", //``, + handoutbuttonstyle: `style='display:inline-block; color: #eee; font-size:12px; background-color: #5e0000; padding: 0px 4px 0px 4px; border-style:solid; border-width: 2px 4px 2px 4px; border-color: #d9c484; text-transformation: all-caps; font-family: "gin", impact, "Arial Bold Condensed", sans-serif;'`, + whisperStyle: `'background-color:#dbd1bc; color:#000; display:block; border-width: 1px; margin-top:15px; padding:5px; font-size: 15px; font-family: "Good OT", arial, sans-serif;'`, + whisperbuttonstyle: `style='display:inline-block; color:#58170D; background-color: transparent; font-weight:bold; padding: 0px; border: none'`, + footer: "" +}, + +apoc: { + boxcode: `
`, + titlecode: `
`, + textcode: `
`, + buttonwrapper: `
`, + buttonstyle: `style='display:inline-block; color:#555; background-color: transparent;padding: 0px; border: none'`, + playerbuttonstyle: `style='display:inline-block; color:#000; font-size:14px; font-weight:normal; background-color: transparent;padding: 0px; border: none;'`, + buttondivider: " / ", + handoutbuttonstyle: `style='display:inline-block; color:#000; font-size:14px; font-weight:normal; background-color: transparent; padding: 0px; border: none;'`, + whisperStyle: `'background-color:#403f3d; color:#ddd; display:block; padding:5px !important; margin:5px; font-family: "Shadows Into Light", Monaco,"Courier New", monospace !important; '`, + whisperbuttonstyle: `style='display:inline-block; color:#bbb; background-color: transparent;padding: 0px; border: none'`, + footer: `` + }, + + roman: { + boxcode: `
`, + titlecode: `
`, + textcode: `
`, + buttonwrapper: `
`, + buttonstyle: `style='display:inline-block; color:#7c6f39; font-weight: bold; background-color: transparent;padding: 0px; border: none'`, + playerbuttonstyle: `style='display:inline-block; color:#000; font-size:12px; font-weight:normal; background-color: transparent;padding: 0px; border: none;'`, + buttondivider: " | ", + handoutbuttonstyle: `style='display:inline-block; color:#000; font-size:12px; font-weight:normal; background-color: transparent; padding: 0px; border: none;'`, + whisperStyle: `'background-image: url(https://files.d20.io/images/459209597/cdZeKGAy2_NKcU1Wjkjeew/original.jpg); background-repeat: no-repeat; background-size: 100% 100%; background-color:#403f3d; color:#ddd; display:block; padding:8px !important; margin:5px 0px; text-shadow: none; line-height:16px;'`, + whisperbuttonstyle: `style='display:inline-block; color:#bbaa55; font-weight: bolder !important; background-color: transparent;padding: 0px; border: none'`, + footer: `` + }, + + notebook: { + boxcode: `
`, + titlecode: `
`, + textcode: `
`, + buttonwrapper: `
`, + buttonstyle: `style='display:inline-block; color: red; background-color: transparent;padding: 0px; border: none'`, + playerbuttonstyle: `style='display:inline-block; color:red; font-size:10px; font-weight:normal; background-color: transparent;padding: 0px; border: none;'`, + buttondivider: `/`, + handoutbuttonstyle: `style='display:inline-block; color:red; font-size:10px; font-weight:normal; background-color: transparent; padding: 0px; border: none;'`, + whisperStyle: `'color:red; display:block; padding-top:7px; font-family: "Patrick Hand", Monaco,"Courier New", monospace; line-height: 16px;'`, + whisperbuttonstyle: `style='display:inline-block; color:#333; background-color: transparent;padding: 0px; border: none'`, + footer: "" + }, + + steam: { + boxcode: `
`, + titlecode: `
`, + textcode: "
", + buttonwrapper: `
`, + buttonstyle: `style='display:inline-block; color:#056b20; background-color: transparent;padding: 0px; border: none'`, + playerbuttonstyle: `style='display:inline-block; color:#056b20; font-size:12px; font-weight:normal; background-color: transparent;padding: 0px; border: none;'`, + buttondivider: ``, + handoutbuttonstyle: `style='display:inline-block; color:#056b20; font-size:12px; font-weight:normal; background-color: transparent; padding: 0px; border: none;'`, + whisperStyle: `'background-color:#2b2130; color:#fbfcf0; display:block; border-width: 1px; border-style: solid; border-color:#a3a681; padding:5px'`, + whisperbuttonstyle: `style='display:inline-block; color:#fff; background-color: transparent;padding: 0px; border: none'`, + footer: "" + }, + + treasure: { + boxcode: `
`, + titlecode: `
`, + textcode: "
", + buttonwrapper: `
`, + buttonstyle: `style='display:inline-block; color:#8a4100; background-color: transparent;padding: 0px; border: none'`, + playerbuttonstyle: `style='display:inline-block; color:#634401; font-size:14px; font-weight:normal; background-color: transparent;padding: 0px; border: none;'`, + buttondivider: ``, + handoutbuttonstyle: `style='display:inline-block; color:#401e00; font-size:14px; font-weight:normal; background-color: transparent; padding: 0px; border: none;'`, + whisperStyle: `'background-color:#401e00; color:#eee; font-family: Tahoma, serif; display:block; border-width: 1px; border-style: solid; border-color:#a3a681; margin-top:10px;padding:5px'`, + whisperbuttonstyle: `style='display:inline-block; color:#e3b76f; background-color: transparent;padding: 0px; border: none'`, + footer: "" + }, + +choices: { + boxcode: `
`, + titlecode: `
`, + textcode: `

`, + buttonwrapper: `
`, + buttonstyle: `style='display:inline-block; color:#eee; hover: yellow; background-color: transparent;padding: 0px; border: none; '`, + playerbuttonstyle: `style='display:inline-block; color: #eee; font-size:16px; font-family: "Minion", "Minion Pro", serif; background-color: transparent;padding: 0px; border: none'`, + buttondivider: "  ◼  ", //``, + handoutbuttonstyle: `style='display:inline-block; color: #eee; font-size:16px; font-family: "Minion", "Minion Pro", serif; background-color: transparent;padding: 0px; border: none'`, + whisperStyle: `'background-image: linear-gradient(to bottom,#4b443d,#3f3732,#4b443d); background-color: transparent; color:#f8e8a6; display:block; border-width: 1px; border: 1px solid #4f4841; margin: 20px, -12px, 15px, -12px; padding:10px, 10px'`, + whisperbuttonstyle: `style='display:inline-block; color:#eee; background-color: transparent;padding: 0px; border: none'`, + footer: "" +}, +gate3: { + boxcode: `
`, + titlecode: `
`, + textcode: `
`, + buttonwrapper: `
`, + buttonstyle: `style='display:inline-block; color:#eada8d; background-color: transparent;padding: 0px; border: none; '`, + playerbuttonstyle: `style='display:inline-block; color: #eee; font-size:16px; font-family: "Minion", "Minion Pro", serif; background-color: transparent;padding: 0px; border: none'`, + buttondivider: "  ◼  ", //``, + handoutbuttonstyle: `style='display:inline-block; color: #eee; font-size:16px; font-family: "Minion", "Minion Pro", serif; background-color: transparent;padding: 0px; border: none'`, + whisperStyle: `'background-image: linear-gradient(to bottom,#4b443d,#3f3732,#4b443d); background-color: transparent; color:#f8e8a6; display:block; border-width: 1px; border: 1px solid #4f4841; margin: 20px, -12px, 15px, -12px; padding:10px, 10px'`, + whisperbuttonstyle: `style='display:inline-block; color:#eee; background-color: transparent;padding: 0px; border: none'`, + footer: "" +}, + + + crt: { + boxcode: `
`, + titlecode: `
`, + textcode: "
", + buttonwrapper: `
`, + buttonstyle: `style='display:inline-block; color:#fff; background-color: transparent;padding: 0px; border: none'`, + playerbuttonstyle: `style='display:inline-block;font-weight:bold; color:white; background-color: transparent;padding: 0px; border: none;font-size: 12px'`, + buttondivider: '|', + handoutbuttonstyle: `style='display:inline-block;font-weight:bold; color:white; background-color: transparent;padding: 0px; border: none;font-size: 12px'`, + whisperStyle: `'background-color:#2b2130; color:#fbfcf0; display:block; border-width: 1px; border-style: solid; border-color:#a3a681; padding:5px'`, + whisperbuttonstyle: `style='display:inline-block; color:#fff; background-color: transparent;padding: 0px; border: none'`, + footer: "" + }, + + news: { + boxcode: `
`, + titlecode: `
`, + textcode: `
`, + buttonwrapper: `
`, + buttonstyle: `style='display:inline-block; color:#222; text-decoration:underline; background-color: transparent;padding: 0px; border: none'`, + playerbuttonstyle: `style='display:inline-block;float:right; margin-top:5px; font-weight:bold; color:#444; background-color: transparent;padding: 0px; border: none;font-size: 12px'`, + buttondivider: ' ', + handoutbuttonstyle: `style='display:inline-block;float:left; margin-top:5px; font-weight:bold; color:#444; background-color: transparent;padding: 0px; border: none;font-size: 12px'`, + whisperStyle: `'background-color: rgba(0, 0, 0, 0.1); color:#444; font-size: 14px;font-family: arial, helvetica, sans-serif; padding:8px; display:block; border: 1px solid #444;'`, + whisperbuttonstyle: `style='display:inline-block; color:#444; text-decoration:underline; background-color: transparent; padding: 0px; border: none'`, + footer: "" + }, + + scroll: { + boxcode: `
`, + titlecode: `
`, + textcode: `
`, + buttonwrapper: `
`, + buttonstyle: `style='display:inline-block; color:#7e2d40; background-color: transparent;padding: 0px; border: none'`, + playerbuttonstyle: `style='display:inline-block; color:#7e2d40; background-color: transparent;padding: 0px; border: none'`, + buttondivider: ' | ', + handoutbuttonstyle: `style='display:inline-block; color:#7e2d40; background-color: transparent;padding: 0px; border: none'`, + whisperStyle: `'background-color:#58170d; color:#d9bf93; display:block; padding:5px'`, + whisperbuttonstyle: `style='display:inline-block; color:#fce5bb; background-color: transparent;padding: 0px; border: none'`, + footer: "" + }, + + scroll2: { + boxcode: `
`, + titlecode: `
`, + textcode: `
`, + buttonwrapper: `
`, + buttonstyle: `style='display:inline-block; color:#58170D; background-color: transparent;padding: 0px; border: none'`, + playerbuttonstyle: `style='display:inline-block; font-size: 14px !important; color:#58170D; background-color: transparent;padding: 0px; border: none'`, + buttondivider: ' | ', + handoutbuttonstyle: `style='display:inline-block; font-size: 14px !important; color:#58170D; background-color: transparent;padding: 0px; border: none'`, + whisperStyle: `'background-color:#241605; color:#eee; box-shadow: 0px 0px 5px 5px #241605; display:block; border-radius:15px; padding:5px; margin: 15px 5px 10px 5px'`, + whisperbuttonstyle: `style='display:inline-block; color:#fcdd6d; background-color: transparent;padding: 0px; border: none'`, + footer: `` + }, + + vault: { + boxcode: `
`, + titlecode: `
`, + textcode: `
`, + buttonwrapper: `
`, + buttonstyle: `style='display:inline-block; color:#111; text-decoration: underline; background-color: transparent;padding: 0px; border: none'`, + playerbuttonstyle: `style='display:inline-block; font-size: 15px !important; color:#fef265; text-shadow: 2px 2px 2px #111; background-color: transparent;padding: 0px; border: none'`, + buttondivider: `   `, + handoutbuttonstyle: `style='display:inline-block; font-size: 15px !important; color:#fef265; text-shadow: 2px 2px 2px #111;background-color: transparent;padding: 0px; border: none'`, + whisperStyle: `'background-color: #transparent; background-image: url(https://files.d20.io/images/459209469/UA2E7Vyf-kncA8k1jUuyAg/original.png; color:#111; display:block; text-shadow: none; text-align:center; font-family: "Contrail One"; border-radius:3px; padding:5px; margin: 15px -20px 10px -20px'`, + whisperbuttonstyle: `style='display:inline-block; color:#284a73; background-color: transparent;padding: 0px; border: none'`, + footer: `` + }, + + osrblue: { + boxcode: `
`, + titlecode: `
`, + textcode: `
`, + buttonwrapper: `
`, + buttonstyle: `style='display:inline-block !important; color:#333; text-decoration: underline; background-color: transparent;padding: 0px; border: none'`, + playerbuttonstyle: `style='display:inline-block; font-size: 14px !important; color:#333; text-decoration: underline; background-color: transparent;padding: 0px; border: none'`, + buttondivider: `|`, + handoutbuttonstyle: `style='display:inline-block; font-size: 14px !important; color:#333; text-decoration: underline; background-color: transparent;padding: 0px; border: none'`, + whisperStyle: `'background-color: #729aa5; color:#eee; display:block; text-align:center; font-family: "Arial"; padding:5px; margin: 15px -20px 10px -20px'`, + whisperbuttonstyle: `style='display:inline-block; color:#eee; text-decoration: underline; background-color: transparent;padding: 0px; border: none'`, + footer: `` + } + +}; + +on('ready', function() { + if (!_.has(state, 'Supernotes')) { + state.Supernotes = { + sheet: 'Default', + template: 'default', + title: 'name', + theText: '', + sendToPlayers: true, + makeHandout: true, + darkMode: false + }; + message = 'Welcome to Supernotes! If this is your first time running it, the script is set to use the Default Roll Template. You can choose a different sheet template below, as well as decide whether you want the script to display a "Send to Players" footer at the end of every GM message. It is currently set to true.

[Default Template - any sheet](!gmnote --config|default)
[D&D 5th Edition by Roll20](!gmnote --config|dnd5e)
[DnD 5e Shaped](!gmnote --config|5eshaped)
[Pathfinder by Roll20](!gmnote --config|pfofficial)
[Pathfinder Community](!gmnote --config|pfcommunity)
[Pathfinder 2e by Roll20](!gmnote --config|pf2e)
[Starfinder by Roll20](!gmnote --config|starfinder)
[Call of Cthulhu 7th Edition by Roll20](!gmnote --config|callofcthulhu)

[Toggle Send to Players](!gmnote --config|sendtoPlayers)'; + sendChat('Supernotes', '/w gm &{template:' + state.Supernotes.template + '}{{' + state.Supernotes.title + '=' + 'Config' + '}} {{' + state.Supernotes.theText + '=' + message + '}}'); + } +}); + +on('ready', () => { + + + /* ========================================================= + * Supernotes Help Handout Builder + * ========================================================= */ + +const buildSupernotesHelp = () => { + + const HANDOUT_NAME = "Help: Supernotes"; + const HANDOUT_AVATAR = "https://files.d20.io/images/470559564/QxDbBYEhr6jLMSpm0x42lg/original.png?1767857147"; // change if desired + +const helpHtml = ` +

Supernotes

+

Documentation for v.${version}

+ +
+ +

Overview

+ +

+Supernotes pulls content from a token’s GM Notes field and from other character fields not normally accessible to macros. +If a token represents a character, you may retrieve: +

+ +
    +
  • Character GM Notes
  • +
  • Character Bio
  • +
  • Character Avatar
  • +
  • Bio images (single, indexed, or all)
  • +
  • Token tooltip
  • +
  • Token image
  • +
+ +

+Notes may be whispered to the GM, sent to all players, whispered to the sender, or written directly to a named handout. +A footer button may optionally appear on GM whispers, allowing the note to be forwarded to players. +

+ +

+Images, API command buttons, links, markdown image syntax [x](imageURL), and most special characters pass through correctly in both chat and handouts. +

+ + +

Special Control Character for Inline GMnotes

+-----

+

Five dashes placed in the gmnotes of a token indicate that any following content is trested as gm-only text when sent to chat. +

+ +
+ +

Commands

+ +

!gmnote +Whispers note to GM.

+ +

!pcnote +Sends note to all players.

+ +

!selfnote +Whispers note to the command sender.

+ +
+ +

Parameters

+ +

Sources

+
    +
  • --token +Pull from selected token GM Notes (default). Token does not require a character.
  • + +
  • --charnote +Pull from represented character GM Notes.
  • + +
  • --bio +Pull from character Bio field.
  • + +
  • --avatar +Return character Avatar image.
  • + +
  • --image +Return first Bio image.
  • + +
  • --images +Return all Bio images.
  • + +
  • --image[number] +Return indexed Bio image (e.g. --image1, --image2).
  • + +
  • --tooltip +Return selected token tooltip.
  • + +
  • --tokenimage +Return selected token image.
  • + +
  • --card +Return token image and gmnotes in one report.
  • + +
+ +

Options

+ +
    +
  • --notitle +Suppress title in chat output. May be added to any command in any order.
  • + +
  • --idTOKENID +Read notes from specific token ID. No space after --id. Example: !gmnote --id-1234567890abcdef
  • + +
  • --handout|Handout Name| +Send output to named handout instead of chat. +Creates the handout if it does not exist. +Content above the automatic horizontal rule remains persistent.
  • + + +
  • --help +Displays help.
  • + +
  • --config +Opens configuration dialog.
  • +
+ +
+ +

Examples

+ +
!pcnote --bio
+

Sends selected character Bio to all players.

+ +
!gmnote --charnote
+

Whispers character GM Notes to GM.

+ +
!pcnote --image --notitle
+

Sends first image without revealing title.

+ +
+ +

Templates

+ +

+Add a template using: +

+ +
--template|templatename
+ +

+Example: +

+ +
!gmnote --template|crt
+!pcnote --template|notebook --bio
+!pcnote --template|faraway --tokenimage
+ +

+All templates include inline buttons and support Send to Players and Make Handout. +Handouts use Roll20’s native styling for cross-platform reliability. +

+ +
+ +

Available Templates

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
generic
Just the facts, Ma'am. Nothing fancy here.
dark
As previous, but in reverse.
crt
Retro greenscreen for hacking and cyberpunk. Or for reports on that xenomorph hiding on your ship.
notebook
You know, for kids. Who like to ride bikes. Maybe they attend a school and solve mysteries.
gothic
Classic noire horror for contending with Universal monsters or maybe contending with elder gods.
apoc
Messages scrawled on a wall. Crumbling and ancient, like the world that was.
scroll
High fantasy. Or low fantasy—we don't judge.
scroll2
An alternative to scroll, thats even scrollier.
lcars
For opening hailing frequencies and to boldly split infinitives that no one has split before!
faraway
No animated title crawl, but still has that space wizard feel.
steam
Gears and brass have changed my life.
western
Return with us now to those thrilling days of yesteryear.
dragon
Three-fivey style
wizard
A fifth edition of templates.
strange
Other kids who ride bikes and play D&D.
gate3
For folks who like the GOTY based on D&D.
choices
A second gate-y style, suitable for for the same crowd.
roll20light
for when you want your notes to have the feeling of authority
roll20dark
As before, but.... dark
news
Extra! Extra! Read all about it! The ink bleeds through from the other side of the newsprint.
treasure
For listing all that loot.
vault
A comforting style for sheltered people.
path
A style that works well with PF2 Adventure Paths
osrblue
Gygax-approved. Maybe. The graph paper even has yellowed edges
roman
Hail Caesar!
+ +
+ +

Configuration

+ +

+On installation, Supernotes defaults to the Default roll template. +The configuration dialog allows you to: +

+ +
    +
  • Select a sheet roll template
  • +
  • Toggle the “Send to Players” footer button
  • +
+ +

+Supported sheet templates include: +

+ +
    +
  • Default Template
  • +
  • D&D 5th Edition by Roll20
  • +
  • 5e Shaped
  • +
  • Pathfinder by Roll20
  • +
  • Pathfinder Community
  • +
  • Pathfinder 2e by Roll20
  • +
  • Starfinder
  • +
+ +
+ +

Troubleshooting

+ +

+If you experience template issues or configuration problems, you may use the buttons below to restore default behavior or re-open the configuration dialog. +

+ + + +

+Restore Default Template resets Supernotes to the Default roll template.
+Re-Run Configuration opens the configuration dialog to select a sheet template and toggle footer options. +

+ +`; + + + // Find existing handout + let handout = findObjs({ + _type: "handout", + name: HANDOUT_NAME + })[0]; + + // Create if missing + if (!handout) { + handout = createObj("handout", { + name: HANDOUT_NAME, + archived: false + }); + } + + // Always overwrite content + avatar + handout.set({ + notes: helpHtml, + avatar: HANDOUT_AVATAR + }); + + const link = `http://journal.roll20.net/handout/${handout.get("_id")}`; + + const box = + `
` + + `
Supernotes Help
` + + `Open Help Handout` + + `
`; + + sendChat("Supernotes", `/w gm ${box}`, null, { noarchive: true }); +}; + + + function parseMarkdown(markdownText) { + const htmlText = markdownText + .replace(/^### (.*$)/gim, '

$1

') + .replace(/^## (.*$)/gim, '

$1

') + .replace(/^# (.*$)/gim, '

$1

') + .replace(/^\> (.*$)/gim, '
$1
') + .replace(/\*\*(.*)\*\*/gim, '$1') + .replace(/\*(.*)\*/gim, '$1') + .replace(/!\[(.*?)\]\((.*?)\)/gim, "$1") + .replace(/\[(.*?)\]\((.*?)\)/gim, "$1") + .replace(/\n$/gim, '
') + + return htmlText.trim() + } + +function cleanText(text,buttonStyle){ + text = ((undefined !== text) ? text.replace(/\[([^\]]*?)\]\(([^\)]*?)\)(?$1").replace(/

/gm, "").replace(/<\/p>/gm, "
").replace("padding:5px'>

", "padding:5px'>") : ""); + text = text.replace(' + .replace(/\r?\n+/g, "
") + // Normalize mixed
,
,
variations to
+ .replace(/<\s*br\s*\/?\s*>/gi, "
") + // Remove accidental duplicate


etc + .replace(/(
\s*){2,}/g, "
") + .trim(); + +return text; +} + + + + const decodeUnicode = (str) => str.replace(/%u[0-9a-fA-F]{2,4}/g, (m) => String.fromCharCode(parseInt(m.slice(2), 16))); + + const version = '0.2.6'; + log('Supernotes v' + version + ' is ready! --offset ' + API_Meta.Supernotes.offset + 'To set the template of choice or to toggle the send to players option, Use the command !gmnote --config'); +//Changelong +// 0.2.6 Reworked and updated Help system to use handout. Fixed logic issue Card output. +// 0.2.5 fixed trailing space problem in command line, fixed linebreak issue. + + + + + on('chat:message', function(msg) { + if ('api' === msg.type && msg.content.match(/^!(gm|pc|self)note\b/)) { + let match = msg.content.match(/^!gmnote-(.*)$/); +let selectedObject = msg.selected; + +//################## EXPERIMENTAL TO GET TOKEN ID FROM SUPPLIED VALUE +if(msg.content.includes("--token|")){ + virtualTokenID = msg.content.split(/--token\|/)[1].split(/\s/)[0]; +sendChat ("notes","success. Virtual token id is " + virtualTokenID); + if (virtualTokenID.length !== 20 && virtualTokenID.charAt(0) !== "-"){ + sendChat ("notes","this is not a token id :" + virtualTokenID); + sendChat ("notes","player page id :" + Campaign().get("playerpageid")); + + selectedObject = findObjs({ + _type: "graphic", + _id: virtualTokenID, + }); + log ("selectedObject is " + selectedObject); + // selectedObject = theToken[0]; + } + if (selectedObject){ + sendChat ("notes", "number of 'selected' objects is " +selectedObject.length); + } else{ + sendChat ("notes", "no passed value"); + } +//sendChat ("notes","virtual ID is " + selectedObject[0].get("_id")); +} +//################## EXPERIMENTAL TO GET TOKEN ID FROM SUPPLIED VALUE + + + + + + + + //define command + let command = msg.content.split(/\s+--/)[0]; + let sender = msg.who; + let senderID = msg.playerid; + + let isGM = playerIsGM(senderID); + let messagePrefix = '/w gm '; + if (command === '!pcnote') { + messagePrefix = ''; + } + + if (command === '!selfnote') { + messagePrefix = '/w ' + sender + ' '; + } + + let secondOption = ''; + let args = msg.content.trim().split(/\s+--/); + + let customTemplate = ''; + let option = ''; + let notitle = false; + let id = ''; + let tokenImage = ''; + let tooltip = ''; + let tokenName = ''; + let trueToken = []; + let tokenID = ''; + let handoutTitle = ''; + let whisper = ''; + + let templates = Supernotes_Templates; + + + + + function sendMessage(whom, messagePrefix, template, title, theText, message, tokenID, playerButton, handoutButton) { + handoutButton = ((handoutButton) ? handoutButton.replace(/NamePlaceholder/, whom) : handoutButton); + + if (message === "" && option.match(/^(bio|charnote|token|tooltip)/)) { + message = `The information does not exist for the ${option} option` + } + + if (handoutTitle === '') { + //Crops out GM info on player messages + if (isGM) { + //message = (message.includes("-----") ? message.split('-----')[0] + "
" + message.split('-----')[1] + "
" : message); + whisper = (message.includes("-----") ? message.split('-----')[1] : ""); + message = (message.includes("-----") ? message.split('-----')[0] : message); + + } + + if (customTemplate.length > 0) { + let chosenTemplate = templates.generic; + switch (customTemplate) { + case "crt": + chosenTemplate = templates.crt; + break; + case "dark": + chosenTemplate = templates.dark; + break; + case "roll20light": + chosenTemplate = templates.roll20light; + break; + case "roll20dark": + chosenTemplate = templates.roll20dark; + break; + case "scroll": + chosenTemplate = templates.scroll; + break; + case "scroll2": + chosenTemplate = templates.scroll2; + break; + case "vault": + chosenTemplate = templates.vault; + break; + case "osrblue": + chosenTemplate = templates.osrblue; + break; + case "lcars": + chosenTemplate = templates.lcars; + break; + case "faraway": + chosenTemplate = templates.faraway; + break; + case "strange": + chosenTemplate = templates.strange; + break; + case "gothic": + chosenTemplate = templates.gothic; + break; + case "western": + chosenTemplate = templates.western; + break; + case "dragon": + chosenTemplate = templates.dragon; + break; + case "wizard": + chosenTemplate = templates.wizard; + break; + case "path": + chosenTemplate = templates.path; + break; + case "treasure": + chosenTemplate = templates.treasure; + break; + case "steam": + chosenTemplate = templates.steam; + break; + case "gate3": + chosenTemplate = templates.gate3; + break; + case "choices": + chosenTemplate = templates.choices; + break; + case "apoc": + chosenTemplate = templates.apoc; + break; + case "news": + chosenTemplate = templates.news; + break; + case "roman": + chosenTemplate = templates.roman; + break; + case "notebook": + chosenTemplate = templates.notebook; + break; + case "bob": + break; + default: + chosenTemplate = templates.generic; + // code block + } + + + + + playerButton = playerButton.split('\n')[1]; + + playerButton = ((undefined !== playerButton) ? playerButton.replace(/\[(.*?)\]\((.*?)\)/gim, "
$1") : ""); + handoutButton = ((undefined !== handoutButton) ? handoutButton.replace(/\[(.*?)\]\((.*?)\)/gim, "$1").replace(" | 0) ? "
" + whisper + "
" : ""); + + +message = cleanText(message,chosenTemplate.buttonstyle); +//the following lines attempt to account for numerous Roll20 CSS and HTML oddities. +whisper = cleanText(whisper,chosenTemplate.whisperbuttonstyle); +whisper= whisper.replace(/<\/span>
/i,"") +.replace(/
/i,'') +.replace(/

/i,'

') +.replace(/(

|<\/p>)/,'') +.replace(/>
/i,'>'); + + + + + + +// message = ((undefined !== message) ? message.replace(/\[([^\]]*?)\]\(([^\)]*?)\)(?$1").replace(/

/gm, "").replace(/<\/p>/gm, "
").replace("padding:5px'>

", "padding:5px'>") : ""); +// message = message.replace('\n

$1") : ""); + handoutButton = ((undefined !== handoutButton) ? handoutButton.replace(/\[([^\]]*?)\]\(([^\)]*?)\)(?$1") : ""); +whisper = ((whisper.length>0) ? "
" + whisper + "
" : ""); +//log ("whisper = " + whisper); + return sendChat(whom, messagePrefix + '&{template:' + template + '}{{' + title + '=' + whom + '}} {{' + theText + '=' + message + whisper + playerButton + handoutButton + '}}'); + } + + } else { + let noteHandout = findObjs({ + type: 'handout', + name: handoutTitle + }); + noteHandout = noteHandout ? noteHandout[0] : undefined; + + if (!noteHandout) { + noteHandout = createObj('handout', { + name: handoutTitle, + archived: false, + inplayerjournals: "", + controlledby: "" + }); + let noteHandoutid = noteHandout.get("_id"); + sendChat('Supernotes', `/w gm Supernotes has created a handout named ${handoutTitle}.
Click here to open.`, null, { + noarchive: true + }); + } + if (noteHandout) { + + playerButton = '
Send to Players in Chat'; + if (makeHandout) { + handoutButton = ((playerButton) ? ' | ' : '
') + 'Make Handout'; + } + message = message.replace(/\[.*?\]\((.*?\.(jpg|jpeg|png|gif))\)/g, ``); + message = message.replace(/\[(.*?)\]\((.*?)\)/g, '$1'); + message = message.replace(//g, `\(\d*\)/)) { + let reportCount= notes.match(/(?<=\()\d+/);; +//log ("reportCount = " + reportCount); + +let newHeight = reportCount * 20; +if (newHeight > 500){newHeight = 500}; +if (newHeight < 200){newHeight = 200}; +//log ("newHeight = " + newHeight); +message = message.replace(/201px/,newHeight+'px'); + + } +//##############TEST FOR VARIABLE IMAGE HEIGHT BASED ON HEIGHT OF REPORT################################################### + + + if (notes.includes('')) { + if (notes.includes('!report')) { + notes = notes.split('')[0] + ''; + } else { + notes = notes.split(/
/i)[0] + ''; + } + } else { + playerButton = ''; + handoutButton = ''; + notes = ''; //'; + } + /*if (notes.includes('     ')) { + notes = notes.split('     ')[0] + '     ' + } else { + notes = '     ' + }*/ + //message = '
' + message +'
'; + + noteHandout.set("gmnotes", gmnote); + noteHandout.set("notes", notes + "

" + whom + "

" + message + playerButton + handoutButton) + //THIS NEEDS A TOGGLE + //if(!tokenImage.includes("marketplace")){noteHandout.set("avatar", tokenImage+"?12345678")} + }) + } else { + sendChat('Supernotes', whom + `No handout named ${handoutTitle} was found.`, null, { + noarchive: true + }, ) + } + + } + + } + + let theToken = selectedObject; + + args.forEach(a => { + if (a === 'notitle') { + notitle = true + } + if (a.includes('id-')) { + id = a.split(/id/)[1] + } + if (a.match(/handout\|.*?\|/)) { + handoutTitle = a.match(/handout\|.*?\|/).toString().split('|')[1] + } + if (a !== command && !(a.includes('id-')) && !(a.includes('handout|')) && a !== 'notitle') { + option = a + } + if (a.includes('template|')) { + customTemplate = a.split(/\|/)[1] + } + + }); + + ((id) ? theToken = [{ + "_id": id, + "type": "graphic" + }] : theToken = selectedObject); + + + if (undefined !== theToken) { + trueToken = getObj('graphic', theToken[0]._id); + tokenImage = trueToken.get('imgsrc'); + tokenTooltip = trueToken.get('tooltip'); + tokenName = trueToken.get('name'); + tokenID = trueToken.get('_id'); + } + + + + const template = state.Supernotes.template; + const title = state.Supernotes.title; + const theText = state.Supernotes.theText; + const sendToPlayers = state.Supernotes.sendToPlayers; + const makeHandout = state.Supernotes.makeHandout || false; + const darkMode = state.Supernotes.darkMode || false; + const whisperStyle = ((darkMode) ? `'background-color:#2b2130; color:#fbfcf0; display:block; border-width: 1px; border-style: solid; border-color:#a3a681; padding:5px'` : `'background-color:#fff; color:#000; display:block; border-width: 1px; border-style: solid; border-color:#a3a681; padding:5px'`); + + const whisperColor = ((darkMode) ? "#2b2130" : "#fbfcf0"); + const whisperTextColor = ((darkMode) ? "#fff" : "#000"); + const buttonstyle = ((darkMode) ? `style='display:inline-block; color:#a980bd; font-size: 0.9em; background-color: transparent;padding: 0px; border: none'` : `style='display:inline-block; color:#ce0f69; font-size: 0.9em; background-color: transparent;padding: 0px; border: none'`); + + + + + if (option !== undefined && option.includes('config')) { + let templateChoice = option.split('|')[1] + + if (templateChoice === undefined) { + message = 'Current sheet template:
' + state.Supernotes.sheet + '
Send to Players:
' + state.Supernotes.sendToPlayers + '

Choose a template for Supernotes to use.

[Default Template - any sheet](!gmnote --config|default)
[D&D 5th Edition by Roll20](!gmnote --config|dnd5e)
[DnD 5e Shaped](!gmnote --config|5eshaped)
[Pathfinder Community](!gmnote --config|pfcommunity)
[Pathfinder by Roll20](!gmnote --config|pfofficial)
[Pathfinder 2e by Roll20](!gmnote --config|pf2e)
[Starfinder by Roll20](!gmnote --config|starfinder)
[Call of Cthulhu 7th Edition by Roll20](!gmnote --config|callofcthulhu)

[Toggle Send to Players](!gmnote --config|sendtoPlayers)
[Toggle Make Handout button](!gmnote --config|makeHandout)
[Toggle Darkmode](!gmnote --config|darkMode)' + sendChat('Supernotes', messagePrefix + '&{template:' + template + '}{{' + title + '=' + 'Config' + '}} {{' + theText + '=' + message + '}}'); + } + + + switch (templateChoice) { + case 'default': + state.Supernotes.sheet = 'Default'; + state.Supernotes.template = 'default'; + state.Supernotes.title = 'name'; + state.Supernotes.theText = ''; + sendChat('Supernotes', '/w gm Supernotes set to Default roll template'); + break; + case 'dnd5e': + state.Supernotes.sheet = 'D&D 5th Edition by Roll20'; + state.Supernotes.template = 'npcaction'; + state.Supernotes.title = 'rname'; + state.Supernotes.theText = 'description'; + sendChat('Supernotes', '/w gm Supernotes set to ' + state.Supernotes.sheet); + break; + case '5eshaped': + state.Supernotes.sheet = 'DnD 5e Shaped'; + state.Supernotes.template = '5e-shaped'; + state.Supernotes.title = 'title'; + state.Supernotes.theText = 'text_big'; + sendChat('Supernotes', '/w gm Supernotes set to ' + state.Supernotes.sheet); + break; + case 'pfcommunity': + state.Supernotes.sheet = 'Pathfinder Community'; + state.Supernotes.template = 'pf_generic'; + state.Supernotes.title = 'name'; + state.Supernotes.theText = 'description'; + sendChat('Supernotes', '/w gm Supernotes set to ' + state.Supernotes.sheet); + break; + case 'pfofficial': + state.Supernotes.sheet = 'Pathfinder by Roll20'; + state.Supernotes.template = 'npc'; + state.Supernotes.title = 'name'; + state.Supernotes.theText = 'descflag=1}} {{desc'; + sendChat('Supernotes', '/w gm Supernotes set to ' + state.Supernotes.sheet); + break; + case 'pf2e': + state.Supernotes.sheet = 'Pathefinder 2e'; + state.Supernotes.template = 'rolls'; + state.Supernotes.title = 'header'; + state.Supernotes.theText = 'notes_show=[[1]]}} {{notes'; + sendChat('Supernotes', '/w gm Supernotes set to ' + state.Supernotes.sheet); + break; + case 'starfinder': + state.Supernotes.sheet = 'Starfinder'; + state.Supernotes.template = 'sf_generic'; + state.Supernotes.title = 'title'; + state.Supernotes.theText = 'buttons0'; + sendChat('Supernotes', '/w gm Supernotes set to ' + state.Supernotes.sheet); + break; + case 'callofcthulhu': + state.Supernotes.sheet = 'Call of Cthulhu 7th Edition by Roll20'; + state.Supernotes.template = 'callofcthulhu'; + state.Supernotes.title = 'title'; + state.Supernotes.theText = 'roll_bonus'; + sendChat('Supernotes', '/w gm Supernotes set to ' + state.Supernotes.sheet); + break; + case 'sendtoPlayers': + if (state.Supernotes.sendToPlayers) { + state.Supernotes.sendToPlayers = false + } else { + state.Supernotes.sendToPlayers = true + }; + sendChat('Supernotes', '/w gm Send to Players set to ' + state.Supernotes.sendToPlayers); + break; + case 'makeHandout': + if (state.Supernotes.makeHandout) { + state.Supernotes.makeHandout = false + } else { + state.Supernotes.makeHandout = true + }; + sendChat('Supernotes', '/w gm Make Handout button set to ' + state.Supernotes.makeHandout); + break; + case 'darkMode': + if (state.Supernotes.darkMode) { + state.Supernotes.darkMode = false + } else { + state.Supernotes.darkMode = true + }; + sendChat('Supernotes', '/w gm darkMode set to ' + state.Supernotes.darkMode); + break; + } + } else { + if (option !== undefined && option.includes('help')) { + buildSupernotesHelp(); + return; + } else { + if (!(option + '').match(/^(card|bio|charnote|tokenimage|tooltip|avatar|imag(e|es|e[1-9]))/)) { + option = 'token'; + } + + let playerButton = ''; + if (sendToPlayers && (command === '!gmnote' || command === '!selfnote')) { + playerButton = '\n[Send to Players](' + msg.content.replace(/!(gm|self)/, "!pc") + ')'; + } + + let handoutButton = ''; + if (makeHandout && (command.includes('gmnote') || command.includes('selfnote'))) { + handoutButton = ((playerButton) ? ' | ' : '
') + '[Make Handout](' + msg.content.replace(/!(pc|self)/, "!gm") + ' --handout|NamePlaceholder|)'; + } else { + //handoutButton = '\n[Make Handout](' + msg.content.replace(/!(pc|self)/, "!gm") +')'; + + } + + let regex; + if (match && match[1]) { + regex = new RegExp(`^${match[1]}`, 'i'); + } + + let message = ''; + let whom = ''; + + + +if (option === 'card') { + + (theToken || []).forEach(sel => { + + const o = getObj('graphic', sel._id); + if (!o) return; + + const tokenID = o.id; + const tokenName = o.get('name') || ''; + const rawGM = o.get('gmnotes') || ''; + + // Always assign whom deterministically + whom = tokenName; + + // Decode GM notes safely + let decodedGM = rawGM ? unescape(decodeUnicode(rawGM)) : ''; + + // Apply regex filtering if present + if (decodedGM && regex) { + decodedGM = _.filter( + decodedGM.split(/(?:[\n\r]+|)/), + l => regex.test(l) + ).join('\r'); + } + + message = decodedGM || ''; + + // Crop GM-only content for player/self notes + if (command === '!pcnote' || command === '!selfnote') { + if (message.includes("-----")) { + message = message.split('-----')[0]; + } + } + + // Apply notitle + if (notitle) { + whom = ''; + } + + // Inject token image if message isn't an image URL + if (!/\.(png|jpg|jpeg|gif)/i.test(message)) { + + let styledTokenImage = ``; + + if (!message) { + message = `

`; + } + + message = styledTokenImage + message; + } + + sendMessage( + whom, + messagePrefix, + template, + title, + theText, + message, + tokenID, + playerButton, + handoutButton + ); + + }); + + } else { + if (option === 'tooltip') { + (theToken || []) + .map(o => getObj('graphic', o._id)) + .filter(g => undefined !== g) + .map(t => getObj('character', t.get('represents'))) + .filter(c => undefined !== c) + .forEach(c => { + message = tokenTooltip; + whom = tokenName; + if (notitle) { + whom = ''; + } + sendMessage(whom, messagePrefix, template, title, theText, message, tokenID, playerButton, handoutButton); + }); + } else { + if (option === 'tokenimage') { + (theToken || []) + .map(o => getObj('graphic', o._id)) + .filter(g => undefined !== g) + /* .map(t => getObj('character', t.get('represents')))*/ + .filter(c => undefined !== c) + .forEach(c => { + message = ""; + whom = tokenName; + if (notitle) { + whom = ''; + } + sendMessage(whom, messagePrefix, template, title, theText, message, tokenID, playerButton, handoutButton); + }); + } else { + if (option === 'avatar') { + (theToken || []) + .map(o => getObj('graphic', o._id)) + .filter(g => undefined !== g) + .map(t => getObj('character', t.get('represents'))) + .filter(c => undefined !== c) + .forEach(c => { + message = ""; + whom = c.get('name'); + if (notitle) { + whom = ''; + } + sendMessage(whom, messagePrefix, template, title, theText, message, tokenID, playerButton, handoutButton); + }); + } else { + + if (option.match(/^imag(e|es|e[1-9])/)) { + + + (theToken || []) + .map(o => getObj('graphic', o._id)) + .filter(g => undefined !== g) + .map(t => getObj('character', t.get('represents'))) + .filter(c => undefined !== c) + .forEach(c => c.get('bio', (val) => { + if (null !== val && 'null' !== val && val.length > 0) { + if (regex) { + message = _.filter( + decodeUnicode(val).split(/(?:[\n\r]+|)/), + (l) => regex.test(l.replace(/<[^>]*>/g, '')) + ).join('\r'); + message = message.replace("/g); + if (artwork === null) { + artwork = 'No artwork exists for this character. Consider specifiying avatar.' + }; + + } else { + artwork = message.match(/\<.* src.*?\>/g); + artwork = String(artwork); + if (artwork === null) { + artwork = 'No artwork exists for this character. Consider specifiying avatar.' + }; + + + imageIndex = option.match(/\d+/g); + + + if (isNaN(imageIndex) || !imageIndex) { + imageIndex = 1 + } + + if (imageIndex > (artwork.split(",")).length) { + imageIndex = 1 + } + + imageIndex = imageIndex - 1; //corrects from human readable + + artwork = artwork.split(",")[imageIndex]; + + } + if (('' + artwork).length > 3) { + message = artwork; + } else { + message = 'No artwork exists for this character.'; + } + if (artwork === "null" || message === "null") { + message = 'No artwork exists for this character. Consider specifiying avatar.' + }; + + whom = c.get('name'); + + //Sends the final message + if (notitle) { + whom = ''; + } + sendMessage(whom, messagePrefix, template, title, theText, message, tokenID, playerButton, handoutButton); + + } + })); + } else { + + + + if ((option === 'bio') || (option === 'charnote')) { + let suboption = (option === 'charnote') ? 'gmnotes' : 'bio'; + + (theToken || []) + .map(o => getObj('graphic', o._id)) + .filter(g => undefined !== g) + .map(t => getObj('character', t.get('represents'))) + .filter(c => undefined !== c) + .forEach(c => c.get(suboption, (val) => { + if (null !== val && 'null' !== val && val.length > 0) { + if (regex) { + message = _.filter( + decodeUnicode(val).split(/(?:[\n\r]+|)/), + (l) => regex.test(l.replace(/<[^>]*>/g, '')) + ).join('\r'); + } else { + message = decodeUnicode(val); + } + whom = c.get('name'); + //Crops out GM info on player messages + if (command === '!pcnote' || command === '!selfnote') { + message = (message.includes("-----") ? message.split('-----')[0] : message); + } + //Sends the final message + if (notitle) { + whom = ''; + } + sendMessage(whom, messagePrefix, template, title, theText, message, tokenID, playerButton, handoutButton); + + } else { + if (notitle) { + whom = '' + } + message = `The information does not exist for the ${option} option`; + sendMessage(whom, messagePrefix, template, title, theText, message, tokenID, playerButton, handoutButton); + + } + })); + } else { + (theToken || []) + .map(o => getObj('graphic', o._id)) + .filter(g => undefined !== g) + .filter((o) => { + const gm = (o && o.get) ? o.get('gmnotes') : ''; + return !!(gm && gm.length > 0); +}) + .forEach(o => { + if (regex) { + message = _.filter(unescape(decodeUnicode(o.get('gmnotes'))).split(/(?:[\n\r]+|)/), (l) => regex.test(l)).join('\r'); + } else { + message = unescape(decodeUnicode(o.get('gmnotes'))); + } + whom = o.get('name'); + + }); + + //Crops out GM info on player messages + if (command === '!pcnote' || command === '!selfnote') { + message = (message.includes("-----") ? message.split('-----')[0] : message); + } + + //Sends the final message + if (notitle) { + whom = ''; + } + sendMessage(whom, messagePrefix, template, title, theText, message, tokenID, playerButton, handoutButton); + + } + + /* Log Block. Turn on for debugging + [ + `### REPORT###`, + `THE MESSAGE =${message}`, + `command = ${command}`, + // `option = ${option}`, + `secondOption = ${secondOption}`, + `messagePrefix = ${messagePrefix}`, + `whom = ${whom}`, + `message =${message}` + ].forEach(m => log(m)); + */ + } + } + } + } + } + } + } + } + }); +}); + +{ try { throw new Error(''); } catch (e) { API_Meta.Supernotes.lineCount = (parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/, '$1'), 10) - API_Meta.Supernotes.offset); } } diff --git a/Supernotes/Supernotes.js b/Supernotes/Supernotes.js index 08b4c15a1..3b8bcec52 100644 --- a/Supernotes/Supernotes.js +++ b/Supernotes/Supernotes.js @@ -1,14 +1,7 @@ -var API_Meta = API_Meta || {}; -API_Meta.Supernotes = { - offset: Number.MAX_SAFE_INTEGER, - lineCount: -1 -}; { - try { - throw new Error(''); - } catch (e) { - API_Meta.Supernotes.offset = (parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/, '$1'), 10) - (7)); - } -} +var API_Meta = API_Meta||{}; +API_Meta.Supernotes={offset:Number.MAX_SAFE_INTEGER,lineCount:-1}; +{try{throw new Error('');}catch(e){API_Meta.Supernotes.offset=(parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/,'$1'),10)-3);}} + // Supernotes_Templates can be called by other scripts. At this point ScriptCards is the only One Click script that does this. let Supernotes_Templates = { @@ -385,6 +378,324 @@ on('ready', function() { }); on('ready', () => { + + + /* ========================================================= + * Supernotes Help Handout Builder + * ========================================================= */ + +const buildSupernotesHelp = () => { + + const HANDOUT_NAME = "Help: Supernotes"; + const HANDOUT_AVATAR = "https://files.d20.io/images/470559564/QxDbBYEhr6jLMSpm0x42lg/original.png?1767857147"; // change if desired + +const helpHtml = ` +

Supernotes

+

Documentation for v.${version}

+ +
+ +

Overview

+ +

+Supernotes pulls content from a token’s GM Notes field and from other character fields not normally accessible to macros. +If a token represents a character, you may retrieve: +

+ +
    +
  • Character GM Notes
  • +
  • Character Bio
  • +
  • Character Avatar
  • +
  • Bio images (single, indexed, or all)
  • +
  • Token tooltip
  • +
  • Token image
  • +
+ +

+Notes may be whispered to the GM, sent to all players, whispered to the sender, or written directly to a named handout. +A footer button may optionally appear on GM whispers, allowing the note to be forwarded to players. +

+ +

+Images, API command buttons, links, markdown image syntax [x](imageURL), and most special characters pass through correctly in both chat and handouts. +

+ + +

Special Control Character for Inline GMnotes

+-----

+

Five dashes placed in the gmnotes of a token indicate that any following content is trested as gm-only text when sent to chat. +

+ +
+ +

Commands

+ +

!gmnote +Whispers note to GM.

+ +

!pcnote +Sends note to all players.

+ +

!selfnote +Whispers note to the command sender.

+ +
+ +

Parameters

+ +

Sources

+
    +
  • --token +Pull from selected token GM Notes (default). Token does not require a character.
  • + +
  • --charnote +Pull from represented character GM Notes.
  • + +
  • --bio +Pull from character Bio field.
  • + +
  • --avatar +Return character Avatar image.
  • + +
  • --image +Return first Bio image.
  • + +
  • --images +Return all Bio images.
  • + +
  • --image[number] +Return indexed Bio image (e.g. --image1, --image2).
  • + +
  • --tooltip +Return selected token tooltip.
  • + +
  • --tokenimage +Return selected token image.
  • + +
  • --card +Return token image and gmnotes in one report.
  • + +
+ +

Options

+ +
    +
  • --notitle +Suppress title in chat output. May be added to any command in any order.
  • + +
  • --idTOKENID +Read notes from specific token ID. No space after --id. Example: !gmnote --id-1234567890abcdef
  • + +
  • --handout|Handout Name| +Send output to named handout instead of chat. +Creates the handout if it does not exist. +Content above the automatic horizontal rule remains persistent.
  • + + +
  • --help +Displays help.
  • + +
  • --config +Opens configuration dialog.
  • +
+ +
+ +

Examples

+ +
!pcnote --bio
+

Sends selected character Bio to all players.

+ +
!gmnote --charnote
+

Whispers character GM Notes to GM.

+ +
!pcnote --image --notitle
+

Sends first image without revealing title.

+ +
+ +

Templates

+ +

+Add a template using: +

+ +
--template|templatename
+ +

+Example: +

+ +
!gmnote --template|crt
+!pcnote --template|notebook --bio
+!pcnote --template|faraway --tokenimage
+ +

+All templates include inline buttons and support Send to Players and Make Handout. +Handouts use Roll20’s native styling for cross-platform reliability. +

+ +
+ +

Available Templates

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
generic
Just the facts, Ma'am. Nothing fancy here.
dark
As previous, but in reverse.
crt
Retro greenscreen for hacking and cyberpunk. Or for reports on that xenomorph hiding on your ship.
notebook
You know, for kids. Who like to ride bikes. Maybe they attend a school and solve mysteries.
gothic
Classic noire horror for contending with Universal monsters or maybe contending with elder gods.
apoc
Messages scrawled on a wall. Crumbling and ancient, like the world that was.
scroll
High fantasy. Or low fantasy—we don't judge.
scroll2
An alternative to scroll, thats even scrollier.
lcars
For opening hailing frequencies and to boldly split infinitives that no one has split before!
faraway
No animated title crawl, but still has that space wizard feel.
steam
Gears and brass have changed my life.
western
Return with us now to those thrilling days of yesteryear.
dragon
Three-fivey style
wizard
A fifth edition of templates.
strange
Other kids who ride bikes and play D&D.
gate3
For folks who like the GOTY based on D&D.
choices
A second gate-y style, suitable for for the same crowd.
roll20light
for when you want your notes to have the feeling of authority
roll20dark
As before, but.... dark
news
Extra! Extra! Read all about it! The ink bleeds through from the other side of the newsprint.
treasure
For listing all that loot.
vault
A comforting style for sheltered people.
path
A style that works well with PF2 Adventure Paths
osrblue
Gygax-approved. Maybe. The graph paper even has yellowed edges
roman
Hail Caesar!
+ +
+ +

Configuration

+ +

+On installation, Supernotes defaults to the Default roll template. +The configuration dialog allows you to: +

+ +
    +
  • Select a sheet roll template
  • +
  • Toggle the “Send to Players” footer button
  • +
+ +

+Supported sheet templates include: +

+ +
    +
  • Default Template
  • +
  • D&D 5th Edition by Roll20
  • +
  • 5e Shaped
  • +
  • Pathfinder by Roll20
  • +
  • Pathfinder Community
  • +
  • Pathfinder 2e by Roll20
  • +
  • Starfinder
  • +
+ +
+ +

Troubleshooting

+ +

+If you experience template issues or configuration problems, you may use the buttons below to restore default behavior or re-open the configuration dialog. +

+ + + +

+Restore Default Template resets Supernotes to the Default roll template.
+Re-Run Configuration opens the configuration dialog to select a sheet template and toggle footer options. +

+ +`; + + + // Find existing handout + let handout = findObjs({ + _type: "handout", + name: HANDOUT_NAME + })[0]; + + // Create if missing + if (!handout) { + handout = createObj("handout", { + name: HANDOUT_NAME, + archived: false + }); + } + + // Always overwrite content + avatar + handout.set({ + notes: helpHtml, + avatar: HANDOUT_AVATAR + }); + + const link = `http://journal.roll20.net/handout/${handout.get("_id")}`; + + const box = + `
` + + `
Supernotes Help
` + + `Open Help Handout` + + `
`; + + sendChat("Supernotes", `/w gm ${box}`, null, { noarchive: true }); +}; + function parseMarkdown(markdownText) { const htmlText = markdownText @@ -405,14 +716,29 @@ function cleanText(text,buttonStyle){ text = ((undefined !== text) ? text.replace(/\[([^\]]*?)\]\(([^\)]*?)\)(?$1").replace(/

/gm, "").replace(/<\/p>/gm, "
").replace("padding:5px'>

", "padding:5px'>") : ""); text = text.replace(' + .replace(/\r?\n+/g, "
") + // Normalize mixed
,
,
variations to
+ .replace(/<\s*br\s*\/?\s*>/gi, "
") + // Remove accidental duplicate


etc + .replace(/(
\s*){2,}/g, "
") + .trim(); + return text; } + const decodeUnicode = (str) => str.replace(/%u[0-9a-fA-F]{2,4}/g, (m) => String.fromCharCode(parseInt(m.slice(2), 16))); - const version = '0.2.5'; + const version = '0.2.6'; log('Supernotes v' + version + ' is ready! --offset ' + API_Meta.Supernotes.offset + 'To set the template of choice or to toggle the send to players option, Use the command !gmnote --config'); +//Changelong +// 0.2.6 Reworked and updated Help system to use handout. Fixed logic issue Card output. +// 0.2.5 fixed trailing space problem in command line, fixed linebreak issue. + + + on('chat:message', function(msg) { if ('api' === msg.type && msg.content.match(/^!(gm|pc|self)note\b/)) { @@ -465,7 +791,7 @@ sendChat ("notes","success. Virtual token id is " + virtualTokenID); } let secondOption = ''; - let args = msg.content.split(/\s+--/); + let args = msg.content.trim().split(/\s+--/); let customTemplate = ''; let option = ''; @@ -634,7 +960,6 @@ whisper= whisper.replace(/<\/span>
/i,"") handoutButton = ((undefined !== handoutButton) ? handoutButton.replace(/\[([^\]]*?)\]\(([^\)]*?)\)(?$1
") : ""); whisper = ((whisper.length>0) ? "
" + whisper + "
" : ""); //log ("whisper = " + whisper); - return sendChat(whom, messagePrefix + '&{template:' + template + '}{{' + title + '=' + whom + '}} {{' + theText + '=' + message + whisper + playerButton + handoutButton + '}}'); } @@ -871,11 +1196,10 @@ message = message.replace(/201px/,newHeight+'px'); } } else { if (option !== undefined && option.includes('help')) { - message = 'Supernotes pulls the contents from a token's GM Notes field. If the token represents a character, you can optionally pull in the Bio or GM notes from the character, as well as the avatar, or extract just the image from the bio field. The user can decide whether to whisper the notes to the GM or broadcast them to all players. Finally, there is the option to add a footer to notes whispered to the GM. This footer creates a chat button to give the option of sending the notes on to the players.
This script as written is optimized for the D&D 5th Edition by Roll20 sheet, but can be adapted easily suing the Configuration section below.

Commands:
!gmnote whispers the note to the GM
!pcnote sends the note to all players

Paramaters
--token Pulls notes from the selected token's gm notes field. This is optional. If it is missing, the script assumes --token
--charnote Pulls notes from the gm notes field of the character assigned to a token.
--bio Pulls notes from the bio field of the character assigned to a token.
--avatar Pulls the image from the avatar field of the character assigned to a token.
--image Pulls first image from the bio field of the character assigned to a token, if any exists. Otherwise returns notice that no artwork is available
--images Pulls all images from the bio field of the character assigned to a token, if any exist. Otherwise returns notice that no artwork is available
--image[number] Pulls indexed image from the bio field of the character assigned to a token, if any exist. --image1 will pull the first image, --image2 the second and so on. Otherwise returns first image if available. If no images are available, returns notice that no artwork is available.
--template[templatename] Instead of using the configured sheet roll template, you can choose from between more than 10 custom templates that cover most common genres. Add the template command directly after the main prompt, followed by any of the regular parameters above. The current choices are:
generic. Just the facts, ma'am. Nothing fancy here.
dark. As above, but in reverse.
crt. Retro greenscreen for hacking and cyberpunk. Or for reports on that xenomorph hiding on your ship.
notebook. You know, for kids. Who like to ride bikes. Maybe they attend a school and fight vampires or rescue lost extraterrestrials
gothic. Classic noire horror for contending with Universal monsters or maybe contending with elder gods.
apoc. Messages scrawled on a wall. Crumbling and ancient, like the world that was.
scroll. High fantasy. Or low fantasy—we don't judge.
lcars. For opening hailing frequencies and to boldly split infinitives that no one has split before!
faraway. No animated title crawl, but still has that space wizard feel.
steam. Gears and brass have changed my life.
western. Return with us now to those thrilling days of yesteryear!

--help Displays help.
--config Returns a configuration dialog box that allows you to set which sheet's roll template to use, and to toggle the "Send to Players" footer.


Configuration
When first installed, Supernotes is configured for the default roll template. It will display a config dialog box at startup that will allow you to choose a roll template based on your character sheet of choice, as well as the option to toggle whether you want the "Send to Players" footer button to appear.
You will need to edit the code of the script to create a custom configuration. The pre-installed sheets are:
Default Template
D&D 5th Edition by Roll20
5e Shaped
Pathfinder by Roll20
Pathfinder Community
Pathfinder 2e by Roll20
Starfinder
Call of Cthulhu 7th Edition by Roll20
'; - sendMessage('Supernotes', messagePrefix, template, title, theText, message, false); - + buildSupernotesHelp(); + return; } else { - if (!(option + '').match(/^(bio|charnote|tokenimage|tooltip|avatar|imag(e|es|e[1-9]))/)) { + if (!(option + '').match(/^(card|bio|charnote|tokenimage|tooltip|avatar|imag(e|es|e[1-9]))/)) { option = 'token'; } @@ -902,7 +1226,73 @@ message = message.replace(/201px/,newHeight+'px'); - if (option === 'tooltip') { +if (option === 'card') { + + (theToken || []).forEach(sel => { + + const o = getObj('graphic', sel._id); + if (!o) return; + + const tokenID = o.id; + const tokenName = o.get('name') || ''; + const rawGM = o.get('gmnotes') || ''; + + // Always assign whom deterministically + whom = tokenName; + + // Decode GM notes safely + let decodedGM = rawGM ? unescape(decodeUnicode(rawGM)) : ''; + + // Apply regex filtering if present + if (decodedGM && regex) { + decodedGM = _.filter( + decodedGM.split(/(?:[\n\r]+|)/), + l => regex.test(l) + ).join('\r'); + } + + message = decodedGM || ''; + + // Crop GM-only content for player/self notes + if (command === '!pcnote' || command === '!selfnote') { + if (message.includes("-----")) { + message = message.split('-----')[0]; + } + } + + // Apply notitle + if (notitle) { + whom = ''; + } + + // Inject token image if message isn't an image URL + if (!/\.(png|jpg|jpeg|gif)/i.test(message)) { + + let styledTokenImage = ``; + + if (!message) { + message = `

`; + } + + message = styledTokenImage + message; + } + + sendMessage( + whom, + messagePrefix, + template, + title, + theText, + message, + tokenID, + playerButton, + handoutButton + ); + + }); + + } else { + if (option === 'tooltip') { (theToken || []) .map(o => getObj('graphic', o._id)) .filter(g => undefined !== g) @@ -1064,7 +1454,10 @@ message = message.replace(/201px/,newHeight+'px'); (theToken || []) .map(o => getObj('graphic', o._id)) .filter(g => undefined !== g) - .filter((o) => o.get('gmnotes').length > 0) + .filter((o) => { + const gm = (o && o.get) ? o.get('gmnotes') : ''; + return !!(gm && gm.length > 0); +}) .forEach(o => { if (regex) { message = _.filter(unescape(decodeUnicode(o.get('gmnotes'))).split(/(?:[\n\r]+|)/), (l) => regex.test(l)).join('\r'); @@ -1101,6 +1494,7 @@ message = message.replace(/201px/,newHeight+'px'); ].forEach(m => log(m)); */ } + } } } } diff --git a/Supernotes/script.json b/Supernotes/script.json index e75c2dd8b..4ec70f8e3 100644 --- a/Supernotes/script.json +++ b/Supernotes/script.json @@ -1,7 +1,7 @@ { "name": "Supernotes", "script": "Supernotes.js", - "version": "0.2.5", + "version": "0.2.6", "description": "# Supernotes\r*by keithcurtis, expanded from code written by the Aaron.*\r\rThis script pulls the contents from a token's GM Notes field and sends them to chat, based on a user-selectable roll template. If the token represents a character, you can optionally pull in the Bio or GM notes from the character. The user can decide whether to whisper the notes to the GM or broadcast them to all players. Finally, there is the option to add a footer to notes whispered to the GM. This footer creates a chat button to give the option of sending the notes on to the players.\r\rThis script as written is optimized for the D&D 5th Edition by Roll20 sheet, but can be adapted easily suing the Configuration section below.\r\r* [SuperNotes forum thread](https://app.roll20.net/forum/post/8293909/script-supernotes)\r\r\r## Commands:\r\r**!gmnote** whispers the note to the GM\r\r**!pcnote** sends the note to all players\r\r**!selfnote** whispers the note to to the sender\r\r\r## Paramaters\r\r*--token* Pulls notes from the selected token's gm notes field. This is optional. If it is missing, the script assumes --token\r\r*--charnote* Pulls notes from the gm notes field of the character assigned to a token.\r\r*--bio* Pulls notes from the bio field of the character assigned to a token.\r\r*--avatar* Pulls the image from the avatar field of the character assigned to a token.\r\r--image Pulls first image from the bio field of the character assigned to a token, if any exists. Otherwise returns notice that no artwork is available\r\r*--images* Pulls all images from the bio field of the character assigned to a token, if any exist. Otherwise returns notice that no artwork is available\r\r*--image[number]* Pulls indexed image from the bio field of the character assigned to a token, if any exist. *--image1* will pull the first image, *--image2* the second and so on. Otherwise returns first image if available. If no images are available, returns notice that no artwork is available.\r\r*--notitle* This option suppresses the title in the chat output. It is useful for times when the GM might wish to show an image or note to the player without clueing them in wha the note is about. For instance, they may wish to reveal an image of a monster without revealing its name.\r\r*--id* supply this with a token id, and the script will attempt to read the notes associated with a specific token, or the character associate with that token. There is no space between --id and the token id. Only one token id may be passed.\r\r*--handout|Handoutname|* If this is present in the arguments, the note will be sent to a handout instead of chat. This can allow a note to remain usable without scrolling through the chat. It can also be used as a sort of floating palette. Notes in handouts can be updated. Running the macro again will regenerate the note. The string in between pipes will be used as the name of the note handout. If no handout by that name exists, Supernotes will create one and post a link in chat to open it. The title must be placed between two pipes. handout|My Handout| will work. handout|My Handout will break.\rA note handout automatically creates a horizontal rule at the top of the handout. Anything typed manually above that rule will be persistent. Supernotes will not overwrite this portion. You can use this area to create Journal Command Buttons to generate new notes or to give some context to the existing note. All updates are live.\r\r--template[templatename] Instead of using the configured sheet roll template, you can choose from between more than 10 custom templates that cover most common genres. Add the template command directly after the main prompt, followed by any of the regular parameters above. The current choices are:\r**template|generic.** Just the facts, ma'am. Nothing fancy here.\r**template|dark.** As above, but in reverse.\r**template|crt.** Retro greenscreen for hacking and cyberpunk. Or for reports on that xenomorph hiding on your ship.\r**template|notebook.** You know, for kids. Who like to ride bikes. Maybe they attend a school and fight vampires or rescue lost extraterrestrials\r**template|gothic.** Classic noire horror for contending with Universal monsters or maybe contending with elder gods.\r**template|apoc.** Messages scrawled on a wall. Crumbling and ancient, like the world that was.\r**template|scroll.** High fantasy. Or low fantasy—we don't judge.\r**template|lcars.** For opening hailing frequencies and to boldly split infinitives that no one has split before!\r**template|faraway.** No animated title crawl, but still has that space wizard feel.\r**template|steam.** Gears and brass have changed my life.\r**template|western.** Return with us now to those thrilling days of yesteryear!\r**template|wizard.** Like those ones that live on the coast\r**template|dragon.** Third Edition goodness!\r\r*--help* Displays help.\r\r*--config* Returns a configuration dialog box that allows you to set which sheet's roll template to use, and to toggle the '\r Players' footer.\r\r\r## Configuration\r\rWhen first installed, Supernotes is configured for the default roll template. It will display a config dialog box at startup that will allow you to choose a roll template based on your character sheet of choice, as well as the option to toggle whether you want the '\r Players' footer button to appear.\r\rYou will need to edit the code of the script if you wish to create a custom configuration, or contact keithcurtis on the Roll20 forum and request an addition. The pre-installed sheets are:\r\rDefault Template, D&D 5th Edition by Roll20, 5e Shaped, Pathfinder by Roll20, Pathfinder Community, Pathfinder 2e by Roll20, Starfinder, Starfinder, Call of Cthulhu 7th Edition by Roll20", "authors": "Keith Curtis", "roll20userid": "162065", @@ -12,5 +12,5 @@ "character.represents": "read" }, "conflicts": [], - "previousversions": ["0.0.4","0.0.5","0.0.6","0.0.7","0.0.8","0.0.9","0.0.91","0.1.0","0.1.1","0.1.2","0.1.3","0.1.4","0.2.0","0.2.1","0.2.2","0.2.3","0.2.4","0.2.5"] + "previousversions": ["0.0.4","0.0.5","0.0.6","0.0.7","0.0.8","0.0.9","0.0.91","0.1.0","0.1.1","0.1.2","0.1.3","0.1.4","0.2.0","0.2.1","0.2.2","0.2.3","0.2.4","0.2.5","0.2.6"] } diff --git a/Supernotes/templateimages/apoc.png b/Supernotes/templateimages/apoc.png new file mode 100644 index 000000000..9b3d73132 Binary files /dev/null and b/Supernotes/templateimages/apoc.png differ diff --git a/Supernotes/templateimages/choices.png b/Supernotes/templateimages/choices.png new file mode 100644 index 000000000..606cf5614 Binary files /dev/null and b/Supernotes/templateimages/choices.png differ diff --git a/Supernotes/templateimages/crt.png b/Supernotes/templateimages/crt.png new file mode 100644 index 000000000..6e41ecdb3 Binary files /dev/null and b/Supernotes/templateimages/crt.png differ diff --git a/Supernotes/templateimages/dark.png b/Supernotes/templateimages/dark.png new file mode 100644 index 000000000..5fe725d29 Binary files /dev/null and b/Supernotes/templateimages/dark.png differ diff --git a/Supernotes/templateimages/dragon.png b/Supernotes/templateimages/dragon.png new file mode 100644 index 000000000..7b1f048ae Binary files /dev/null and b/Supernotes/templateimages/dragon.png differ diff --git a/Supernotes/templateimages/faraway.png b/Supernotes/templateimages/faraway.png new file mode 100644 index 000000000..73a6f5229 Binary files /dev/null and b/Supernotes/templateimages/faraway.png differ diff --git a/Supernotes/templateimages/gate3.png b/Supernotes/templateimages/gate3.png new file mode 100644 index 000000000..2b59b668c Binary files /dev/null and b/Supernotes/templateimages/gate3.png differ diff --git a/Supernotes/templateimages/generic.png b/Supernotes/templateimages/generic.png new file mode 100644 index 000000000..b21812a58 Binary files /dev/null and b/Supernotes/templateimages/generic.png differ diff --git a/Supernotes/templateimages/gothic.png b/Supernotes/templateimages/gothic.png new file mode 100644 index 000000000..1f35c35e7 Binary files /dev/null and b/Supernotes/templateimages/gothic.png differ diff --git a/Supernotes/templateimages/lcars.png b/Supernotes/templateimages/lcars.png new file mode 100644 index 000000000..4a3445bb6 Binary files /dev/null and b/Supernotes/templateimages/lcars.png differ diff --git a/Supernotes/templateimages/news.png b/Supernotes/templateimages/news.png new file mode 100644 index 000000000..08b2fdeee Binary files /dev/null and b/Supernotes/templateimages/news.png differ diff --git a/Supernotes/templateimages/notebook.png b/Supernotes/templateimages/notebook.png new file mode 100644 index 000000000..30391eb37 Binary files /dev/null and b/Supernotes/templateimages/notebook.png differ diff --git a/Supernotes/templateimages/osrblue.png b/Supernotes/templateimages/osrblue.png new file mode 100644 index 000000000..a749c020f Binary files /dev/null and b/Supernotes/templateimages/osrblue.png differ diff --git a/Supernotes/templateimages/path.png b/Supernotes/templateimages/path.png new file mode 100644 index 000000000..72a0cc3fd Binary files /dev/null and b/Supernotes/templateimages/path.png differ diff --git a/Supernotes/templateimages/rol200dark.png b/Supernotes/templateimages/rol200dark.png new file mode 100644 index 000000000..d12367ecb Binary files /dev/null and b/Supernotes/templateimages/rol200dark.png differ diff --git a/Supernotes/templateimages/roll20light.png b/Supernotes/templateimages/roll20light.png new file mode 100644 index 000000000..0dcb9d665 Binary files /dev/null and b/Supernotes/templateimages/roll20light.png differ diff --git a/Supernotes/templateimages/roman.png b/Supernotes/templateimages/roman.png new file mode 100644 index 000000000..25e7c4fec Binary files /dev/null and b/Supernotes/templateimages/roman.png differ diff --git a/Supernotes/templateimages/scroll.png b/Supernotes/templateimages/scroll.png new file mode 100644 index 000000000..f1c215acc Binary files /dev/null and b/Supernotes/templateimages/scroll.png differ diff --git a/Supernotes/templateimages/scroll2.png b/Supernotes/templateimages/scroll2.png new file mode 100644 index 000000000..bccfd0382 Binary files /dev/null and b/Supernotes/templateimages/scroll2.png differ diff --git a/Supernotes/templateimages/steeam.png b/Supernotes/templateimages/steeam.png new file mode 100644 index 000000000..e61675e73 Binary files /dev/null and b/Supernotes/templateimages/steeam.png differ diff --git a/Supernotes/templateimages/strange.png b/Supernotes/templateimages/strange.png new file mode 100644 index 000000000..898a820b2 Binary files /dev/null and b/Supernotes/templateimages/strange.png differ diff --git a/Supernotes/templateimages/treasure.png b/Supernotes/templateimages/treasure.png new file mode 100644 index 000000000..ce6712ae6 Binary files /dev/null and b/Supernotes/templateimages/treasure.png differ diff --git a/Supernotes/templateimages/vault.png b/Supernotes/templateimages/vault.png new file mode 100644 index 000000000..bec2a8617 Binary files /dev/null and b/Supernotes/templateimages/vault.png differ diff --git a/Supernotes/templateimages/western.png b/Supernotes/templateimages/western.png new file mode 100644 index 000000000..8e18f5aa3 Binary files /dev/null and b/Supernotes/templateimages/western.png differ diff --git a/Supernotes/templateimages/wizard.png b/Supernotes/templateimages/wizard.png new file mode 100644 index 000000000..ebc890b7d Binary files /dev/null and b/Supernotes/templateimages/wizard.png differ