Skip to content

Commit 3d8b5cb

Browse files
committed
relations tabl
1 parent 871b339 commit 3d8b5cb

File tree

110 files changed

+117282
-1579
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

110 files changed

+117282
-1579
lines changed
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
name: Generate Relation Data
2+
3+
on:
4+
workflow_dispatch: # Manual trigger
5+
schedule:
6+
- cron: '0 0 * * 0' # Weekly on Sunday at midnight
7+
8+
jobs:
9+
generate:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- name: Checkout repository
13+
uses: actions/checkout@v3
14+
15+
- name: Setup Node.js
16+
uses: actions/setup-node@v3
17+
with:
18+
node-version: '18'
19+
20+
- name: Install jq
21+
run: sudo apt-get update && sudo apt-get install -y jq
22+
23+
- name: Get relationship types
24+
working-directory: relation
25+
run: |
26+
curl -G "https://sparql.vanderbilt.edu/sparql" \
27+
--data-urlencode 'query=
28+
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
29+
30+
SELECT DISTINCT ?subject ?label
31+
WHERE {
32+
VALUES (?collection) {
33+
(<http://syriaca.org/taxonomy/directed-relations-collection>)
34+
(<http://syriaca.org/taxonomy/mutual-relations-collection>)
35+
}
36+
?collection skos:member ?subject .
37+
?subject skos:prefLabel ?label .
38+
}
39+
ORDER BY ?label
40+
' \
41+
-H "Accept: application/sparql-results+json" > relationship_types.json
42+
43+
echo "✓ Relationship types retrieved"
44+
jq '.results.bindings | length' relationship_types.json
45+
46+
- name: Query all relation factoids
47+
working-directory: relation
48+
run: |
49+
chmod +x batch_query_relationships.sh
50+
./batch_query_relationships.sh
51+
52+
echo "✓ All relation factoids generated"
53+
ls -lh all_relation_factoids.json
54+
55+
- name: Filter relation factoids
56+
working-directory: relation
57+
run: |
58+
node filter_factoids.js
59+
60+
echo "✓ Filtered relation factoids generated"
61+
ls -lh filtered_relation_factoids.json
62+
63+
- name: Query person relation factoids
64+
working-directory: relation
65+
run: |
66+
chmod +x batch_query_person_relationship.sh
67+
./batch_query_person_relationship.sh
68+
69+
echo "✓ Person relation factoids generated"
70+
ls -lh all_person_relation_factoids.json
71+
72+
- name: Commit and push changes
73+
run: |
74+
git config --local user.email "github-actions[bot]@users.noreply.github.com"
75+
git config --local user.name "github-actions[bot]"
76+
git add relation/*.json
77+
git diff --staged --quiet || git commit -m "chore: update relation data files [skip ci]"
78+
git push

broken_caching_menu.js

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
export const SPARQL_ENDPOINT = "https://sparql.vanderbilt.edu/sparql";
2+
3+
4+
// Add filter menu options
5+
// Example: wrap your SPARQL calls
6+
// export async function getEventKeywords() {
7+
// return fetchWithCache('eventKeywords', async () => {
8+
// const response = await fetch('../menus/events.json');
9+
10+
// if (!response.ok) throw new Error('Failed to load event keywords');
11+
// return response.json();
12+
// });
13+
// }
14+
15+
16+
17+
export const getEventKeywords = () => `
18+
PREFIX swdt: <http://syriaca.org/prop/direct/>
19+
SELECT DISTINCT ?keyword
20+
FROM <https://spear-prosop.org>
21+
WHERE {
22+
?event swdt:event-keyword ?keyword .
23+
}
24+
ORDER BY ?keyword
25+
`;
26+
27+
export const getRelationshipOptions = () => `
28+
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
29+
SELECT DISTINCT ?collectionType ?subject ?label
30+
WHERE {
31+
VALUES (?collection ?collectionType) {
32+
(<http://syriaca.org/taxonomy/directed-relations-collection> "directed")
33+
(<http://syriaca.org/taxonomy/mutual-relations-collection> "mutual")
34+
}
35+
?collection skos:member ?subject .
36+
?subject skos:prefLabel ?label .
37+
}
38+
ORDER BY ?collectionType ?label
39+
`;
40+
41+
export const getEthnicityOptions = () => `
42+
PREFIX swdt: <http://syriaca.org/prop/direct/>
43+
SELECT DISTINCT ?ethnicity
44+
FROM <https://spear-prosop.org>
45+
WHERE {
46+
?person swdt:ethnic-label ?ethnicity .
47+
}
48+
ORDER BY ?ethnicity
49+
`;
50+
51+
export const getPlaceOptions = () => `
52+
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
53+
PREFIX swdt: <http://syriaca.org/prop/direct/>
54+
SELECT DISTINCT ?place ?label
55+
FROM <http://syriaca.org/geo#graph>
56+
FROM <https://spear-prosop.org>
57+
WHERE {
58+
{
59+
?person swdt:residence ?place .
60+
} UNION {
61+
?person swdt:birth-place ?place .
62+
} UNION {
63+
?person swdt:death-place ?place .
64+
} UNION {
65+
?event swdt:event-place ?place .
66+
} UNION {
67+
?person swdt:citizenship ?place .
68+
}
69+
?place rdfs:label ?label .
70+
FILTER(LANG(?label) = "en")
71+
}
72+
ORDER BY ?label
73+
`;
74+
75+
export const getEducationFieldsOfStudy = () => `
76+
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
77+
SELECT DISTINCT ?subject ?label
78+
WHERE {
79+
<http://syriaca.org/taxonomy/fields-of-study-collection> skos:member ?subject .
80+
?subject skos:prefLabel ?label .
81+
}
82+
ORDER BY ?label
83+
`;
84+
export const getOccupationOptions = () => `
85+
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
86+
SELECT DISTINCT ?occupation ?label
87+
WHERE {
88+
<http://syriaca.org/taxonomy/occupations-collection> skos:member ?occupation .
89+
?occupation skos:prefLabel ?label .
90+
}
91+
ORDER BY ?label
92+
`;
93+
94+
/**
95+
* Populates a <select> element with options from a SPARQL query above
96+
* Results are cached for 24 hours to reduce server load
97+
*/
98+
export async function populateDropdown(query, dropdownId, labelField = "label", valueField = "value") {
99+
const cacheKey = `dropdown_${dropdownId}`;
100+
101+
try {
102+
const data = await fetchWithCache(cacheKey, async () => {
103+
const res = await fetch(`${SPARQL_ENDPOINT}?query=${encodeURIComponent(query)}`, {
104+
headers: { Accept: 'application/sparql-results+json' }
105+
});
106+
return res.json();
107+
});
108+
109+
const select = document.getElementById(dropdownId);
110+
if (!select) return;
111+
112+
select.innerHTML = ''; // Clear existing options
113+
114+
// Add a default "All" option
115+
const allOpt = document.createElement('option');
116+
allOpt.value = '';
117+
allOpt.textContent = 'All';
118+
select.appendChild(allOpt);
119+
120+
data.results.bindings.forEach(binding => {
121+
const option = document.createElement("option");
122+
const label = binding[labelField]?.value || binding[valueField].value;
123+
option.value = binding[valueField].value;
124+
option.textContent = label;
125+
select.appendChild(option);
126+
});
127+
} catch (err) {
128+
console.error("Dropdown load failed:", err);
129+
}
130+
}
131+
132+
/**
133+
* Renders a scrollable <ul> list from a SPARQL query for keywords, used by relationships menu
134+
* Calls `onSelect(uri)` when a user clicks an item, also see `renderKeywordPrettyList` for a more generic version
135+
* Results are cached for 24 hours to reduce server load
136+
* @param {string} query - SPARQL query to fetch keywords
137+
* @param {string} itemsId - ID of the <ul> element to populate
138+
* @param {string} listId - ID of the container for scroll listener
139+
* @param {string} labelField - Field to use for display label (default "label")
140+
* @param {string} valueField - Field to use for value (default "value")
141+
* @param {function} onSelect - Callback function to call with the selected URI
142+
* @returns {Promise<void>}
143+
*/
144+
export async function renderKeywordList(query, itemsId, listId, labelField = "label", valueField = "value", onSelect) {
145+
const listEl = document.getElementById(itemsId);
146+
const container = document.getElementById(listId);
147+
if (!listEl || !container) return;
148+
149+
// Create cache key from query
150+
const cacheKey = `keywordList_${btoa(query).substring(0, 20)}`;
151+
152+
try {
153+
const data = await fetchWithCache(cacheKey, async () => {
154+
const res = await fetch(`${SPARQL_ENDPOINT}?query=${encodeURIComponent(query)}`, {
155+
headers: { Accept: 'application/sparql-results+json' }
156+
});
157+
return res.json();
158+
});
159+
160+
const results = data.results.bindings;
161+
162+
// Render all results at once since we're caching
163+
results.forEach(binding => {
164+
const uri = binding[valueField]?.value || "";
165+
const label = binding[labelField]?.value || uri;
166+
167+
const li = document.createElement("li");
168+
li.textContent = label;
169+
li.style.marginBottom = "0.5rem";
170+
li.style.fontFamily = "Georgia, serif";
171+
li.style.fontSize = ".78rem";
172+
li.style.cursor = "pointer";
173+
li.style.padding = "0.4rem";
174+
li.style.borderBottom = "1px solid #eee";
175+
176+
li.addEventListener("click", () => {
177+
onSelect(uri);
178+
});
179+
180+
listEl.appendChild(li);
181+
});
182+
} catch (err) {
183+
console.error("Failed to load keyword list:", err);
184+
}
185+
}
186+
const CACHE_TTL_HOURS = 24; // or 0 if you want once per session
187+
188+
async function fetchWithCache(key, fetchFn) {
189+
const now = Date.now();
190+
191+
// Check cache
192+
const cached = localStorage.getItem(key);
193+
const cacheTime = localStorage.getItem(key + '_time');
194+
195+
if (cached && cacheTime && (now - Number(cacheTime)) < CACHE_TTL_HOURS * 3600 * 1000) {
196+
console.log(`[CACHE] Using cached data for ${key}`);
197+
return JSON.parse(cached);
198+
}
199+
200+
// If not cached or expired, fetch fresh
201+
console.log(`[CACHE] Fetching new data for ${key}`);
202+
const data = await fetchFn();
203+
204+
// Store cache
205+
localStorage.setItem(key, JSON.stringify(data));
206+
localStorage.setItem(key + '_time', now.toString());
207+
208+
return data;
209+
}
210+
211+
/**
212+
* Clears all cached menu data from localStorage
213+
* Call this function to force fresh data on next load
214+
*/
215+
export function clearMenuCache() {
216+
const keys = Object.keys(localStorage);
217+
keys.forEach(key => {
218+
if (key.startsWith('keywordList_') || key.startsWith('dropdown_')) {
219+
localStorage.removeItem(key);
220+
localStorage.removeItem(key + '_time');
221+
}
222+
});
223+
console.log('Menu cache cleared');
224+
}
225+

browse.html

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@
8888
<button type="button" class="btn btn-outline-dark" data-type="event">
8989
Events
9090
</button>
91+
<button type="button" class="btn btn-outline-dark" data-type="relation">
92+
Relations
93+
</button>
9194
</div>
9295
<div id="filter-sidebar" class="filter-sidebar"></div>
9396
</div>
@@ -109,6 +112,15 @@
109112
<div class="pagination-dots d-none"></div>
110113
</div>
111114
<div class="mt-3 p-3" id="event--items-container"></div>
115+
</div>
116+
<!-- Relation results -->
117+
<div id="relationResults">
118+
<div class="card p-3 shadow-lg border-0">
119+
<hr class="d-none" />
120+
<div id="relation--items" class="gap-3"></div>
121+
<div class="pagination-dots d-none"></div>
122+
</div>
123+
<div class="mt-3 p-3" id="relation--items-container"></div>
112124
</div>
113125
<!-- Factoid results -->
114126
<div id="factoidResults" class="d-none">
@@ -131,9 +143,10 @@
131143
import { boot } from './mode.js';
132144
import personMode from './modes/person.js';
133145
import eventMode from './modes/event.js';
146+
import relationMode from './modes/relation.js';
134147

135148
boot({
136-
registry: { person: personMode, event: eventMode },
149+
registry: { person: personMode, event: eventMode, relation: relationMode },
137150
defaultType: 'person'
138151
});
139152
</script>

menu.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,11 +95,16 @@ export const getOccupationOptions = () => `
9595
* Populates a <select> element with options from a SPARQL query above
9696
*/
9797
export async function populateDropdown(query, dropdownId, labelField = "label", valueField = "value") {
98+
const cacheKey = `dropdown_${dropdownId}`;
99+
98100
try {
99-
const res = await fetch(`${SPARQL_ENDPOINT}?query=${encodeURIComponent(query)}`, {
100-
headers: { Accept: 'application/sparql-results+json' }
101+
const data = await fetchWithCache(cacheKey, async () => {
102+
const res = await fetch(`${SPARQL_ENDPOINT}?query=${encodeURIComponent(query)}`, {
103+
headers: { Accept: 'application/sparql-results+json' }
104+
});
105+
return res.json();
101106
});
102-
const data = await res.json();
107+
103108
const select = document.getElementById(dropdownId);
104109
if (!select) return;
105110

0 commit comments

Comments
 (0)