vault commit: 2025-10-09 00:46, 16 files changed
This commit is contained in:
+100
@@ -3,6 +3,7 @@ Level_of_Influence: 3
|
|||||||
Faction: "[[The Archmasons]]"
|
Faction: "[[The Archmasons]]"
|
||||||
Tags:
|
Tags:
|
||||||
- npc
|
- npc
|
||||||
|
Intel:
|
||||||
---
|
---
|
||||||
|
|
||||||
# Amity Cornwallis
|
# Amity Cornwallis
|
||||||
@@ -43,8 +44,107 @@ Tags:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 🧠 Intel (auto)
|
||||||
|
```dataviewjs
|
||||||
|
const THIS = dv.current();
|
||||||
|
const thisName = String(THIS.file.name);
|
||||||
|
const thisLC = thisName.toLowerCase();
|
||||||
|
|
||||||
|
// Scan faction & NPC notes that have Intel arrays
|
||||||
|
const pages = dv.pages("#npc or #faction").where(p => p.Intel);
|
||||||
|
|
||||||
|
// Does an Intel entry reference THIS NPC?
|
||||||
|
function mentionsThisNPC(entry) {
|
||||||
|
const sum = String(entry.summary ?? "");
|
||||||
|
|
||||||
|
// 1) Explicit wikilink in summary
|
||||||
|
if (sum.includes(`[[${thisName}]]`) || sum.includes(`[[${thisName}|`)) return true;
|
||||||
|
|
||||||
|
// 2) Source field (string/link/list)
|
||||||
|
const src = entry.source;
|
||||||
|
const arr = Array.isArray(src) ? src : (src ? [src] : []);
|
||||||
|
for (const s of arr) {
|
||||||
|
if (typeof s === "string") {
|
||||||
|
// quoted link or plain text
|
||||||
|
const unq = s.replace(/^"+|"+$/g, ""); // drop surrounding quotes if any
|
||||||
|
if (unq.includes(`[[${thisName}]]`) || unq.includes(`[[${thisName}|`)) return true;
|
||||||
|
// if they typed just the name
|
||||||
|
if (unq.replace(/\[\[|\]\]/g, "").trim().toLowerCase() === thisLC) return true;
|
||||||
|
} else if (s && typeof s === "object" && s.path) {
|
||||||
|
// wikilink object
|
||||||
|
const base = s.path.split("/").pop().replace(/\.md$/i, "").toLowerCase();
|
||||||
|
if (base === thisLC) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional (looser): plain text mention in summary
|
||||||
|
// return sum.toLowerCase().includes(thisLC);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect rows: 1) own Intel, 2) mentions from other notes
|
||||||
|
const rows = [];
|
||||||
|
|
||||||
|
// Own Intel (from this NPC note)
|
||||||
|
for (const it of (THIS.Intel ?? [])) {
|
||||||
|
rows.push({
|
||||||
|
from: THIS.file.link,
|
||||||
|
date: it.date ?? "",
|
||||||
|
type: it.type ?? "",
|
||||||
|
summary: String(it.summary ?? ""),
|
||||||
|
session: it.session ?? "",
|
||||||
|
source: it.source ?? "",
|
||||||
|
rel: it.reliability ?? "",
|
||||||
|
cred: it.credibility ?? "",
|
||||||
|
tags: Array.isArray(it.tags) ? it.tags.join(", ") : (it.tags ?? "")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mentions elsewhere
|
||||||
|
for (const p of pages.where(p => p.file.path !== THIS.file.path)) {
|
||||||
|
for (const it of (p.Intel ?? [])) {
|
||||||
|
if (mentionsThisNPC(it)) {
|
||||||
|
rows.push({
|
||||||
|
from: p.file.link,
|
||||||
|
date: it.date ?? "",
|
||||||
|
type: it.type ?? "",
|
||||||
|
summary: String(it.summary ?? ""),
|
||||||
|
session: it.session ?? "",
|
||||||
|
source: it.source ?? "",
|
||||||
|
rel: it.reliability ?? "",
|
||||||
|
cred: it.credibility ?? "",
|
||||||
|
tags: Array.isArray(it.tags) ? it.tags.join(", ") : (it.tags ?? "")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort newest first
|
||||||
|
rows.sort((a,b) => String(b.date).localeCompare(String(a.date)));
|
||||||
|
|
||||||
|
if (!rows.length) {
|
||||||
|
dv.paragraph("_No intel yet._");
|
||||||
|
} else {
|
||||||
|
dv.paragraph(`_Entries: ${rows.length} (own ${(THIS.Intel ?? []).length} + mentions ${Math.max(0, rows.length - (THIS.Intel ?? []).length)})_`);
|
||||||
|
dv.table(
|
||||||
|
["From Note","Date","Type","Summary","Session","Source","Rel","Cred","Tags"],
|
||||||
|
rows.map(r => [r.from, r.date, r.type, r.summary, r.session, r.source, r.rel, r.cred, r.tags])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```button
|
||||||
|
name ➕ Add Intel
|
||||||
|
type command
|
||||||
|
action Templater: Insert Add Intel Entry to NPC
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 🏛️ Relations & Factions
|
## 🏛️ Relations & Factions
|
||||||
- **Primary Faction:** [[The Archmasons]]
|
- **Primary Faction:** [[The Archmasons]]
|
||||||
|
|
||||||
- **Allied Factions:** [[Faction A]], [[Faction B]]
|
- **Allied Factions:** [[Faction A]], [[Faction B]]
|
||||||
- **Rival Factions:** [[Faction C]]
|
- **Rival Factions:** [[Faction C]]
|
||||||
- **Personal Enemies / Allies:** [[NPC or PC]]
|
- **Personal Enemies / Allies:** [[NPC or PC]]
|
||||||
|
|||||||
+97
@@ -38,6 +38,103 @@ Tags:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 🧠 Intel (auto)
|
||||||
|
```dataviewjs
|
||||||
|
const THIS = dv.current();
|
||||||
|
const thisName = String(THIS.file.name);
|
||||||
|
const thisLC = thisName.toLowerCase();
|
||||||
|
|
||||||
|
// Scan faction & NPC notes that have Intel arrays
|
||||||
|
const pages = dv.pages("#npc or #faction").where(p => p.Intel);
|
||||||
|
|
||||||
|
// Does an Intel entry reference THIS NPC?
|
||||||
|
function mentionsThisNPC(entry) {
|
||||||
|
const sum = String(entry.summary ?? "");
|
||||||
|
|
||||||
|
// 1) Explicit wikilink in summary
|
||||||
|
if (sum.includes(`[[${thisName}]]`) || sum.includes(`[[${thisName}|`)) return true;
|
||||||
|
|
||||||
|
// 2) Source field (string/link/list)
|
||||||
|
const src = entry.source;
|
||||||
|
const arr = Array.isArray(src) ? src : (src ? [src] : []);
|
||||||
|
for (const s of arr) {
|
||||||
|
if (typeof s === "string") {
|
||||||
|
// quoted link or plain text
|
||||||
|
const unq = s.replace(/^"+|"+$/g, ""); // drop surrounding quotes if any
|
||||||
|
if (unq.includes(`[[${thisName}]]`) || unq.includes(`[[${thisName}|`)) return true;
|
||||||
|
// if they typed just the name
|
||||||
|
if (unq.replace(/\[\[|\]\]/g, "").trim().toLowerCase() === thisLC) return true;
|
||||||
|
} else if (s && typeof s === "object" && s.path) {
|
||||||
|
// wikilink object
|
||||||
|
const base = s.path.split("/").pop().replace(/\.md$/i, "").toLowerCase();
|
||||||
|
if (base === thisLC) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional (looser): plain text mention in summary
|
||||||
|
// return sum.toLowerCase().includes(thisLC);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect rows: 1) own Intel, 2) mentions from other notes
|
||||||
|
const rows = [];
|
||||||
|
|
||||||
|
// Own Intel (from this NPC note)
|
||||||
|
for (const it of (THIS.Intel ?? [])) {
|
||||||
|
rows.push({
|
||||||
|
from: THIS.file.link,
|
||||||
|
date: it.date ?? "",
|
||||||
|
type: it.type ?? "",
|
||||||
|
summary: String(it.summary ?? ""),
|
||||||
|
session: it.session ?? "",
|
||||||
|
source: it.source ?? "",
|
||||||
|
rel: it.reliability ?? "",
|
||||||
|
cred: it.credibility ?? "",
|
||||||
|
tags: Array.isArray(it.tags) ? it.tags.join(", ") : (it.tags ?? "")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mentions elsewhere
|
||||||
|
for (const p of pages.where(p => p.file.path !== THIS.file.path)) {
|
||||||
|
for (const it of (p.Intel ?? [])) {
|
||||||
|
if (mentionsThisNPC(it)) {
|
||||||
|
rows.push({
|
||||||
|
from: p.file.link,
|
||||||
|
date: it.date ?? "",
|
||||||
|
type: it.type ?? "",
|
||||||
|
summary: String(it.summary ?? ""),
|
||||||
|
session: it.session ?? "",
|
||||||
|
source: it.source ?? "",
|
||||||
|
rel: it.reliability ?? "",
|
||||||
|
cred: it.credibility ?? "",
|
||||||
|
tags: Array.isArray(it.tags) ? it.tags.join(", ") : (it.tags ?? "")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort newest first
|
||||||
|
rows.sort((a,b) => String(b.date).localeCompare(String(a.date)));
|
||||||
|
|
||||||
|
if (!rows.length) {
|
||||||
|
dv.paragraph("_No intel yet._");
|
||||||
|
} else {
|
||||||
|
dv.paragraph(`_Entries: ${rows.length} (own ${(THIS.Intel ?? []).length} + mentions ${Math.max(0, rows.length - (THIS.Intel ?? []).length)})_`);
|
||||||
|
dv.table(
|
||||||
|
["From Note","Date","Type","Summary","Session","Source","Rel","Cred","Tags"],
|
||||||
|
rows.map(r => [r.from, r.date, r.type, r.summary, r.session, r.source, r.rel, r.cred, r.tags])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```button
|
||||||
|
name ➕ Add Intel
|
||||||
|
type command
|
||||||
|
action Templater: Insert Add Intel Entry to NPC
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
## 🏛️ Relations & Factions
|
## 🏛️ Relations & Factions
|
||||||
- **Primary Faction:** [[Faction Name]]
|
- **Primary Faction:** [[Faction Name]]
|
||||||
- **Allied Factions:** [[Faction A]], [[Faction B]]
|
- **Allied Factions:** [[Faction A]], [[Faction B]]
|
||||||
|
|||||||
+98
@@ -38,6 +38,104 @@ Tags:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 🧠 Intel (auto)
|
||||||
|
```dataviewjs
|
||||||
|
const THIS = dv.current();
|
||||||
|
const thisName = String(THIS.file.name);
|
||||||
|
const thisLC = thisName.toLowerCase();
|
||||||
|
|
||||||
|
// Scan faction & NPC notes that have Intel arrays
|
||||||
|
const pages = dv.pages("#npc or #faction").where(p => p.Intel);
|
||||||
|
|
||||||
|
// Does an Intel entry reference THIS NPC?
|
||||||
|
function mentionsThisNPC(entry) {
|
||||||
|
const sum = String(entry.summary ?? "");
|
||||||
|
|
||||||
|
// 1) Explicit wikilink in summary
|
||||||
|
if (sum.includes(`[[${thisName}]]`) || sum.includes(`[[${thisName}|`)) return true;
|
||||||
|
|
||||||
|
// 2) Source field (string/link/list)
|
||||||
|
const src = entry.source;
|
||||||
|
const arr = Array.isArray(src) ? src : (src ? [src] : []);
|
||||||
|
for (const s of arr) {
|
||||||
|
if (typeof s === "string") {
|
||||||
|
// quoted link or plain text
|
||||||
|
const unq = s.replace(/^"+|"+$/g, ""); // drop surrounding quotes if any
|
||||||
|
if (unq.includes(`[[${thisName}]]`) || unq.includes(`[[${thisName}|`)) return true;
|
||||||
|
// if they typed just the name
|
||||||
|
if (unq.replace(/\[\[|\]\]/g, "").trim().toLowerCase() === thisLC) return true;
|
||||||
|
} else if (s && typeof s === "object" && s.path) {
|
||||||
|
// wikilink object
|
||||||
|
const base = s.path.split("/").pop().replace(/\.md$/i, "").toLowerCase();
|
||||||
|
if (base === thisLC) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional (looser): plain text mention in summary
|
||||||
|
// return sum.toLowerCase().includes(thisLC);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect rows: 1) own Intel, 2) mentions from other notes
|
||||||
|
const rows = [];
|
||||||
|
|
||||||
|
// Own Intel (from this NPC note)
|
||||||
|
for (const it of (THIS.Intel ?? [])) {
|
||||||
|
rows.push({
|
||||||
|
from: THIS.file.link,
|
||||||
|
date: it.date ?? "",
|
||||||
|
type: it.type ?? "",
|
||||||
|
summary: String(it.summary ?? ""),
|
||||||
|
session: it.session ?? "",
|
||||||
|
source: it.source ?? "",
|
||||||
|
rel: it.reliability ?? "",
|
||||||
|
cred: it.credibility ?? "",
|
||||||
|
tags: Array.isArray(it.tags) ? it.tags.join(", ") : (it.tags ?? "")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mentions elsewhere
|
||||||
|
for (const p of pages.where(p => p.file.path !== THIS.file.path)) {
|
||||||
|
for (const it of (p.Intel ?? [])) {
|
||||||
|
if (mentionsThisNPC(it)) {
|
||||||
|
rows.push({
|
||||||
|
from: p.file.link,
|
||||||
|
date: it.date ?? "",
|
||||||
|
type: it.type ?? "",
|
||||||
|
summary: String(it.summary ?? ""),
|
||||||
|
session: it.session ?? "",
|
||||||
|
source: it.source ?? "",
|
||||||
|
rel: it.reliability ?? "",
|
||||||
|
cred: it.credibility ?? "",
|
||||||
|
tags: Array.isArray(it.tags) ? it.tags.join(", ") : (it.tags ?? "")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort newest first
|
||||||
|
rows.sort((a,b) => String(b.date).localeCompare(String(a.date)));
|
||||||
|
|
||||||
|
if (!rows.length) {
|
||||||
|
dv.paragraph("_No intel yet._");
|
||||||
|
} else {
|
||||||
|
dv.paragraph(`_Entries: ${rows.length} (own ${(THIS.Intel ?? []).length} + mentions ${Math.max(0, rows.length - (THIS.Intel ?? []).length)})_`);
|
||||||
|
dv.table(
|
||||||
|
["From Note","Date","Type","Summary","Session","Source","Rel","Cred","Tags"],
|
||||||
|
rows.map(r => [r.from, r.date, r.type, r.summary, r.session, r.source, r.rel, r.cred, r.tags])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```button
|
||||||
|
name ➕ Add Intel
|
||||||
|
type command
|
||||||
|
action Templater: Insert Add Intel Entry to NPC
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 🏛️ Relations & Factions
|
## 🏛️ Relations & Factions
|
||||||
- **Primary Faction:** [[House Noctilun]]
|
- **Primary Faction:** [[House Noctilun]]
|
||||||
- However, in conversation with Calia, she mentions that she is not in the houses good graces, hence why she is stationed in Y'athyr as opposed to Ri'lesera
|
- However, in conversation with Calia, she mentions that she is not in the houses good graces, hence why she is stationed in Y'athyr as opposed to Ri'lesera
|
||||||
|
|||||||
+98
@@ -41,6 +41,104 @@ Tags:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 🧠 Intel (auto)
|
||||||
|
```dataviewjs
|
||||||
|
const THIS = dv.current();
|
||||||
|
const thisName = String(THIS.file.name);
|
||||||
|
const thisLC = thisName.toLowerCase();
|
||||||
|
|
||||||
|
// Scan faction & NPC notes that have Intel arrays
|
||||||
|
const pages = dv.pages("#npc or #faction").where(p => p.Intel);
|
||||||
|
|
||||||
|
// Does an Intel entry reference THIS NPC?
|
||||||
|
function mentionsThisNPC(entry) {
|
||||||
|
const sum = String(entry.summary ?? "");
|
||||||
|
|
||||||
|
// 1) Explicit wikilink in summary
|
||||||
|
if (sum.includes(`[[${thisName}]]`) || sum.includes(`[[${thisName}|`)) return true;
|
||||||
|
|
||||||
|
// 2) Source field (string/link/list)
|
||||||
|
const src = entry.source;
|
||||||
|
const arr = Array.isArray(src) ? src : (src ? [src] : []);
|
||||||
|
for (const s of arr) {
|
||||||
|
if (typeof s === "string") {
|
||||||
|
// quoted link or plain text
|
||||||
|
const unq = s.replace(/^"+|"+$/g, ""); // drop surrounding quotes if any
|
||||||
|
if (unq.includes(`[[${thisName}]]`) || unq.includes(`[[${thisName}|`)) return true;
|
||||||
|
// if they typed just the name
|
||||||
|
if (unq.replace(/\[\[|\]\]/g, "").trim().toLowerCase() === thisLC) return true;
|
||||||
|
} else if (s && typeof s === "object" && s.path) {
|
||||||
|
// wikilink object
|
||||||
|
const base = s.path.split("/").pop().replace(/\.md$/i, "").toLowerCase();
|
||||||
|
if (base === thisLC) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional (looser): plain text mention in summary
|
||||||
|
// return sum.toLowerCase().includes(thisLC);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect rows: 1) own Intel, 2) mentions from other notes
|
||||||
|
const rows = [];
|
||||||
|
|
||||||
|
// Own Intel (from this NPC note)
|
||||||
|
for (const it of (THIS.Intel ?? [])) {
|
||||||
|
rows.push({
|
||||||
|
from: THIS.file.link,
|
||||||
|
date: it.date ?? "",
|
||||||
|
type: it.type ?? "",
|
||||||
|
summary: String(it.summary ?? ""),
|
||||||
|
session: it.session ?? "",
|
||||||
|
source: it.source ?? "",
|
||||||
|
rel: it.reliability ?? "",
|
||||||
|
cred: it.credibility ?? "",
|
||||||
|
tags: Array.isArray(it.tags) ? it.tags.join(", ") : (it.tags ?? "")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mentions elsewhere
|
||||||
|
for (const p of pages.where(p => p.file.path !== THIS.file.path)) {
|
||||||
|
for (const it of (p.Intel ?? [])) {
|
||||||
|
if (mentionsThisNPC(it)) {
|
||||||
|
rows.push({
|
||||||
|
from: p.file.link,
|
||||||
|
date: it.date ?? "",
|
||||||
|
type: it.type ?? "",
|
||||||
|
summary: String(it.summary ?? ""),
|
||||||
|
session: it.session ?? "",
|
||||||
|
source: it.source ?? "",
|
||||||
|
rel: it.reliability ?? "",
|
||||||
|
cred: it.credibility ?? "",
|
||||||
|
tags: Array.isArray(it.tags) ? it.tags.join(", ") : (it.tags ?? "")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort newest first
|
||||||
|
rows.sort((a,b) => String(b.date).localeCompare(String(a.date)));
|
||||||
|
|
||||||
|
if (!rows.length) {
|
||||||
|
dv.paragraph("_No intel yet._");
|
||||||
|
} else {
|
||||||
|
dv.paragraph(`_Entries: ${rows.length} (own ${(THIS.Intel ?? []).length} + mentions ${Math.max(0, rows.length - (THIS.Intel ?? []).length)})_`);
|
||||||
|
dv.table(
|
||||||
|
["From Note","Date","Type","Summary","Session","Source","Rel","Cred","Tags"],
|
||||||
|
rows.map(r => [r.from, r.date, r.type, r.summary, r.session, r.source, r.rel, r.cred, r.tags])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```button
|
||||||
|
name ➕ Add Intel
|
||||||
|
type command
|
||||||
|
action Templater: Insert Add Intel Entry to NPC
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 🏛️ Relations & Factions
|
## 🏛️ Relations & Factions
|
||||||
- **Primary Faction:** [[House Gille]]
|
- **Primary Faction:** [[House Gille]]
|
||||||
- **Allied Factions:** [[Faction A]], [[Faction B]]
|
- **Allied Factions:** [[Faction A]], [[Faction B]]
|
||||||
|
|||||||
+98
@@ -44,6 +44,104 @@ Tags:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 🧠 Intel (auto)
|
||||||
|
```dataviewjs
|
||||||
|
const THIS = dv.current();
|
||||||
|
const thisName = String(THIS.file.name);
|
||||||
|
const thisLC = thisName.toLowerCase();
|
||||||
|
|
||||||
|
// Scan faction & NPC notes that have Intel arrays
|
||||||
|
const pages = dv.pages("#npc or #faction").where(p => p.Intel);
|
||||||
|
|
||||||
|
// Does an Intel entry reference THIS NPC?
|
||||||
|
function mentionsThisNPC(entry) {
|
||||||
|
const sum = String(entry.summary ?? "");
|
||||||
|
|
||||||
|
// 1) Explicit wikilink in summary
|
||||||
|
if (sum.includes(`[[${thisName}]]`) || sum.includes(`[[${thisName}|`)) return true;
|
||||||
|
|
||||||
|
// 2) Source field (string/link/list)
|
||||||
|
const src = entry.source;
|
||||||
|
const arr = Array.isArray(src) ? src : (src ? [src] : []);
|
||||||
|
for (const s of arr) {
|
||||||
|
if (typeof s === "string") {
|
||||||
|
// quoted link or plain text
|
||||||
|
const unq = s.replace(/^"+|"+$/g, ""); // drop surrounding quotes if any
|
||||||
|
if (unq.includes(`[[${thisName}]]`) || unq.includes(`[[${thisName}|`)) return true;
|
||||||
|
// if they typed just the name
|
||||||
|
if (unq.replace(/\[\[|\]\]/g, "").trim().toLowerCase() === thisLC) return true;
|
||||||
|
} else if (s && typeof s === "object" && s.path) {
|
||||||
|
// wikilink object
|
||||||
|
const base = s.path.split("/").pop().replace(/\.md$/i, "").toLowerCase();
|
||||||
|
if (base === thisLC) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional (looser): plain text mention in summary
|
||||||
|
// return sum.toLowerCase().includes(thisLC);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect rows: 1) own Intel, 2) mentions from other notes
|
||||||
|
const rows = [];
|
||||||
|
|
||||||
|
// Own Intel (from this NPC note)
|
||||||
|
for (const it of (THIS.Intel ?? [])) {
|
||||||
|
rows.push({
|
||||||
|
from: THIS.file.link,
|
||||||
|
date: it.date ?? "",
|
||||||
|
type: it.type ?? "",
|
||||||
|
summary: String(it.summary ?? ""),
|
||||||
|
session: it.session ?? "",
|
||||||
|
source: it.source ?? "",
|
||||||
|
rel: it.reliability ?? "",
|
||||||
|
cred: it.credibility ?? "",
|
||||||
|
tags: Array.isArray(it.tags) ? it.tags.join(", ") : (it.tags ?? "")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mentions elsewhere
|
||||||
|
for (const p of pages.where(p => p.file.path !== THIS.file.path)) {
|
||||||
|
for (const it of (p.Intel ?? [])) {
|
||||||
|
if (mentionsThisNPC(it)) {
|
||||||
|
rows.push({
|
||||||
|
from: p.file.link,
|
||||||
|
date: it.date ?? "",
|
||||||
|
type: it.type ?? "",
|
||||||
|
summary: String(it.summary ?? ""),
|
||||||
|
session: it.session ?? "",
|
||||||
|
source: it.source ?? "",
|
||||||
|
rel: it.reliability ?? "",
|
||||||
|
cred: it.credibility ?? "",
|
||||||
|
tags: Array.isArray(it.tags) ? it.tags.join(", ") : (it.tags ?? "")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort newest first
|
||||||
|
rows.sort((a,b) => String(b.date).localeCompare(String(a.date)));
|
||||||
|
|
||||||
|
if (!rows.length) {
|
||||||
|
dv.paragraph("_No intel yet._");
|
||||||
|
} else {
|
||||||
|
dv.paragraph(`_Entries: ${rows.length} (own ${(THIS.Intel ?? []).length} + mentions ${Math.max(0, rows.length - (THIS.Intel ?? []).length)})_`);
|
||||||
|
dv.table(
|
||||||
|
["From Note","Date","Type","Summary","Session","Source","Rel","Cred","Tags"],
|
||||||
|
rows.map(r => [r.from, r.date, r.type, r.summary, r.session, r.source, r.rel, r.cred, r.tags])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```button
|
||||||
|
name ➕ Add Intel
|
||||||
|
type command
|
||||||
|
action Templater: Insert Add Intel Entry to NPC
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 🏛️ Relations & Factions
|
## 🏛️ Relations & Factions
|
||||||
- **Primary Faction:** [[Faction Name]]
|
- **Primary Faction:** [[Faction Name]]
|
||||||
- **Allied Factions:** [[Faction A]], [[Faction B]]
|
- **Allied Factions:** [[Faction A]], [[Faction B]]
|
||||||
|
|||||||
+98
@@ -38,6 +38,104 @@ Tags:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 🧠 Intel (auto)
|
||||||
|
```dataviewjs
|
||||||
|
const THIS = dv.current();
|
||||||
|
const thisName = String(THIS.file.name);
|
||||||
|
const thisLC = thisName.toLowerCase();
|
||||||
|
|
||||||
|
// Scan faction & NPC notes that have Intel arrays
|
||||||
|
const pages = dv.pages("#npc or #faction").where(p => p.Intel);
|
||||||
|
|
||||||
|
// Does an Intel entry reference THIS NPC?
|
||||||
|
function mentionsThisNPC(entry) {
|
||||||
|
const sum = String(entry.summary ?? "");
|
||||||
|
|
||||||
|
// 1) Explicit wikilink in summary
|
||||||
|
if (sum.includes(`[[${thisName}]]`) || sum.includes(`[[${thisName}|`)) return true;
|
||||||
|
|
||||||
|
// 2) Source field (string/link/list)
|
||||||
|
const src = entry.source;
|
||||||
|
const arr = Array.isArray(src) ? src : (src ? [src] : []);
|
||||||
|
for (const s of arr) {
|
||||||
|
if (typeof s === "string") {
|
||||||
|
// quoted link or plain text
|
||||||
|
const unq = s.replace(/^"+|"+$/g, ""); // drop surrounding quotes if any
|
||||||
|
if (unq.includes(`[[${thisName}]]`) || unq.includes(`[[${thisName}|`)) return true;
|
||||||
|
// if they typed just the name
|
||||||
|
if (unq.replace(/\[\[|\]\]/g, "").trim().toLowerCase() === thisLC) return true;
|
||||||
|
} else if (s && typeof s === "object" && s.path) {
|
||||||
|
// wikilink object
|
||||||
|
const base = s.path.split("/").pop().replace(/\.md$/i, "").toLowerCase();
|
||||||
|
if (base === thisLC) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional (looser): plain text mention in summary
|
||||||
|
// return sum.toLowerCase().includes(thisLC);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect rows: 1) own Intel, 2) mentions from other notes
|
||||||
|
const rows = [];
|
||||||
|
|
||||||
|
// Own Intel (from this NPC note)
|
||||||
|
for (const it of (THIS.Intel ?? [])) {
|
||||||
|
rows.push({
|
||||||
|
from: THIS.file.link,
|
||||||
|
date: it.date ?? "",
|
||||||
|
type: it.type ?? "",
|
||||||
|
summary: String(it.summary ?? ""),
|
||||||
|
session: it.session ?? "",
|
||||||
|
source: it.source ?? "",
|
||||||
|
rel: it.reliability ?? "",
|
||||||
|
cred: it.credibility ?? "",
|
||||||
|
tags: Array.isArray(it.tags) ? it.tags.join(", ") : (it.tags ?? "")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mentions elsewhere
|
||||||
|
for (const p of pages.where(p => p.file.path !== THIS.file.path)) {
|
||||||
|
for (const it of (p.Intel ?? [])) {
|
||||||
|
if (mentionsThisNPC(it)) {
|
||||||
|
rows.push({
|
||||||
|
from: p.file.link,
|
||||||
|
date: it.date ?? "",
|
||||||
|
type: it.type ?? "",
|
||||||
|
summary: String(it.summary ?? ""),
|
||||||
|
session: it.session ?? "",
|
||||||
|
source: it.source ?? "",
|
||||||
|
rel: it.reliability ?? "",
|
||||||
|
cred: it.credibility ?? "",
|
||||||
|
tags: Array.isArray(it.tags) ? it.tags.join(", ") : (it.tags ?? "")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort newest first
|
||||||
|
rows.sort((a,b) => String(b.date).localeCompare(String(a.date)));
|
||||||
|
|
||||||
|
if (!rows.length) {
|
||||||
|
dv.paragraph("_No intel yet._");
|
||||||
|
} else {
|
||||||
|
dv.paragraph(`_Entries: ${rows.length} (own ${(THIS.Intel ?? []).length} + mentions ${Math.max(0, rows.length - (THIS.Intel ?? []).length)})_`);
|
||||||
|
dv.table(
|
||||||
|
["From Note","Date","Type","Summary","Session","Source","Rel","Cred","Tags"],
|
||||||
|
rows.map(r => [r.from, r.date, r.type, r.summary, r.session, r.source, r.rel, r.cred, r.tags])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```button
|
||||||
|
name ➕ Add Intel
|
||||||
|
type command
|
||||||
|
action Templater: Insert Add Intel Entry to NPC
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 🏛️ Relations & Factions
|
## 🏛️ Relations & Factions
|
||||||
- **Primary Faction:** [[Draycott Family]]
|
- **Primary Faction:** [[Draycott Family]]
|
||||||
- **Allied Factions:** [[House Valora]]
|
- **Allied Factions:** [[House Valora]]
|
||||||
|
|||||||
+98
@@ -48,6 +48,104 @@ Tags:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 🧠 Intel (auto)
|
||||||
|
```dataviewjs
|
||||||
|
const THIS = dv.current();
|
||||||
|
const thisName = String(THIS.file.name);
|
||||||
|
const thisLC = thisName.toLowerCase();
|
||||||
|
|
||||||
|
// Scan faction & NPC notes that have Intel arrays
|
||||||
|
const pages = dv.pages("#npc or #faction").where(p => p.Intel);
|
||||||
|
|
||||||
|
// Does an Intel entry reference THIS NPC?
|
||||||
|
function mentionsThisNPC(entry) {
|
||||||
|
const sum = String(entry.summary ?? "");
|
||||||
|
|
||||||
|
// 1) Explicit wikilink in summary
|
||||||
|
if (sum.includes(`[[${thisName}]]`) || sum.includes(`[[${thisName}|`)) return true;
|
||||||
|
|
||||||
|
// 2) Source field (string/link/list)
|
||||||
|
const src = entry.source;
|
||||||
|
const arr = Array.isArray(src) ? src : (src ? [src] : []);
|
||||||
|
for (const s of arr) {
|
||||||
|
if (typeof s === "string") {
|
||||||
|
// quoted link or plain text
|
||||||
|
const unq = s.replace(/^"+|"+$/g, ""); // drop surrounding quotes if any
|
||||||
|
if (unq.includes(`[[${thisName}]]`) || unq.includes(`[[${thisName}|`)) return true;
|
||||||
|
// if they typed just the name
|
||||||
|
if (unq.replace(/\[\[|\]\]/g, "").trim().toLowerCase() === thisLC) return true;
|
||||||
|
} else if (s && typeof s === "object" && s.path) {
|
||||||
|
// wikilink object
|
||||||
|
const base = s.path.split("/").pop().replace(/\.md$/i, "").toLowerCase();
|
||||||
|
if (base === thisLC) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional (looser): plain text mention in summary
|
||||||
|
// return sum.toLowerCase().includes(thisLC);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect rows: 1) own Intel, 2) mentions from other notes
|
||||||
|
const rows = [];
|
||||||
|
|
||||||
|
// Own Intel (from this NPC note)
|
||||||
|
for (const it of (THIS.Intel ?? [])) {
|
||||||
|
rows.push({
|
||||||
|
from: THIS.file.link,
|
||||||
|
date: it.date ?? "",
|
||||||
|
type: it.type ?? "",
|
||||||
|
summary: String(it.summary ?? ""),
|
||||||
|
session: it.session ?? "",
|
||||||
|
source: it.source ?? "",
|
||||||
|
rel: it.reliability ?? "",
|
||||||
|
cred: it.credibility ?? "",
|
||||||
|
tags: Array.isArray(it.tags) ? it.tags.join(", ") : (it.tags ?? "")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mentions elsewhere
|
||||||
|
for (const p of pages.where(p => p.file.path !== THIS.file.path)) {
|
||||||
|
for (const it of (p.Intel ?? [])) {
|
||||||
|
if (mentionsThisNPC(it)) {
|
||||||
|
rows.push({
|
||||||
|
from: p.file.link,
|
||||||
|
date: it.date ?? "",
|
||||||
|
type: it.type ?? "",
|
||||||
|
summary: String(it.summary ?? ""),
|
||||||
|
session: it.session ?? "",
|
||||||
|
source: it.source ?? "",
|
||||||
|
rel: it.reliability ?? "",
|
||||||
|
cred: it.credibility ?? "",
|
||||||
|
tags: Array.isArray(it.tags) ? it.tags.join(", ") : (it.tags ?? "")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort newest first
|
||||||
|
rows.sort((a,b) => String(b.date).localeCompare(String(a.date)));
|
||||||
|
|
||||||
|
if (!rows.length) {
|
||||||
|
dv.paragraph("_No intel yet._");
|
||||||
|
} else {
|
||||||
|
dv.paragraph(`_Entries: ${rows.length} (own ${(THIS.Intel ?? []).length} + mentions ${Math.max(0, rows.length - (THIS.Intel ?? []).length)})_`);
|
||||||
|
dv.table(
|
||||||
|
["From Note","Date","Type","Summary","Session","Source","Rel","Cred","Tags"],
|
||||||
|
rows.map(r => [r.from, r.date, r.type, r.summary, r.session, r.source, r.rel, r.cred, r.tags])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```button
|
||||||
|
name ➕ Add Intel
|
||||||
|
type command
|
||||||
|
action Templater: Insert Add Intel Entry to NPC
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 🏛️ Relations & Factions
|
## 🏛️ Relations & Factions
|
||||||
- **Primary Faction:** [[House Asase]]
|
- **Primary Faction:** [[House Asase]]
|
||||||
- **Allied Factions:** [[Faction A]], [[Faction B]]
|
- **Allied Factions:** [[Faction A]], [[Faction B]]
|
||||||
|
|||||||
+100
@@ -3,8 +3,10 @@ Level_of_Influence: 2
|
|||||||
Faction:
|
Faction:
|
||||||
Tags:
|
Tags:
|
||||||
- npc
|
- npc
|
||||||
|
Intel:
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
# Laughlin
|
# Laughlin
|
||||||
*Figment Grandhall Scholar, Solicitor, Schmoozer and Fundraiser*
|
*Figment Grandhall Scholar, Solicitor, Schmoozer and Fundraiser*
|
||||||
|
|
||||||
@@ -47,6 +49,104 @@ Tags:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 🧠 Intel (auto)
|
||||||
|
```dataviewjs
|
||||||
|
const THIS = dv.current();
|
||||||
|
const thisName = String(THIS.file.name);
|
||||||
|
const thisLC = thisName.toLowerCase();
|
||||||
|
|
||||||
|
// Scan faction & NPC notes that have Intel arrays
|
||||||
|
const pages = dv.pages("#npc or #faction").where(p => p.Intel);
|
||||||
|
|
||||||
|
// Does an Intel entry reference THIS NPC?
|
||||||
|
function mentionsThisNPC(entry) {
|
||||||
|
const sum = String(entry.summary ?? "");
|
||||||
|
|
||||||
|
// 1) Explicit wikilink in summary
|
||||||
|
if (sum.includes(`[[${thisName}]]`) || sum.includes(`[[${thisName}|`)) return true;
|
||||||
|
|
||||||
|
// 2) Source field (string/link/list)
|
||||||
|
const src = entry.source;
|
||||||
|
const arr = Array.isArray(src) ? src : (src ? [src] : []);
|
||||||
|
for (const s of arr) {
|
||||||
|
if (typeof s === "string") {
|
||||||
|
// quoted link or plain text
|
||||||
|
const unq = s.replace(/^"+|"+$/g, ""); // drop surrounding quotes if any
|
||||||
|
if (unq.includes(`[[${thisName}]]`) || unq.includes(`[[${thisName}|`)) return true;
|
||||||
|
// if they typed just the name
|
||||||
|
if (unq.replace(/\[\[|\]\]/g, "").trim().toLowerCase() === thisLC) return true;
|
||||||
|
} else if (s && typeof s === "object" && s.path) {
|
||||||
|
// wikilink object
|
||||||
|
const base = s.path.split("/").pop().replace(/\.md$/i, "").toLowerCase();
|
||||||
|
if (base === thisLC) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional (looser): plain text mention in summary
|
||||||
|
// return sum.toLowerCase().includes(thisLC);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect rows: 1) own Intel, 2) mentions from other notes
|
||||||
|
const rows = [];
|
||||||
|
|
||||||
|
// Own Intel (from this NPC note)
|
||||||
|
for (const it of (THIS.Intel ?? [])) {
|
||||||
|
rows.push({
|
||||||
|
from: THIS.file.link,
|
||||||
|
date: it.date ?? "",
|
||||||
|
type: it.type ?? "",
|
||||||
|
summary: String(it.summary ?? ""),
|
||||||
|
session: it.session ?? "",
|
||||||
|
source: it.source ?? "",
|
||||||
|
rel: it.reliability ?? "",
|
||||||
|
cred: it.credibility ?? "",
|
||||||
|
tags: Array.isArray(it.tags) ? it.tags.join(", ") : (it.tags ?? "")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mentions elsewhere
|
||||||
|
for (const p of pages.where(p => p.file.path !== THIS.file.path)) {
|
||||||
|
for (const it of (p.Intel ?? [])) {
|
||||||
|
if (mentionsThisNPC(it)) {
|
||||||
|
rows.push({
|
||||||
|
from: p.file.link,
|
||||||
|
date: it.date ?? "",
|
||||||
|
type: it.type ?? "",
|
||||||
|
summary: String(it.summary ?? ""),
|
||||||
|
session: it.session ?? "",
|
||||||
|
source: it.source ?? "",
|
||||||
|
rel: it.reliability ?? "",
|
||||||
|
cred: it.credibility ?? "",
|
||||||
|
tags: Array.isArray(it.tags) ? it.tags.join(", ") : (it.tags ?? "")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort newest first
|
||||||
|
rows.sort((a,b) => String(b.date).localeCompare(String(a.date)));
|
||||||
|
|
||||||
|
if (!rows.length) {
|
||||||
|
dv.paragraph("_No intel yet._");
|
||||||
|
} else {
|
||||||
|
dv.paragraph(`_Entries: ${rows.length} (own ${(THIS.Intel ?? []).length} + mentions ${Math.max(0, rows.length - (THIS.Intel ?? []).length)})_`);
|
||||||
|
dv.table(
|
||||||
|
["From Note","Date","Type","Summary","Session","Source","Rel","Cred","Tags"],
|
||||||
|
rows.map(r => [r.from, r.date, r.type, r.summary, r.session, r.source, r.rel, r.cred, r.tags])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```button
|
||||||
|
name ➕ Add Intel
|
||||||
|
type command
|
||||||
|
action Templater: Insert Add Intel Entry to NPC
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 🏛️ Relations & Factions
|
## 🏛️ Relations & Factions
|
||||||
- **Primary Faction:** [[Faction Name]]
|
- **Primary Faction:** [[Faction Name]]
|
||||||
- **Allied Factions:** [[Faction A]], [[Faction B]]
|
- **Allied Factions:** [[Faction A]], [[Faction B]]
|
||||||
|
|||||||
+98
@@ -40,6 +40,104 @@ Tags:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 🧠 Intel (auto)
|
||||||
|
```dataviewjs
|
||||||
|
const THIS = dv.current();
|
||||||
|
const thisName = String(THIS.file.name);
|
||||||
|
const thisLC = thisName.toLowerCase();
|
||||||
|
|
||||||
|
// Scan faction & NPC notes that have Intel arrays
|
||||||
|
const pages = dv.pages("#npc or #faction").where(p => p.Intel);
|
||||||
|
|
||||||
|
// Does an Intel entry reference THIS NPC?
|
||||||
|
function mentionsThisNPC(entry) {
|
||||||
|
const sum = String(entry.summary ?? "");
|
||||||
|
|
||||||
|
// 1) Explicit wikilink in summary
|
||||||
|
if (sum.includes(`[[${thisName}]]`) || sum.includes(`[[${thisName}|`)) return true;
|
||||||
|
|
||||||
|
// 2) Source field (string/link/list)
|
||||||
|
const src = entry.source;
|
||||||
|
const arr = Array.isArray(src) ? src : (src ? [src] : []);
|
||||||
|
for (const s of arr) {
|
||||||
|
if (typeof s === "string") {
|
||||||
|
// quoted link or plain text
|
||||||
|
const unq = s.replace(/^"+|"+$/g, ""); // drop surrounding quotes if any
|
||||||
|
if (unq.includes(`[[${thisName}]]`) || unq.includes(`[[${thisName}|`)) return true;
|
||||||
|
// if they typed just the name
|
||||||
|
if (unq.replace(/\[\[|\]\]/g, "").trim().toLowerCase() === thisLC) return true;
|
||||||
|
} else if (s && typeof s === "object" && s.path) {
|
||||||
|
// wikilink object
|
||||||
|
const base = s.path.split("/").pop().replace(/\.md$/i, "").toLowerCase();
|
||||||
|
if (base === thisLC) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional (looser): plain text mention in summary
|
||||||
|
// return sum.toLowerCase().includes(thisLC);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect rows: 1) own Intel, 2) mentions from other notes
|
||||||
|
const rows = [];
|
||||||
|
|
||||||
|
// Own Intel (from this NPC note)
|
||||||
|
for (const it of (THIS.Intel ?? [])) {
|
||||||
|
rows.push({
|
||||||
|
from: THIS.file.link,
|
||||||
|
date: it.date ?? "",
|
||||||
|
type: it.type ?? "",
|
||||||
|
summary: String(it.summary ?? ""),
|
||||||
|
session: it.session ?? "",
|
||||||
|
source: it.source ?? "",
|
||||||
|
rel: it.reliability ?? "",
|
||||||
|
cred: it.credibility ?? "",
|
||||||
|
tags: Array.isArray(it.tags) ? it.tags.join(", ") : (it.tags ?? "")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mentions elsewhere
|
||||||
|
for (const p of pages.where(p => p.file.path !== THIS.file.path)) {
|
||||||
|
for (const it of (p.Intel ?? [])) {
|
||||||
|
if (mentionsThisNPC(it)) {
|
||||||
|
rows.push({
|
||||||
|
from: p.file.link,
|
||||||
|
date: it.date ?? "",
|
||||||
|
type: it.type ?? "",
|
||||||
|
summary: String(it.summary ?? ""),
|
||||||
|
session: it.session ?? "",
|
||||||
|
source: it.source ?? "",
|
||||||
|
rel: it.reliability ?? "",
|
||||||
|
cred: it.credibility ?? "",
|
||||||
|
tags: Array.isArray(it.tags) ? it.tags.join(", ") : (it.tags ?? "")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort newest first
|
||||||
|
rows.sort((a,b) => String(b.date).localeCompare(String(a.date)));
|
||||||
|
|
||||||
|
if (!rows.length) {
|
||||||
|
dv.paragraph("_No intel yet._");
|
||||||
|
} else {
|
||||||
|
dv.paragraph(`_Entries: ${rows.length} (own ${(THIS.Intel ?? []).length} + mentions ${Math.max(0, rows.length - (THIS.Intel ?? []).length)})_`);
|
||||||
|
dv.table(
|
||||||
|
["From Note","Date","Type","Summary","Session","Source","Rel","Cred","Tags"],
|
||||||
|
rows.map(r => [r.from, r.date, r.type, r.summary, r.session, r.source, r.rel, r.cred, r.tags])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```button
|
||||||
|
name ➕ Add Intel
|
||||||
|
type command
|
||||||
|
action Templater: Insert Add Intel Entry to NPC
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 🏛️ Relations & Factions
|
## 🏛️ Relations & Factions
|
||||||
- **Primary Faction:** [[Faction Name]]
|
- **Primary Faction:** [[Faction Name]]
|
||||||
- **Allied Factions:** [[Faction A]], [[Faction B]]
|
- **Allied Factions:** [[Faction A]], [[Faction B]]
|
||||||
|
|||||||
+98
@@ -38,6 +38,104 @@ Tags:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 🧠 Intel (auto)
|
||||||
|
```dataviewjs
|
||||||
|
const THIS = dv.current();
|
||||||
|
const thisName = String(THIS.file.name);
|
||||||
|
const thisLC = thisName.toLowerCase();
|
||||||
|
|
||||||
|
// Scan faction & NPC notes that have Intel arrays
|
||||||
|
const pages = dv.pages("#npc or #faction").where(p => p.Intel);
|
||||||
|
|
||||||
|
// Does an Intel entry reference THIS NPC?
|
||||||
|
function mentionsThisNPC(entry) {
|
||||||
|
const sum = String(entry.summary ?? "");
|
||||||
|
|
||||||
|
// 1) Explicit wikilink in summary
|
||||||
|
if (sum.includes(`[[${thisName}]]`) || sum.includes(`[[${thisName}|`)) return true;
|
||||||
|
|
||||||
|
// 2) Source field (string/link/list)
|
||||||
|
const src = entry.source;
|
||||||
|
const arr = Array.isArray(src) ? src : (src ? [src] : []);
|
||||||
|
for (const s of arr) {
|
||||||
|
if (typeof s === "string") {
|
||||||
|
// quoted link or plain text
|
||||||
|
const unq = s.replace(/^"+|"+$/g, ""); // drop surrounding quotes if any
|
||||||
|
if (unq.includes(`[[${thisName}]]`) || unq.includes(`[[${thisName}|`)) return true;
|
||||||
|
// if they typed just the name
|
||||||
|
if (unq.replace(/\[\[|\]\]/g, "").trim().toLowerCase() === thisLC) return true;
|
||||||
|
} else if (s && typeof s === "object" && s.path) {
|
||||||
|
// wikilink object
|
||||||
|
const base = s.path.split("/").pop().replace(/\.md$/i, "").toLowerCase();
|
||||||
|
if (base === thisLC) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional (looser): plain text mention in summary
|
||||||
|
// return sum.toLowerCase().includes(thisLC);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect rows: 1) own Intel, 2) mentions from other notes
|
||||||
|
const rows = [];
|
||||||
|
|
||||||
|
// Own Intel (from this NPC note)
|
||||||
|
for (const it of (THIS.Intel ?? [])) {
|
||||||
|
rows.push({
|
||||||
|
from: THIS.file.link,
|
||||||
|
date: it.date ?? "",
|
||||||
|
type: it.type ?? "",
|
||||||
|
summary: String(it.summary ?? ""),
|
||||||
|
session: it.session ?? "",
|
||||||
|
source: it.source ?? "",
|
||||||
|
rel: it.reliability ?? "",
|
||||||
|
cred: it.credibility ?? "",
|
||||||
|
tags: Array.isArray(it.tags) ? it.tags.join(", ") : (it.tags ?? "")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mentions elsewhere
|
||||||
|
for (const p of pages.where(p => p.file.path !== THIS.file.path)) {
|
||||||
|
for (const it of (p.Intel ?? [])) {
|
||||||
|
if (mentionsThisNPC(it)) {
|
||||||
|
rows.push({
|
||||||
|
from: p.file.link,
|
||||||
|
date: it.date ?? "",
|
||||||
|
type: it.type ?? "",
|
||||||
|
summary: String(it.summary ?? ""),
|
||||||
|
session: it.session ?? "",
|
||||||
|
source: it.source ?? "",
|
||||||
|
rel: it.reliability ?? "",
|
||||||
|
cred: it.credibility ?? "",
|
||||||
|
tags: Array.isArray(it.tags) ? it.tags.join(", ") : (it.tags ?? "")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort newest first
|
||||||
|
rows.sort((a,b) => String(b.date).localeCompare(String(a.date)));
|
||||||
|
|
||||||
|
if (!rows.length) {
|
||||||
|
dv.paragraph("_No intel yet._");
|
||||||
|
} else {
|
||||||
|
dv.paragraph(`_Entries: ${rows.length} (own ${(THIS.Intel ?? []).length} + mentions ${Math.max(0, rows.length - (THIS.Intel ?? []).length)})_`);
|
||||||
|
dv.table(
|
||||||
|
["From Note","Date","Type","Summary","Session","Source","Rel","Cred","Tags"],
|
||||||
|
rows.map(r => [r.from, r.date, r.type, r.summary, r.session, r.source, r.rel, r.cred, r.tags])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```button
|
||||||
|
name ➕ Add Intel
|
||||||
|
type command
|
||||||
|
action Templater: Insert Add Intel Entry to NPC
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 🏛️ Relations & Factions
|
## 🏛️ Relations & Factions
|
||||||
- **Primary Faction:** [[Faction Name]]
|
- **Primary Faction:** [[Faction Name]]
|
||||||
- **Allied Factions:** [[Faction A]], [[Faction B]]
|
- **Allied Factions:** [[Faction A]], [[Faction B]]
|
||||||
|
|||||||
+98
@@ -47,6 +47,104 @@ Tags:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
## 🧠 Intel (auto)
|
||||||
|
```dataviewjs
|
||||||
|
const THIS = dv.current();
|
||||||
|
const thisName = String(THIS.file.name);
|
||||||
|
const thisLC = thisName.toLowerCase();
|
||||||
|
|
||||||
|
// Scan faction & NPC notes that have Intel arrays
|
||||||
|
const pages = dv.pages("#npc or #faction").where(p => p.Intel);
|
||||||
|
|
||||||
|
// Does an Intel entry reference THIS NPC?
|
||||||
|
function mentionsThisNPC(entry) {
|
||||||
|
const sum = String(entry.summary ?? "");
|
||||||
|
|
||||||
|
// 1) Explicit wikilink in summary
|
||||||
|
if (sum.includes(`[[${thisName}]]`) || sum.includes(`[[${thisName}|`)) return true;
|
||||||
|
|
||||||
|
// 2) Source field (string/link/list)
|
||||||
|
const src = entry.source;
|
||||||
|
const arr = Array.isArray(src) ? src : (src ? [src] : []);
|
||||||
|
for (const s of arr) {
|
||||||
|
if (typeof s === "string") {
|
||||||
|
// quoted link or plain text
|
||||||
|
const unq = s.replace(/^"+|"+$/g, ""); // drop surrounding quotes if any
|
||||||
|
if (unq.includes(`[[${thisName}]]`) || unq.includes(`[[${thisName}|`)) return true;
|
||||||
|
// if they typed just the name
|
||||||
|
if (unq.replace(/\[\[|\]\]/g, "").trim().toLowerCase() === thisLC) return true;
|
||||||
|
} else if (s && typeof s === "object" && s.path) {
|
||||||
|
// wikilink object
|
||||||
|
const base = s.path.split("/").pop().replace(/\.md$/i, "").toLowerCase();
|
||||||
|
if (base === thisLC) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional (looser): plain text mention in summary
|
||||||
|
// return sum.toLowerCase().includes(thisLC);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect rows: 1) own Intel, 2) mentions from other notes
|
||||||
|
const rows = [];
|
||||||
|
|
||||||
|
// Own Intel (from this NPC note)
|
||||||
|
for (const it of (THIS.Intel ?? [])) {
|
||||||
|
rows.push({
|
||||||
|
from: THIS.file.link,
|
||||||
|
date: it.date ?? "",
|
||||||
|
type: it.type ?? "",
|
||||||
|
summary: String(it.summary ?? ""),
|
||||||
|
session: it.session ?? "",
|
||||||
|
source: it.source ?? "",
|
||||||
|
rel: it.reliability ?? "",
|
||||||
|
cred: it.credibility ?? "",
|
||||||
|
tags: Array.isArray(it.tags) ? it.tags.join(", ") : (it.tags ?? "")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mentions elsewhere
|
||||||
|
for (const p of pages.where(p => p.file.path !== THIS.file.path)) {
|
||||||
|
for (const it of (p.Intel ?? [])) {
|
||||||
|
if (mentionsThisNPC(it)) {
|
||||||
|
rows.push({
|
||||||
|
from: p.file.link,
|
||||||
|
date: it.date ?? "",
|
||||||
|
type: it.type ?? "",
|
||||||
|
summary: String(it.summary ?? ""),
|
||||||
|
session: it.session ?? "",
|
||||||
|
source: it.source ?? "",
|
||||||
|
rel: it.reliability ?? "",
|
||||||
|
cred: it.credibility ?? "",
|
||||||
|
tags: Array.isArray(it.tags) ? it.tags.join(", ") : (it.tags ?? "")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort newest first
|
||||||
|
rows.sort((a,b) => String(b.date).localeCompare(String(a.date)));
|
||||||
|
|
||||||
|
if (!rows.length) {
|
||||||
|
dv.paragraph("_No intel yet._");
|
||||||
|
} else {
|
||||||
|
dv.paragraph(`_Entries: ${rows.length} (own ${(THIS.Intel ?? []).length} + mentions ${Math.max(0, rows.length - (THIS.Intel ?? []).length)})_`);
|
||||||
|
dv.table(
|
||||||
|
["From Note","Date","Type","Summary","Session","Source","Rel","Cred","Tags"],
|
||||||
|
rows.map(r => [r.from, r.date, r.type, r.summary, r.session, r.source, r.rel, r.cred, r.tags])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```button
|
||||||
|
name ➕ Add Intel
|
||||||
|
type command
|
||||||
|
action Templater: Insert Add Intel Entry to NPC
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
## 🏛️ Relations & Factions
|
## 🏛️ Relations & Factions
|
||||||
- **Primary Faction:** [[House Elidari]]
|
- **Primary Faction:** [[House Elidari]]
|
||||||
- **Allied Factions:** [[Faction A]], [[Faction B]]
|
- **Allied Factions:** [[Faction A]], [[Faction B]]
|
||||||
|
|||||||
+60
-14
@@ -8,6 +8,7 @@ Tags:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Session 10 - Wined, Dined and Influenced
|
# Session 10 - Wined, Dined and Influenced
|
||||||
|
|
||||||
> **Date:** `= this.Date` • **Name:** `= this.Session_Name`
|
> **Date:** `= this.Date` • **Name:** `= this.Session_Name`
|
||||||
@@ -16,19 +17,6 @@ Tags:
|
|||||||
|
|
||||||
## 📄 Session Summary (auto)
|
## 📄 Session Summary (auto)
|
||||||
<!-- SUMMARY-START -->
|
<!-- SUMMARY-START -->
|
||||||
At dinner, the party split across three tables to engage the city’s notables.
|
|
||||||
- **Val & Calen with [[Iniana Asase]]**
|
|
||||||
- Learned her activism roots in the Drenches and struggles with the [[Thin Wisps]].
|
|
||||||
- She advised the party to seek official **licensure** for legitimacy.
|
|
||||||
- Discussions focused on improving Y’athyr and how to confront entrenched problems.
|
|
||||||
- **[[Faylen Valora]] & Metternich with [[Ronan Elidari]], [[Amity Cornwallis]], [[Deidre Gille]]**
|
|
||||||
- [[Faylen Valora]]’s speech earned **+1 Influence with every notable**, though reactions varied.
|
|
||||||
- Later debate with Ronan won **+1 Influence with [[Amity Cornwallis**]].
|
|
||||||
- Metternich bonded with Amity privately, trading cynical and practical views on leadership and power.
|
|
||||||
- His attempt at Discovery with Deidre Gille failed.
|
|
||||||
- **Kite with Elthea Rinvale**
|
|
||||||
- Discussed her presence in Y’athyr, her business with the Consortium, and his reasons for leaving Castore.
|
|
||||||
- Conversation touched on Trackless vs. E’in society.
|
|
||||||
<!-- SUMMARY-END -->
|
<!-- SUMMARY-END -->
|
||||||
|
|
||||||
```button
|
```button
|
||||||
@@ -152,7 +140,65 @@ action Templater: Insert Generate Session Summary
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🧠 Intel Captured
|
## 🧠 Intel Captured (auto)
|
||||||
|
|
||||||
|
```dataviewjs
|
||||||
|
const THIS = dv.current();
|
||||||
|
const sessName = THIS.file.name;
|
||||||
|
const sessDate = THIS.Date ?? dv.current().file.frontmatter?.Date ?? "";
|
||||||
|
const sessLC = sessName.toLowerCase();
|
||||||
|
|
||||||
|
const pages = dv.pages("#npc or #faction").where(p => p.Intel);
|
||||||
|
|
||||||
|
function matchesSessionField(sessionField) {
|
||||||
|
const list = Array.isArray(sessionField) ? sessionField : [sessionField];
|
||||||
|
for (const s of list) {
|
||||||
|
if (!s) continue;
|
||||||
|
if (typeof s === "object" && s.path) {
|
||||||
|
const base = s.path.split("/").pop().replace(/\.md$/i,"");
|
||||||
|
if (base.toLowerCase() === sessLC) return true;
|
||||||
|
} else {
|
||||||
|
const text = String(s);
|
||||||
|
if (text.includes(`[[${sessName}]]`)) return true;
|
||||||
|
if (text.toLowerCase() === sessLC) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows = [];
|
||||||
|
for (const p of pages) {
|
||||||
|
for (const it of p.Intel) {
|
||||||
|
let ok = false;
|
||||||
|
if (it.session) ok = matchesSessionField(it.session);
|
||||||
|
if (!ok && sessDate && it.date) ok = String(it.date) === String(sessDate); // fallback
|
||||||
|
if (ok) {
|
||||||
|
rows.push({
|
||||||
|
from: p.file.link,
|
||||||
|
date: it.date ?? "",
|
||||||
|
type: it.type ?? "",
|
||||||
|
summary: String(it.summary ?? ""),
|
||||||
|
source: it.source ?? "",
|
||||||
|
rel: it.reliability ?? "",
|
||||||
|
cred: it.credibility ?? "",
|
||||||
|
tags: Array.isArray(it.tags) ? it.tags.join(", ") : (it.tags ?? "")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rows.sort((a,b)=> String(b.date).localeCompare(String(a.date)));
|
||||||
|
|
||||||
|
if (!rows.length) {
|
||||||
|
dv.paragraph("_No intel captured for this session yet._");
|
||||||
|
} else {
|
||||||
|
dv.table(
|
||||||
|
["From", "Date", "Type", "Summary", "Source", "Rel", "Cred", "Tags"],
|
||||||
|
rows.map(r => [r.from, r.date, r.type, r.summary, r.source, r.rel, r.cred, r.tags])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,128 @@
|
|||||||
|
<%*
|
||||||
|
/* Add Intel Entry (NPC) — clear, step-labeled prompts */
|
||||||
|
|
||||||
|
const file = app.workspace.getActiveFile();
|
||||||
|
if (!file) { new Notice("Open an NPC note first."); return; }
|
||||||
|
|
||||||
|
const today = tp.date.now("YYYY-MM-DD");
|
||||||
|
|
||||||
|
/* ——— Step 1: TYPE ——— */
|
||||||
|
new Notice("Step 1/7 — Type (what kind of intel?)");
|
||||||
|
const typeDisp = [
|
||||||
|
"interaction — direct conversation/action with this NPC",
|
||||||
|
"report — secondhand info relayed by someone else",
|
||||||
|
"rumor — unverified hearsay",
|
||||||
|
"asset — resource/contact identified or secured",
|
||||||
|
"op — operation/mission detail"
|
||||||
|
];
|
||||||
|
const typeVals = ["interaction","report","rumor","asset","op"];
|
||||||
|
const TYPE = await tp.system.suggester(typeDisp, typeVals);
|
||||||
|
if (!TYPE) { new Notice("Cancelled."); return; }
|
||||||
|
|
||||||
|
/* ——— Step 2: SUMMARY ——— */
|
||||||
|
new Notice("Step 2/7 — Summary (what happened?)");
|
||||||
|
const summary = await tp.system.prompt(
|
||||||
|
"Summary (1–2 sentences). You can include [[links]] to NPCs/factions/places.\nExample: Says that [[House Asase]] is rebuilding the Drenches walls."
|
||||||
|
) ?? "";
|
||||||
|
|
||||||
|
/* ——— Step 3: SESSION link (optional) ——— */
|
||||||
|
new Notice("Step 3/7 — Session link (optional)");
|
||||||
|
const allFiles = app.vault.getMarkdownFiles();
|
||||||
|
function hasSessionTag(f) {
|
||||||
|
const fm = app.metadataCache.getFileCache(f)?.frontmatter ?? {};
|
||||||
|
const tags = fm.Tags ?? fm.tags ?? [];
|
||||||
|
const arr = Array.isArray(tags) ? tags : [tags];
|
||||||
|
const lower = arr.map(x => String(x||"").toLowerCase());
|
||||||
|
return lower.includes("session") || lower.includes("pf2e");
|
||||||
|
}
|
||||||
|
const sessions = allFiles.filter(hasSessionTag).sort((a,b)=>{
|
||||||
|
const fa = app.metadataCache.getFileCache(a)?.frontmatter ?? {};
|
||||||
|
const fb = app.metadataCache.getFileCache(b)?.frontmatter ?? {};
|
||||||
|
const da = String(fa.Date||"");
|
||||||
|
const db = String(fb.Date||"");
|
||||||
|
return db.localeCompare(da) || b.basename.localeCompare(a.basename);
|
||||||
|
});
|
||||||
|
const sessionChoicesDisp = ["(skip — no session link)"].concat(
|
||||||
|
sessions.map(f=>{
|
||||||
|
const d = app.metadataCache.getFileCache(f)?.frontmatter?.Date ?? "";
|
||||||
|
return d ? `Link to session: [[${f.basename}]] — ${d}` : `Link to session: [[${f.basename}]]`;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const sessionChoicesVal = ["__skip__"].concat(sessions.map(f=>`[[${f.basename}]]`));
|
||||||
|
let sesPick = await tp.system.suggester(sessionChoicesDisp, sessionChoicesVal);
|
||||||
|
if (!sesPick) sesPick = "__skip__";
|
||||||
|
const sessionField = (sesPick === "__skip__") ? undefined : sesPick;
|
||||||
|
|
||||||
|
/* ——— Step 4: SOURCE (who/what told us?) ——— */
|
||||||
|
new Notice("Step 4/7 — Source (who/what told us?)");
|
||||||
|
const srcChoicesDisp = ["(skip)","(manual entry — type your own)"].concat(
|
||||||
|
allFiles.map(f=>`[[${f.basename}]]`).sort((a,b)=>a.localeCompare(b, undefined, {sensitivity:"base"}))
|
||||||
|
);
|
||||||
|
const srcChoicesVal = ["__skip__","__manual__"].concat(
|
||||||
|
allFiles.map(f=>`[[${f.basename}]]`).sort((a,b)=>a.localeCompare(b, undefined, {sensitivity:"base"}))
|
||||||
|
);
|
||||||
|
let srcPick = await tp.system.suggester(srcChoicesDisp, srcChoicesVal);
|
||||||
|
if (!srcPick) srcPick = "__skip__";
|
||||||
|
let sourceField = "";
|
||||||
|
if (srcPick === "__manual__") {
|
||||||
|
sourceField = (await tp.system.prompt("Type any source (free text or [[Link]])")) ?? "";
|
||||||
|
} else if (srcPick !== "__skip__") {
|
||||||
|
sourceField = srcPick;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ——— Step 5: RELIABILITY (A–E) ——— */
|
||||||
|
new Notice("Step 5/7 — Reliability (A–E)");
|
||||||
|
const relDisp = [
|
||||||
|
"A — confirmed (firsthand/corroborated)",
|
||||||
|
"B — likely (trustworthy witness/evidence)",
|
||||||
|
"C — uncertain (unverified details)",
|
||||||
|
"D — doubtful (conflicts / low trust)",
|
||||||
|
"E — unknown (no basis to judge)"
|
||||||
|
];
|
||||||
|
const relVals = ["A","B","C","D","E"];
|
||||||
|
const reliability = await tp.system.suggester(relDisp, relVals) ?? "C";
|
||||||
|
|
||||||
|
/* ——— Step 6: CREDIBILITY (1–5) ——— */
|
||||||
|
new Notice("Step 6/7 — Credibility (1–5)");
|
||||||
|
const credDisp = ["5 — strong","4 — good","3 — fair","2 — weak","1 — very weak"];
|
||||||
|
const credVals = ["5","4","3","2","1"];
|
||||||
|
const credibility = Number(await tp.system.suggester(credDisp, credVals) ?? "3");
|
||||||
|
|
||||||
|
/* ——— Step 7: TAGS (multi-select, optional) ——— */
|
||||||
|
new Notice("Step 7/7 — Tags (optional)");
|
||||||
|
const tagMap = app.metadataCache.getTags?.() ?? {};
|
||||||
|
let allTags = Array.from(new Set(Object.keys(tagMap).map(t => t.replace(/^#/, ""))))
|
||||||
|
.sort((a,b)=>a.localeCompare(b, undefined, {sensitivity:"base"}));
|
||||||
|
const chosen = new Set();
|
||||||
|
while (true) {
|
||||||
|
const remaining = allTags.filter(t => !chosen.has(t));
|
||||||
|
const disp = ["(done)","(new tag…)"].concat(remaining.map(t=>`#${t}`));
|
||||||
|
const vals = ["__done__","__new__"].concat(remaining);
|
||||||
|
const pick = await tp.system.suggester(disp, vals);
|
||||||
|
if (!pick || pick === "__done__") break;
|
||||||
|
if (pick === "__new__") {
|
||||||
|
const nt = (await tp.system.prompt("New tag (without #)"))?.trim();
|
||||||
|
if (nt) { chosen.add(nt); if (!allTags.includes(nt)) allTags.push(nt); }
|
||||||
|
} else {
|
||||||
|
chosen.add(pick);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const tags = Array.from(chosen);
|
||||||
|
|
||||||
|
/* ——— Write to frontmatter ——— */
|
||||||
|
await app.fileManager.processFrontMatter(file, (fm) => {
|
||||||
|
if (!Array.isArray(fm.Intel)) fm.Intel = [];
|
||||||
|
fm.Intel.unshift({
|
||||||
|
date: today,
|
||||||
|
type: TYPE,
|
||||||
|
summary,
|
||||||
|
session: sessionField, // shows up in NPC/Session tables
|
||||||
|
source: sourceField || undefined, // e.g., [[Ronan Elidari]] or free text
|
||||||
|
reliability,
|
||||||
|
credibility,
|
||||||
|
tags
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
new Notice("NPC Intel entry added.");
|
||||||
|
%>
|
||||||
@@ -1,101 +1,120 @@
|
|||||||
<%*
|
<%*
|
||||||
/* PF2e AI Session Summary — FULL NOTE MODE (single-string call)
|
/* PF2e AI Session Summary — CHUNKED + ROBUST (AI for Templater)
|
||||||
- Uses the whole note body (not just bullets)
|
- Summarizes each section separately to avoid long-context issues
|
||||||
- Strips frontmatter, code fences, and prior summaries/history
|
- Preserves [[wikilinks]]; strips headings/labels/scaffolding
|
||||||
- Lets you pick Short / Standard / Detailed length
|
- Writes the final recap between SUMMARY markers
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const file = app.workspace.getActiveFile();
|
const file = app.workspace.getActiveFile();
|
||||||
if (!file) { new Notice("Open a session note first."); return; }
|
if (!file) { new Notice("Open a session note first."); return; }
|
||||||
|
|
||||||
/* ---------- Read & sanitize whole note ---------- */
|
// ---------- read & basic cleaning ----------
|
||||||
let body = await app.vault.read(file);
|
let raw = await app.vault.read(file);
|
||||||
|
raw = raw.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, ""); // strip YAML
|
||||||
|
raw = raw.replace(/<!--\s*SUMMARY-START\s*-->[\s\S]*?<!--\s*SUMMARY-END\s*-->/gi, "");
|
||||||
|
raw = raw.replace(/```[\s\S]*?```/g, ""); // code fences
|
||||||
|
raw = raw.replace(/`=\s*[^`]+`/g, ""); // inline dataview
|
||||||
|
raw = raw.replace(/\r/g, "");
|
||||||
|
|
||||||
// strip YAML frontmatter
|
// ---------- helpers ----------
|
||||||
body = body.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "");
|
function sectionBetween(names){
|
||||||
|
for (const name of names){
|
||||||
|
const reHead = new RegExp(`^##\\s*.*${name}.*$`,"im");
|
||||||
|
const m = raw.match(reHead);
|
||||||
|
if (!m) continue;
|
||||||
|
const start = m.index + m[0].length;
|
||||||
|
const rest = raw.slice(start);
|
||||||
|
const next = rest.search(/^##\s+/m);
|
||||||
|
return raw.slice(start, next === -1 ? raw.length : start + next).trim();
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
function linesClean(block){
|
||||||
|
if (!block) return [];
|
||||||
|
const BOLD_LABEL=/^\s*(?:[-*]\s+)?\*\*[^*]+:\*\*\s*$/i; // **Label:**
|
||||||
|
const ROUND=/^\s*(?:[-*]\s*)?round\s+\d+\s*$/i;
|
||||||
|
const HR=/^\s*[-*_]{3,}\s*$/;
|
||||||
|
const QUOTE=/^\s*>/;
|
||||||
|
const H=/^\s*#{1,6}\s+/;
|
||||||
|
return block.split("\n")
|
||||||
|
.map(s=>s.trim())
|
||||||
|
.filter(s=>s && !BOLD_LABEL.test(s) && !ROUND.test(s) && !HR.test(s) && !QUOTE.test(s) && !H.test(s))
|
||||||
|
.map(s=>s.replace(/\[\[\s*\]\]/g,""))
|
||||||
|
.map(s=>s.replace(/\s+—\s*$/,""));
|
||||||
|
}
|
||||||
|
|
||||||
// strip prior summaries & history blocks if present
|
// ---------- pull sections (accept emoji or plain headings) ----------
|
||||||
body = body.replace(/<!--\s*SUMMARY-START\s*-->[\s\S]*?<!--\s*SUMMARY-END\s*-->/gi, "");
|
const S = {
|
||||||
body = body.replace(/<!--\s*SUMMARY-HISTORY-START\s*-->[\s\S]*?<!--\s*SUMMARY-HISTORY-END\s*-->/gi, "");
|
whereWhen : sectionBetween(["Where / When"]),
|
||||||
|
pcs : sectionBetween(["PCs Present / Absent","PCs Present","Present / Absent"]),
|
||||||
|
highlights: sectionBetween(["⭐ Highlights","Highlights"]),
|
||||||
|
quests : sectionBetween(["🎯 Quests & Progress","Quests & Progress","Quests"]),
|
||||||
|
npcs : sectionBetween(["🗣️ NPCs Met / Factions","NPCs Met / Factions","NPCs / Factions","NPCs"]),
|
||||||
|
encounters: sectionBetween(["⚔️ Encounters","Encounters"]),
|
||||||
|
intel : sectionBetween(["🧠 Intel Captured","Intel Captured"]),
|
||||||
|
treasure : sectionBetween(["📦 Treasure & Rewards","Treasure & Rewards","Treasure"]),
|
||||||
|
nextHooks : sectionBetween(["🔜 Next Hooks / To-Dos","Next Hooks / To-Dos","Next Hooks","Hooks"]),
|
||||||
|
};
|
||||||
|
|
||||||
// strip code fences & inline dataview
|
// ---------- chunk prompts ----------
|
||||||
body = body.replace(/```[\s\S]*?```/g, "");
|
async function summarizeBullets(label, text, max=6){
|
||||||
body = body.replace(/`=\s*[^`]+`/g, "");
|
text = linesClean(text).join("\n").trim();
|
||||||
|
if (!text) return [];
|
||||||
|
const prompt = `You are a concise Pathfinder 2e GM assistant.
|
||||||
|
Summarize the "${label}" section below as **bullet points only** (max ${max}).
|
||||||
|
Keep existing [[wikilinks]] exactly as written. Do not echo headings, labels, or placeholders.
|
||||||
|
|
||||||
// normalize whitespace
|
=== ${label.toUpperCase()} START ===
|
||||||
body = body.replace(/\r/g, "").trim();
|
${text}
|
||||||
|
=== ${label.toUpperCase()} END ===`;
|
||||||
|
const out = await tp.ai.chat(prompt);
|
||||||
|
return String(out)
|
||||||
|
.split(/\n+/)
|
||||||
|
.map(s=>s.replace(/^\s*[-*•]\s*/,"").trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
.slice(0, max);
|
||||||
|
}
|
||||||
|
|
||||||
// If the note is very long, cap input (you can raise this safely)
|
async function oneLineRecap(name, date, allBullets){
|
||||||
const MAX_INPUT_CHARS = 40000;
|
const prompt = `Write a **single bold one-line recap** for a PF2e session called "${name}" on ${date}.
|
||||||
if (body.length > MAX_INPUT_CHARS) body = body.slice(0, MAX_INPUT_CHARS);
|
Base it ONLY on these bullets:\n${allBullets.map(b=>`- ${b}`).join("\n")}\nReturn exactly one bold line.`;
|
||||||
|
return String(await tp.ai.chat(prompt)).trim();
|
||||||
|
}
|
||||||
|
|
||||||
/* ---------- Metadata ---------- */
|
// ---------- run chunked summaries ----------
|
||||||
const cache = app.metadataCache.getFileCache(file) || {};
|
const cache = app.metadataCache.getFileCache(file) || {};
|
||||||
const fm = cache.frontmatter || {};
|
const fm = cache.frontmatter || {};
|
||||||
const seshName = fm.Session_Name || tp.file.title || file.basename;
|
const seshName = fm.Session_Name || tp.file.title || file.basename;
|
||||||
const seshDate = fm.Date || tp.date.now("YYYY-MM-DD");
|
const seshDate = fm.Date || tp.date.now("YYYY-MM-DD");
|
||||||
|
|
||||||
/* ---------- Length selector ---------- */
|
const hi = await summarizeBullets("Highlights", S.highlights, 8);
|
||||||
const lenPick = await tp.system.suggester(
|
const enc = await summarizeBullets("Encounters", S.encounters, 6);
|
||||||
["Short (~6 bullets)","Standard (~12 bullets)","Detailed (~20 bullets)"],
|
const intel = await summarizeBullets("Intel", S.intel, 6);
|
||||||
["short","standard","detailed"]
|
const quests= await summarizeBullets("Quests", S.quests, 6);
|
||||||
) ?? "standard";
|
const nexts = await summarizeBullets("Next Hooks", S.nextHooks, 6);
|
||||||
const bulletTarget = (lenPick === "short") ? 6 : (lenPick === "detailed") ? 20 : 12;
|
const npcfx = await summarizeBullets("NPCs & Factions", S.npcs, 1); // we’ll show names inline instead of bullets
|
||||||
|
const loot = await summarizeBullets("Treasure & Rewards", S.treasure, 3);
|
||||||
|
|
||||||
/* ---------- Build single-string prompt ---------- */
|
const allForRecap = [].concat(hi, enc, intel, quests, nexts).slice(0, 24);
|
||||||
const prompt = `You are a concise Pathfinder 2e GM assistant. Summarize the FULL session note below.
|
let oneLine = await oneLineRecap(seshName, seshDate, allForRecap);
|
||||||
|
if (!oneLine.startsWith("**")) oneLine = `**${seshName} — ${seshDate}:** ${allForRecap[0] || "Session recap."}`;
|
||||||
|
|
||||||
Return ONLY:
|
// ---------- compose final ----------
|
||||||
**One-line recap (bold)**
|
const out = [];
|
||||||
- Key beats (bulleted, chronological, ${bulletTarget} bullets max)
|
out.push(oneLine);
|
||||||
- NPCs & factions touched (preserve existing [[wikilinks]] verbatim if present)
|
if (hi.length) out.push(...hi.map(b=>`- ${b}`));
|
||||||
- Encounters/checks/outcomes (brief)
|
if (enc.length) out.push(...enc.map(b=>`- ${b}`));
|
||||||
- Loot/rewards/XP (brief)
|
if (intel.length) out.push(...intel.map(b=>`- ${b}`));
|
||||||
- Next hooks/to-dos (bullets)
|
if (quests.length)out.push(...quests.map(b=>`- ${b}`));
|
||||||
|
if (npcfx.length) out.push(`- NPCs/Factions touched: ${npcfx[0].replace(/^NPCs?:\s*/i,"")}`);
|
||||||
|
if (loot.length) out.push(...loot.map(b=>`- ${b}`));
|
||||||
|
if (nexts.length) out.push(...nexts.map(b=>`- ${b}`));
|
||||||
|
|
||||||
DO NOT echo raw headings, “Highlight::”, or placeholder text. Cover ALL major content, not just bullet lists.
|
// ---------- inject between markers ----------
|
||||||
|
const START="<!-- SUMMARY-START -->", END="<!-- SUMMARY-END -->";
|
||||||
Session: "${seshName}" on ${seshDate}
|
|
||||||
|
|
||||||
=== NOTE START ===
|
|
||||||
${body}
|
|
||||||
=== NOTE END ===`;
|
|
||||||
|
|
||||||
/* ---------- Call AI (string-only) ---------- */
|
|
||||||
let summary = "";
|
|
||||||
try {
|
|
||||||
const resp = await tp.ai.chat(prompt);
|
|
||||||
summary = (typeof resp === "string") ? resp : (resp?.content ?? resp?.text ?? "");
|
|
||||||
} catch (e) {
|
|
||||||
console.error("AI error:", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------- Fallback if model returns empty ---------- */
|
|
||||||
if (!summary || !summary.trim()) {
|
|
||||||
// Build a tidy, non-noisy fallback from the whole note (no headings/labels)
|
|
||||||
const lines = body
|
|
||||||
.split("\n")
|
|
||||||
.map(s => s.trim())
|
|
||||||
.filter(s => s && !/^#{1,6}\s/.test(s)) // no markdown headings
|
|
||||||
.filter(s => !/^\*\*[^*]+:\*\*$/.test(s)) // no bold-only section labels
|
|
||||||
.filter(s => !/^Highlight::/i.test(s)) // no "Highlight::"
|
|
||||||
.filter(s => !/^Round\s+\d+/i.test(s)) // no "Round X"
|
|
||||||
.filter(s => s !== "[[ ]]"); // no empty wikilinks
|
|
||||||
|
|
||||||
const oneLine = `**${seshName} — ${seshDate}:** ${lines[0] || "Session recap unavailable."}`;
|
|
||||||
const bullets = lines.slice(1, 1 + Math.min(20, bulletTarget + 4)).map(s => `- ${s}`);
|
|
||||||
summary = [oneLine, ...bullets].join("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------- Inject between SUMMARY markers ---------- */
|
|
||||||
const START = "<!-- SUMMARY-START -->";
|
|
||||||
const END = "<!-- SUMMARY-END -->";
|
|
||||||
let content = await app.vault.read(file);
|
let content = await app.vault.read(file);
|
||||||
const sIdx = content.indexOf(START), eIdx = content.indexOf(END);
|
const sIdx = content.indexOf(START), eIdx = content.indexOf(END);
|
||||||
if (sIdx===-1 || eIdx===-1 || eIdx<sIdx){ new Notice("Could not find SUMMARY markers."); return; }
|
if (sIdx===-1 || eIdx===-1 || eIdx<sIdx){ new Notice("Could not find SUMMARY markers."); return; }
|
||||||
|
await app.vault.modify(file, content.slice(0, sIdx+START.length) + "\n" + out.join("\n") + "\n" + content.slice(eIdx));
|
||||||
const before = content.slice(0, sIdx + START.length);
|
new Notice("AI (chunked) session summary generated.");
|
||||||
const after = content.slice(eIdx);
|
|
||||||
await app.vault.modify(file, before + "\n" + summary.trim() + "\n" + after);
|
|
||||||
new Notice("AI session summary generated.");
|
|
||||||
%>
|
%>
|
||||||
|
|||||||
@@ -38,6 +38,104 @@ Tags: [npc]
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 🧠 Intel (auto)
|
||||||
|
```dataviewjs
|
||||||
|
const THIS = dv.current();
|
||||||
|
const thisName = String(THIS.file.name);
|
||||||
|
const thisLC = thisName.toLowerCase();
|
||||||
|
|
||||||
|
// Scan faction & NPC notes that have Intel arrays
|
||||||
|
const pages = dv.pages("#npc or #faction").where(p => p.Intel);
|
||||||
|
|
||||||
|
// Does an Intel entry reference THIS NPC?
|
||||||
|
function mentionsThisNPC(entry) {
|
||||||
|
const sum = String(entry.summary ?? "");
|
||||||
|
|
||||||
|
// 1) Explicit wikilink in summary
|
||||||
|
if (sum.includes(`[[${thisName}]]`) || sum.includes(`[[${thisName}|`)) return true;
|
||||||
|
|
||||||
|
// 2) Source field (string/link/list)
|
||||||
|
const src = entry.source;
|
||||||
|
const arr = Array.isArray(src) ? src : (src ? [src] : []);
|
||||||
|
for (const s of arr) {
|
||||||
|
if (typeof s === "string") {
|
||||||
|
// quoted link or plain text
|
||||||
|
const unq = s.replace(/^"+|"+$/g, ""); // drop surrounding quotes if any
|
||||||
|
if (unq.includes(`[[${thisName}]]`) || unq.includes(`[[${thisName}|`)) return true;
|
||||||
|
// if they typed just the name
|
||||||
|
if (unq.replace(/\[\[|\]\]/g, "").trim().toLowerCase() === thisLC) return true;
|
||||||
|
} else if (s && typeof s === "object" && s.path) {
|
||||||
|
// wikilink object
|
||||||
|
const base = s.path.split("/").pop().replace(/\.md$/i, "").toLowerCase();
|
||||||
|
if (base === thisLC) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional (looser): plain text mention in summary
|
||||||
|
// return sum.toLowerCase().includes(thisLC);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect rows: 1) own Intel, 2) mentions from other notes
|
||||||
|
const rows = [];
|
||||||
|
|
||||||
|
// Own Intel (from this NPC note)
|
||||||
|
for (const it of (THIS.Intel ?? [])) {
|
||||||
|
rows.push({
|
||||||
|
from: THIS.file.link,
|
||||||
|
date: it.date ?? "",
|
||||||
|
type: it.type ?? "",
|
||||||
|
summary: String(it.summary ?? ""),
|
||||||
|
session: it.session ?? "",
|
||||||
|
source: it.source ?? "",
|
||||||
|
rel: it.reliability ?? "",
|
||||||
|
cred: it.credibility ?? "",
|
||||||
|
tags: Array.isArray(it.tags) ? it.tags.join(", ") : (it.tags ?? "")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mentions elsewhere
|
||||||
|
for (const p of pages.where(p => p.file.path !== THIS.file.path)) {
|
||||||
|
for (const it of (p.Intel ?? [])) {
|
||||||
|
if (mentionsThisNPC(it)) {
|
||||||
|
rows.push({
|
||||||
|
from: p.file.link,
|
||||||
|
date: it.date ?? "",
|
||||||
|
type: it.type ?? "",
|
||||||
|
summary: String(it.summary ?? ""),
|
||||||
|
session: it.session ?? "",
|
||||||
|
source: it.source ?? "",
|
||||||
|
rel: it.reliability ?? "",
|
||||||
|
cred: it.credibility ?? "",
|
||||||
|
tags: Array.isArray(it.tags) ? it.tags.join(", ") : (it.tags ?? "")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort newest first
|
||||||
|
rows.sort((a,b) => String(b.date).localeCompare(String(a.date)));
|
||||||
|
|
||||||
|
if (!rows.length) {
|
||||||
|
dv.paragraph("_No intel yet._");
|
||||||
|
} else {
|
||||||
|
dv.paragraph(`_Entries: ${rows.length} (own ${(THIS.Intel ?? []).length} + mentions ${Math.max(0, rows.length - (THIS.Intel ?? []).length)})_`);
|
||||||
|
dv.table(
|
||||||
|
["From Note","Date","Type","Summary","Session","Source","Rel","Cred","Tags"],
|
||||||
|
rows.map(r => [r.from, r.date, r.type, r.summary, r.session, r.source, r.rel, r.cred, r.tags])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```button
|
||||||
|
name ➕ Add Intel
|
||||||
|
type command
|
||||||
|
action Templater: Insert Add Intel Entry to NPC
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 🏛️ Relations & Factions
|
## 🏛️ Relations & Factions
|
||||||
- **Primary Faction:** [[Faction Name]]
|
- **Primary Faction:** [[Faction Name]]
|
||||||
- **Allied Factions:** [[Faction A]], [[Faction B]]
|
- **Allied Factions:** [[Faction A]], [[Faction B]]
|
||||||
|
|||||||
@@ -84,7 +84,65 @@ templater true
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🧠 Intel Captured
|
## 🧠 Intel Captured (auto)
|
||||||
|
|
||||||
|
```dataviewjs
|
||||||
|
const THIS = dv.current();
|
||||||
|
const sessName = THIS.file.name;
|
||||||
|
const sessDate = THIS.Date ?? dv.current().file.frontmatter?.Date ?? "";
|
||||||
|
const sessLC = sessName.toLowerCase();
|
||||||
|
|
||||||
|
const pages = dv.pages("#npc or #faction").where(p => p.Intel);
|
||||||
|
|
||||||
|
function matchesSessionField(sessionField) {
|
||||||
|
const list = Array.isArray(sessionField) ? sessionField : [sessionField];
|
||||||
|
for (const s of list) {
|
||||||
|
if (!s) continue;
|
||||||
|
if (typeof s === "object" && s.path) {
|
||||||
|
const base = s.path.split("/").pop().replace(/\.md$/i,"");
|
||||||
|
if (base.toLowerCase() === sessLC) return true;
|
||||||
|
} else {
|
||||||
|
const text = String(s);
|
||||||
|
if (text.includes(`[[${sessName}]]`)) return true;
|
||||||
|
if (text.toLowerCase() === sessLC) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows = [];
|
||||||
|
for (const p of pages) {
|
||||||
|
for (const it of p.Intel) {
|
||||||
|
let ok = false;
|
||||||
|
if (it.session) ok = matchesSessionField(it.session);
|
||||||
|
if (!ok && sessDate && it.date) ok = String(it.date) === String(sessDate); // fallback
|
||||||
|
if (ok) {
|
||||||
|
rows.push({
|
||||||
|
from: p.file.link,
|
||||||
|
date: it.date ?? "",
|
||||||
|
type: it.type ?? "",
|
||||||
|
summary: String(it.summary ?? ""),
|
||||||
|
source: it.source ?? "",
|
||||||
|
rel: it.reliability ?? "",
|
||||||
|
cred: it.credibility ?? "",
|
||||||
|
tags: Array.isArray(it.tags) ? it.tags.join(", ") : (it.tags ?? "")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rows.sort((a,b)=> String(b.date).localeCompare(String(a.date)));
|
||||||
|
|
||||||
|
if (!rows.length) {
|
||||||
|
dv.paragraph("_No intel captured for this session yet._");
|
||||||
|
} else {
|
||||||
|
dv.table(
|
||||||
|
["From", "Date", "Type", "Summary", "Source", "Rel", "Cred", "Tags"],
|
||||||
|
rows.map(r => [r.from, r.date, r.type, r.summary, r.source, r.rel, r.cred, r.tags])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user