Skip to content

Commit da5bca5

Browse files
authored
Merge pull request #2180 from keithcurtis1/master
PinNote fix
2 parents 7b5bbe3 + f5e1465 commit da5bca5

10 files changed

Lines changed: 3429 additions & 608 deletions

File tree

AutoLinker/1.0.0/AutoLinker.js

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
// Script: AutoLinker
2+
// By: Keith Curtis and Mik Holmes
3+
// Contact: https://app.roll20.net/users/162065/keithcurtis
4+
var API_Meta = API_Meta||{};
5+
API_Meta.AutoLinker={offset:Number.MAX_SAFE_INTEGER,lineCount:-1};
6+
{try{throw new Error('');}catch(e){API_Meta.AutoLinker.offset=(parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/,'$1'),10)-6);}}
7+
8+
on("ready", () => {
9+
'use strict';
10+
11+
const version = '1.0.0';
12+
log('-=> AutoLinker v' + version + ' is loaded. Type "!autolinker --help" for examples.');
13+
//Changelog
14+
//1.0.0 Debut
15+
16+
let eventLockout = false;
17+
18+
const autolink = (str, obj) => {
19+
const regex = /\[(?:([^\]|]*)|([^|]*)\|([^\]|]*))\]/g;
20+
if (!str) str = "";
21+
22+
return str.replace(regex, (all, oneWord, link, text) => {
23+
24+
// =====================================================
25+
// HEADER LINK WITHOUT PIPE
26+
// [Handout#Header]
27+
// =====================================================
28+
if (oneWord && oneWord.includes("#")) {
29+
30+
if (!obj || obj.get("_type") !== "handout") return all;
31+
32+
const parts = oneWord.split("#");
33+
const handoutName = parts[0].trim();
34+
const headerText = parts[1] ? parts[1].trim() : "";
35+
if (!headerText) return all;
36+
37+
let targetID = null;
38+
39+
if (handoutName === "") {
40+
targetID = obj.get("id");
41+
} else {
42+
const found = findObjs(
43+
{ _type: "handout", name: handoutName },
44+
{ caseInsensitive: true }
45+
);
46+
if (found && found[0]) targetID = found[0].get("id");
47+
else return all;
48+
}
49+
50+
const cleanHeader = headerText.replace(/<[^>]*>/g, "");
51+
const encodedHeader = cleanHeader.replace(/ /g, "%20");
52+
const url = `http://journal.roll20.net/handout/${targetID}/#${encodedHeader}`;
53+
54+
// Display text defaults to header text
55+
return `<a href='${url}'>${cleanHeader}</a>`;
56+
}
57+
58+
// =====================================================
59+
// SINGLE WORD MODE (namespace links)
60+
// =====================================================
61+
if (oneWord && oneWord.includes(":")) {
62+
const spell = oneWord.split(":");
63+
switch (spell[0]) {
64+
case "5e":
65+
return `<i><a href='https://roll20.net/compendium/dnd5e/${spell[1]}'>${spell[1]}</a></i>`;
66+
case "pf2":
67+
return `<i><a href='https://roll20.net/compendium/pf2/${spell[1]}'>${spell[1]}</a></i>`;
68+
case "gr":
69+
return `<a href="\`/gmroll ${spell[1]}">${spell[1]}</a>`;
70+
case "r":
71+
return `<a href="\`/roll ${spell[1]}">${spell[1]}</a>`;
72+
case "sot-quote":
73+
return `<div style="${styles.sot.quote}">${spell[1]}</div>`;
74+
default:
75+
return all;
76+
}
77+
}
78+
79+
// =====================================================
80+
// PIPE MODE
81+
// =====================================================
82+
if (link && text) {
83+
84+
// HEADER LINK WITH PIPE
85+
// [Handout#Header|Text]
86+
if (obj && obj.get("_type") === "handout" && link.includes("#")) {
87+
88+
const parts = link.split("#");
89+
const handoutName = parts[0].trim();
90+
const headerText = parts[1] ? parts[1].trim() : "";
91+
if (!headerText) return all;
92+
93+
let targetID = null;
94+
95+
if (handoutName === "") {
96+
targetID = obj.get("id");
97+
} else {
98+
const found = findObjs(
99+
{ _type: "handout", name: handoutName },
100+
{ caseInsensitive: true }
101+
);
102+
if (found && found[0]) targetID = found[0].get("id");
103+
else return all;
104+
}
105+
106+
const cleanHeader = headerText.replace(/<[^>]*>/g, "");
107+
const encodedHeader = cleanHeader.replace(/ /g, "%20");
108+
const url = `http://journal.roll20.net/handout/${targetID}/#${encodedHeader}`;
109+
110+
return `<a href='${url}'>${text}</a>`;
111+
}
112+
113+
// NAMESPACE LINKS WITH PIPE
114+
if (link.includes(":")) {
115+
const spell = link.split(":");
116+
switch (spell[0]) {
117+
case "5e":
118+
return `<i><a href='https://roll20.net/compendium/dnd5e/${spell[1]}'>${text}</a></i>`;
119+
case "pf2":
120+
return `<i><a href='https://roll20.net/compendium/pf2/${spell[1]}'>${text}</a></i>`;
121+
default:
122+
return all;
123+
}
124+
}
125+
126+
// JOURNAL LINKS
127+
const targetObj = findObjs({ name: link }, { caseInsensitive: true });
128+
if (targetObj[0]) {
129+
const targetID = targetObj[0].get("id");
130+
const targetType = targetObj[0].get("type");
131+
132+
if (targetType === "handout")
133+
return `<a href='http://journal.roll20.net/handout/${targetID}'>${text}</a>`;
134+
else if (targetType === "character")
135+
return `<a href='http://journal.roll20.net/character/${targetID}'>${text}</a>`;
136+
}
137+
}
138+
139+
return all;
140+
});
141+
};
142+
143+
const runAutolink = (obj, field) => {
144+
if (!eventLockout) {
145+
eventLockout = true;
146+
147+
obj.get(field, str => {
148+
const newText = autolink(str, obj);
149+
if (newText !== str) obj.set(field, newText);
150+
eventLockout = false;
151+
});
152+
}
153+
};
154+
155+
156+
/* ============================================================
157+
* AUTOLINKER HELP
158+
* Triggered by: !autolinker --help
159+
* ============================================================ */
160+
161+
const showAutoLinkerHelp = function(playerid) {
162+
163+
let helpText =
164+
"<p><span style='font-weight:bold; font-size:24px;'>Autolinker Help</span></p>" +
165+
"<p>Some examples of the autolinker functionality. These can be used on the notes/gmnotes of any handout or character.</p>" +
166+
"<p>Please note that this script works after you save changes to a handout, " +
167+
"but the handout often reloads before the script is finished. Closing and reopening the handout, or clicking Edit again, should give it enough time to properly link things.</p>" +
168+
"<p><code>[goblin|Jimmy]</code> will make a link with the text 'Jimmy' to the 'goblin' handout.</p>" +
169+
"<p><code>[5e:fireball]</code> will link to the 5e compendium page for fireball.</p>" +
170+
"<p><code>[5e:wall of fire|the wall]</code> will make a link with the text 'the wall' to the 5e compendium page for wall of fire</p>" +
171+
"<p>Currently <code>5e:</code> and <code>pf2:</code> will link to their respective compendiums.</p>" +
172+
"<p><b>Handout Header linking:</b></p>" +
173+
"<p>To link to specific headers in a handout (handouts only) use the # character.</p>" +
174+
"<p><code>[Dungeon of Doom#6. Zombie Chorus|See Room 6]</code> will link the header '6. Zombie Chorus' in the handout 'Dungeon of Doom', with the display text 'See Room 6'.</p>" +
175+
"<p>If the link goes to a header in the same handout, you do not need to specify the handout:</p>" +
176+
"<p><code>[#6. Zombie Chorus|See Room 6]</code> will link the header '6. Zombie Chorus' in the same handout, with the display text 'See Room 6'.</p>" +
177+
"<p>If you do not need the display text of the link to be different from the text of the header, you can omit that part as well:</p>" +
178+
"<p><code>[#6. Zombie Chorus]</code> will link the header '6. Zombie Chorus' in the same handout, with the display text '6. Zombie Chorus'.</p>";
179+
180+
let styledDiv =
181+
"<div style='background-color:#bbb; padding:12px; border-radius:10px; border:2px solid #888; color:#111'>" +
182+
helpText +
183+
"</div>";
184+
185+
let player = getObj("player", playerid);
186+
if (player) {
187+
sendChat("AutoLinker", "/w \"" + player.get("_displayname") + "\" " + styledDiv);
188+
}
189+
};
190+
191+
192+
/* ============================================================
193+
* CHAT HANDLER
194+
* ============================================================ */
195+
196+
on("chat:message", function(msg) {
197+
if (msg.type !== "api") return;
198+
199+
if (msg.content.trim() === "!autolinker --help") {
200+
showAutoLinkerHelp(msg.playerid);
201+
}
202+
});
203+
204+
205+
206+
const registerEventHandlers = () => {
207+
on('change:handout:notes', obj => runAutolink(obj, "notes"));
208+
on('change:handout:gmnotes', obj => runAutolink(obj, "gmnotes"));
209+
on('change:character:bio', obj => runAutolink(obj, "bio"));
210+
on('change:character:gmnotes', obj => runAutolink(obj, "gmnotes"));
211+
};
212+
213+
registerEventHandlers();
214+
});
215+
216+
{try{throw new Error('');}catch(e){API_Meta.AutoLinker.lineCount=(parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/,'$1'),10)-API_Meta.AutoLinker.offset);}}

0 commit comments

Comments
 (0)