Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions .github/workflows/generate_relation_data.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
name: Generate Relation Data

on:
workflow_dispatch: # Manual trigger
schedule:
- cron: '0 0 * * 0' # Weekly on Sunday at midnight

jobs:
generate:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3

- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'

- name: Install jq
run: sudo apt-get update && sudo apt-get install -y jq

- name: Get relationship types
working-directory: relation
run: |
curl -G "https://sparql.vanderbilt.edu/sparql" \
--data-urlencode 'query=
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>

SELECT DISTINCT ?subject ?label
WHERE {
VALUES (?collection) {
(<http://syriaca.org/taxonomy/directed-relations-collection>)
(<http://syriaca.org/taxonomy/mutual-relations-collection>)
}
?collection skos:member ?subject .
?subject skos:prefLabel ?label .
}
ORDER BY ?label
' \
-H "Accept: application/sparql-results+json" > relationship_types.json

echo "✓ Relationship types retrieved"
jq '.results.bindings | length' relationship_types.json

- name: Query all relation factoids
working-directory: relation
run: |
chmod +x batch_query_relationships.sh
./batch_query_relationships.sh

echo "✓ All relation factoids generated"
ls -lh all_relation_factoids.json

- name: Filter relation factoids
working-directory: relation
run: |
node filter_factoids.js

echo "✓ Filtered relation factoids generated"
ls -lh filtered_relation_factoids.json

- name: Query person relation factoids
working-directory: relation
run: |
chmod +x batch_query_person_relationship.sh
./batch_query_person_relationship.sh

echo "✓ Person relation factoids generated"
ls -lh all_person_relation_factoids.json

- name: Commit and push changes
run: |
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git add relation/*.json
git diff --staged --quiet || git commit -m "chore: update relation data files [skip ci]"
git push
225 changes: 225 additions & 0 deletions broken_caching_menu.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
export const SPARQL_ENDPOINT = "https://sparql.vanderbilt.edu/sparql";


// Add filter menu options
// Example: wrap your SPARQL calls
// export async function getEventKeywords() {
// return fetchWithCache('eventKeywords', async () => {
// const response = await fetch('../menus/events.json');

// if (!response.ok) throw new Error('Failed to load event keywords');
// return response.json();
// });
// }



export const getEventKeywords = () => `
PREFIX swdt: <http://syriaca.org/prop/direct/>
SELECT DISTINCT ?keyword
FROM <https://spear-prosop.org>
WHERE {
?event swdt:event-keyword ?keyword .
}
ORDER BY ?keyword
`;

export const getRelationshipOptions = () => `
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
SELECT DISTINCT ?collectionType ?subject ?label
WHERE {
VALUES (?collection ?collectionType) {
(<http://syriaca.org/taxonomy/directed-relations-collection> "directed")
(<http://syriaca.org/taxonomy/mutual-relations-collection> "mutual")
}
?collection skos:member ?subject .
?subject skos:prefLabel ?label .
}
ORDER BY ?collectionType ?label
`;

export const getEthnicityOptions = () => `
PREFIX swdt: <http://syriaca.org/prop/direct/>
SELECT DISTINCT ?ethnicity
FROM <https://spear-prosop.org>
WHERE {
?person swdt:ethnic-label ?ethnicity .
}
ORDER BY ?ethnicity
`;

export const getPlaceOptions = () => `
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX swdt: <http://syriaca.org/prop/direct/>
SELECT DISTINCT ?place ?label
FROM <http://syriaca.org/geo#graph>
FROM <https://spear-prosop.org>
WHERE {
{
?person swdt:residence ?place .
} UNION {
?person swdt:birth-place ?place .
} UNION {
?person swdt:death-place ?place .
} UNION {
?event swdt:event-place ?place .
} UNION {
?person swdt:citizenship ?place .
}
?place rdfs:label ?label .
FILTER(LANG(?label) = "en")
}
ORDER BY ?label
`;

export const getEducationFieldsOfStudy = () => `
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
SELECT DISTINCT ?subject ?label
WHERE {
<http://syriaca.org/taxonomy/fields-of-study-collection> skos:member ?subject .
?subject skos:prefLabel ?label .
}
ORDER BY ?label
`;
export const getOccupationOptions = () => `
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
SELECT DISTINCT ?occupation ?label
WHERE {
<http://syriaca.org/taxonomy/occupations-collection> skos:member ?occupation .
?occupation skos:prefLabel ?label .
}
ORDER BY ?label
`;

/**
* Populates a <select> element with options from a SPARQL query above
* Results are cached for 24 hours to reduce server load
*/
export async function populateDropdown(query, dropdownId, labelField = "label", valueField = "value") {
const cacheKey = `dropdown_${dropdownId}`;

try {
const data = await fetchWithCache(cacheKey, async () => {
const res = await fetch(`${SPARQL_ENDPOINT}?query=${encodeURIComponent(query)}`, {
headers: { Accept: 'application/sparql-results+json' }
});
return res.json();
});

const select = document.getElementById(dropdownId);
if (!select) return;

select.innerHTML = ''; // Clear existing options

// Add a default "All" option
const allOpt = document.createElement('option');
allOpt.value = '';
allOpt.textContent = 'All';
select.appendChild(allOpt);

data.results.bindings.forEach(binding => {
const option = document.createElement("option");
const label = binding[labelField]?.value || binding[valueField].value;
option.value = binding[valueField].value;
option.textContent = label;
select.appendChild(option);
});
} catch (err) {
console.error("Dropdown load failed:", err);
}
}

/**
* Renders a scrollable <ul> list from a SPARQL query for keywords, used by relationships menu
* Calls `onSelect(uri)` when a user clicks an item, also see `renderKeywordPrettyList` for a more generic version
* Results are cached for 24 hours to reduce server load
* @param {string} query - SPARQL query to fetch keywords
* @param {string} itemsId - ID of the <ul> element to populate
* @param {string} listId - ID of the container for scroll listener
* @param {string} labelField - Field to use for display label (default "label")
* @param {string} valueField - Field to use for value (default "value")
* @param {function} onSelect - Callback function to call with the selected URI
* @returns {Promise<void>}
*/
export async function renderKeywordList(query, itemsId, listId, labelField = "label", valueField = "value", onSelect) {
const listEl = document.getElementById(itemsId);
const container = document.getElementById(listId);
if (!listEl || !container) return;

// Create cache key from query
const cacheKey = `keywordList_${btoa(query).substring(0, 20)}`;

try {
const data = await fetchWithCache(cacheKey, async () => {
const res = await fetch(`${SPARQL_ENDPOINT}?query=${encodeURIComponent(query)}`, {
headers: { Accept: 'application/sparql-results+json' }
});
return res.json();
});

const results = data.results.bindings;

// Render all results at once since we're caching
results.forEach(binding => {
const uri = binding[valueField]?.value || "";
const label = binding[labelField]?.value || uri;

const li = document.createElement("li");
li.textContent = label;
li.style.marginBottom = "0.5rem";
li.style.fontFamily = "Georgia, serif";
li.style.fontSize = ".78rem";
li.style.cursor = "pointer";
li.style.padding = "0.4rem";
li.style.borderBottom = "1px solid #eee";

li.addEventListener("click", () => {
onSelect(uri);
});

listEl.appendChild(li);
});
} catch (err) {
console.error("Failed to load keyword list:", err);
}
}
const CACHE_TTL_HOURS = 24; // or 0 if you want once per session

async function fetchWithCache(key, fetchFn) {
const now = Date.now();

// Check cache
const cached = localStorage.getItem(key);
const cacheTime = localStorage.getItem(key + '_time');

if (cached && cacheTime && (now - Number(cacheTime)) < CACHE_TTL_HOURS * 3600 * 1000) {
console.log(`[CACHE] Using cached data for ${key}`);
return JSON.parse(cached);
}

// If not cached or expired, fetch fresh
console.log(`[CACHE] Fetching new data for ${key}`);
const data = await fetchFn();

// Store cache
localStorage.setItem(key, JSON.stringify(data));
localStorage.setItem(key + '_time', now.toString());

return data;
}

/**
* Clears all cached menu data from localStorage
* Call this function to force fresh data on next load
*/
export function clearMenuCache() {
const keys = Object.keys(localStorage);
keys.forEach(key => {
if (key.startsWith('keywordList_') || key.startsWith('dropdown_')) {
localStorage.removeItem(key);
localStorage.removeItem(key + '_time');
}
});
console.log('Menu cache cleared');
}

15 changes: 14 additions & 1 deletion browse.html
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@
<button type="button" class="btn btn-outline-dark" data-type="event">
Events
</button>
<button type="button" class="btn btn-outline-dark" data-type="relation">
Relations
</button>
</div>
<div id="filter-sidebar" class="filter-sidebar"></div>
</div>
Expand All @@ -109,6 +112,15 @@
<div class="pagination-dots d-none"></div>
</div>
<div class="mt-3 p-3" id="event--items-container"></div>
</div>
<!-- Relation results -->
<div id="relationResults">
<div class="card p-3 shadow-lg border-0">
<hr class="d-none" />
<div id="relation--items" class="gap-3"></div>
<div class="pagination-dots d-none"></div>
</div>
<div class="mt-3 p-3" id="relation--items-container"></div>
</div>
<!-- Factoid results -->
<div id="factoidResults" class="d-none">
Expand All @@ -131,9 +143,10 @@
import { boot } from './mode.js';
import personMode from './modes/person.js';
import eventMode from './modes/event.js';
import relationMode from './modes/relation.js';

boot({
registry: { person: personMode, event: eventMode },
registry: { person: personMode, event: eventMode, relation: relationMode },
defaultType: 'person'
});
</script>
Expand Down
2 changes: 1 addition & 1 deletion contact.html
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ <h2>Comments and Collaboration</h2>
<h2>Support Our Work</h2>

<p>We are also grateful to all financial supporters of our work. Financial
contributions can be made through Vanderbilt University at <a href="http://www.vu.edu/syriac">www.vu.edu/syriac</a> or by contacting
contributions can be made by contacting
<a href="mailto:info@syriaca.org">info@syriaca.org</a>.
</p>

Expand Down
11 changes: 8 additions & 3 deletions menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,16 @@ export const getOccupationOptions = () => `
* Populates a <select> element with options from a SPARQL query above
*/
export async function populateDropdown(query, dropdownId, labelField = "label", valueField = "value") {
const cacheKey = `dropdown_${dropdownId}`;

try {
const res = await fetch(`${SPARQL_ENDPOINT}?query=${encodeURIComponent(query)}`, {
headers: { Accept: 'application/sparql-results+json' }
const data = await fetchWithCache(cacheKey, async () => {
const res = await fetch(`${SPARQL_ENDPOINT}?query=${encodeURIComponent(query)}`, {
headers: { Accept: 'application/sparql-results+json' }
});
return res.json();
});
const data = await res.json();

const select = document.getElementById(dropdownId);
if (!select) return;

Expand Down
Loading
Loading