code
[--C0DE--]//:8123248186365035307:1348435392098232568: real
const textDecoder = document.createElement("textarea")// used for decoding HTML entities
let setData=null// the search set object from a differnt post
const{li,div,img,span,br,ul,a,table,tr,td, button, h2, iframe}=van.tags()
const debugging = false
const global={mapUrl:"https://maps.google.com/?q=",locaToWebUrl:"https://locatoweb.com/api/data/GetMapDataSingle2?id="}
const appKey = atob("YjBLN1JXUVNKVUQ4QQ")
const redirect = encodeURIComponent(window.location.origin + "/1970/01/auth.html")
//const redirect = encodeURIComponent("https://findingfamilyeverywhere.blogspot.com/1970/01/auth.html")
const authUrl = `https://ident.familysearch.org/cis-web/oauth2/v3/authorization?response_type=code&client_id=${appKey}&redirect_uri=${redirect}`
let searches_started = 0
let searches_complete = 0
let relatives_found = 0
async function initialize(){
global.year=location.pathname.split("/")[1]
global.month=location.pathname.split("/")[2]
if(!localStorage.getItem("svgMale")){
const url = `/feeds/posts/default/-/svg?alt=json&max-results=500`
//console.log("1",url)
fetch(url)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => {
//console.log("data-------------------", data)
for(const entry of data.feed.entry){
localStorage.setItem(entry.title.$t, entry.content.$t)
}
})
}
document.body.addEventListener("click", function (e) {
tag(`menu`).style.left=`-310px`
});
refresh_unauthenticated_token()
// check to see if we have a localStorage item of apiCode
// if so, we are coming in from a authRedirect. need to get the token
const apiCode = localStorage.getItem("apiCode")
if(apiCode){
await setFsToken(apiCode)
}
tag("login").setAttribute("href", authUrl);
showPage()
}
function debug(){
if(!debugging){return}
if(!tag("debug")){
document.body.appendChild(div({id:"debug"}))
}
for(let x=0;x<arguments.length;x++){
tag("debug").appendChild(div(arguments[x]))
}
}
async function setFsToken(code){
let rsp = await fetch('https://ident.familysearch.org/cis-web/oauth2/v3/token?redirect_uri='+redirect, {
method: "POST",
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: `grant_type=authorization_code&code=${code}&client_id=${appKey}`
})
let obj = await rsp.json()
localStorage.setItem("authenticatedToken", obj.access_token)
localStorage.setItem("unauthenticatedToken", obj.access_token)
localStorage.setItem("authenticatedTokenTime", new Date().valueOf())
localStorage.setItem("unauthenticatedTokenTime", new Date().valueOf())
//console.log("obj",JSON.stringify(obj))
//const user = JSON.parse(atob(obj.id_token.split('.')[1]));
const user={}
// get the current user's PID
// rsp = await fetch('https://api.familysearch.org/?access_token=' + obj.access_token)
// obj = await rsp.json()
obj = await api("platform/users/current",true,{method:"GET",headers:{authorization:"Bearer " + obj.access_token}})
// //console.log("person-------",JSON.stringify(obj))
//console.log("about to fetch")
google_form(obj)
user.person=obj.users[0]
user.person.id=user.person.personId
user.person.name = user.person.displayName
//console.log("user",user)
localStorage.setItem("user", JSON.stringify(user))
localStorage.removeItem("apiCode")
//console.log("=========setting serachjpeersons===========")
localStorage.setItem("searchPersons",JSON.stringify([{
id:user.person.id,
name:user.person.name
}]))
}
function showPage(){
debug("ShowPage")
const pagesToFetch=[]
const pagesToFetchNotes=[]
const params = new URLSearchParams(document.location.search)
let event = params.get("e")
let atEvent=false
if(event){
localStorage.setItem("event", event)
atEvent=true
}
//console.log("showPage called")
//console.log("at showpage")
const data = JSON.parse(tag(`post-json`).innerHTML)
global.data=data
//console.log("data",data)
if(!data.searchMethods){data.searchMethods=["living","dead"]}
tag("location").replaceChildren(data.locationLabel)
tag("head-location").replaceChildren(data.locationLabel)
tag("location").style.fontSize=data.locationSize
let setName = data.searchSets[0].set_id // the default search set
//console.log("setname 1", setName)
if(window.location.search){
possibleSetName = getSetNameFromSearch()
//console.log(possibleSetName)
if(possibleSetName){
setName=possibleSetName
}
}
//now we have a set name, fetch the set
localStorage.setItem("personSet", setName)
//set the current index path in case we need to redirect
localStorage.setItem("setIndex", window.location.origin + window.location.pathname)
const pathArray = location.pathname.split("/")
pathArray.length=3
pathArray.push(setName + ".html")
//console.log("pathArray", pathArray)
pagesToFetch.push(fetch(pathArray.join("/")))
pagesToFetchNotes.push("set")
// get the person data
console.log(`=========================== person feed ===========================`)
console.log(`/feeds/posts/default/-/${setName}?alt=json&max-results=500`)
pagesToFetch.push(fetch(`/feeds/posts/default/-/${setName}?alt=json&max-results=150`))
pagesToFetchNotes.push("people")
pagesToFetch.push(fetch(`/feeds/posts/default/-/${setName}?alt=json&max-results=150&start-index=151`))
pagesToFetchNotes.push("people2")
event = localStorage.getItem("event")
if(event){
pagesToFetch.push(fetch(`/feeds/posts/default/-/${event}?alt=json&max-results=500`))
pagesToFetchNotes.push("event")
}
//Fetch all pages then process
Promise.all(pagesToFetch).then((responses) => {
const promises = responses.map((response) => response.text())
Promise.all(promises).then((htmls) => {
// get the set data (htmls[0] is the set data)
setData = getObjectFromPost(htmls[0])
// get the people data (htmls[1] is the people atom feed with the first 150 people)
// (htmls[2] has the second 150 people, if any)
const people = {}
try{
let feed = JSON.parse(htmls[1]).feed
if (feed.entry){
Object.assign(people, feed.entry)
}
feed = JSON.parse(htmls[2]).feed
if (feed.entry){
Object.assign(people, feed.entry)
}
// build the setData.people array
// console.log("processing people set", Array.isArray(people), people)
// console.log(people[0])
// replacing any people recorded in teh old way with the new set of people
setData.people=[]
for(let x=0;x<Object.keys(people).length;x++){
console.log(people[x].content.$t)
setData.people.push(JSON.parse(people[x].content.$t))
}
}catch(e){
console.error(e.message)
}
// get the event data, if there is any (htmls[3] is the feed for the event, if specified)
const eventData = {interpreter:{}}
if(htmls.length > 3){
const atom = JSON.parse(htmls[3])
console.log("atom", atom)
for(const post of atom.feed.entry){
const data = JSON.parse(post.content.$t)
//console.log("data---", data)
if(containsTerm(post.category,"interpreter")){
eventData.interpreter[data.pid]={
imageUrl:data.imageUrl,
meet:data.meet,
meetText:data.meetText,
scheduleUrl:data.scheduleUrl
}
}else if(containsTerm(post.category,"event")){
eventData.noneText=data.noneText
}
}
//console.log("eventData----", eventData)
for(let x=0; x < setData.people.length; x++){
const pid = setData.people[x].pid
if(eventData.interpreter[pid]){
setData.people[x].meet=eventData.interpreter[pid].meet
setData.people[x].imageUrl=eventData.interpreter[pid].imageUrl
setData.people[x].meetText=eventData.interpreter[pid].meetText
setData.people[x].scheduleUrl=eventData.interpreter[pid].scheduleUrl
setData.people[x].meetable=true
}
}
global.data.event=eventData
}
function containsTerm(categories, term){
//console.log("categories", categories, "term", term)
for(const category of categories){
//console.log(term, category.term)
if(category.term===term){
return true
}
}
return false
}
//console.log("setData",setData)
debug("got data 2")
tag("heading").style.display="block"
tag("center-box").style.display="none"
//decide what to show
if(localStorage.getItem('searchMethod')){
// a search method is already established, the set and build the results
//console.log("===ready to append")
const burger = div({id:"burger"},
span({class:"material-symbols-outlined", onclick:showMenu, style:"cursor:pointer"},"menu")
)
document.body.appendChild(burger)
search()
}else{
//console.log("hiding all panes")
showSearchPane(data)
}
})
})
}//end of show page
function showSearchPane(){
hide(".pane")
//console.log("showing search-method")
show(tag("search-method"))
//console.log("done showing search-method")
//tag("search-method").style.display="block"
// no search method established, show person selector
//console.log(data)
for(const searchMethod of global.data.searchMethods){
tag("search-" + searchMethod).style.display="block"
}
if(global.data.searchMethods.length>1){
tag("search-or").style.display="block"
}
}
function getObjectFromPost(html){
debug("in getObjectFromPost", html)
const text = html.split("[--C0"+"DE--]")[2]
//console.log("text: " + text)
const obj = JSON.parse(text)
debug("obj: " + JSON.stringify(obj))
return obj
}
function searchPersonName(pid){
for(const person of JSON.parse(localStorage.getItem("searchPersons"))){
if(person.id===pid){
return person.name
}
}
}
async function find_relationships(id) {
//console.log("find rels", id)
// Iterate person list
searches_started=0
searches_complete=0
relatives_found=0
meetables=[]
//console.log("start", relatives_found)
let access_token=null
if(localStorage.getItem("searchMethod")==="myself"){
//console.log("aboug to get_access_toekn(true)")
access_token = await get_access_token(true)
//console.log("access_token", access_token)
if(!access_token){
// we could not get an access token, so we must in an expired session. show teh login screen
showSearchPane()
//console.log("returning")
return
}
}else{
access_token = await get_access_token()
}
setData.people.forEach(async function(key, idx, array) {
if (key.pid == "") return;
let path=null
if(localStorage.getItem("searchMethod")==="myself"){
path = 'platform/tree/persons/CURRENT/relationships/' + key.pid
}else{
path = 'platform/tree/persons/' + id + '/relationships/' + key.pid
}
//console.log("key----->", key)
// Calculate relationship
//console.log("source pid", id, access_token)
const options = {headers: {Authorization: 'Bearer ' + access_token}}
searches_started++
await fetch("https://api.familysearch.org/" + path, options).then(function(rsp) {
//console.log("rsp",rsp)
// Handle no relationship case
if (rsp.status === 204){
return {persons: []};
}else if(rsp.status === 401){
//console.log("========================unauthorized====================")
if(localStorage.getItem("searchMethod")==="myself"){
// gove thinks we get here when we have an authenticated token that has gone stale
// perhaps we should make a function that refreshes the token
localStorage.removeItem("authenticatedToken")
localStorage.removeItem("authenticatedTokenTime")
}else{
localStorage.removeItem("unauthenticatedToken")
localStorage.removeItem("unauthenticatedTokenTime")
}
location.reload()
}
return rsp.json();
})
.then(async function(rsp) {
//console.log("---------------------------",rsp.persons.length)
//console.log(rsp)
searches_complete++
let related=false
if (rsp.persons.length > 0){
relatives_found++
related=true
}
let type = ""
let level = 100
let treeDiv = ""
let article = ""
let sketch = ""
let meet = ""
let schedule = ""
let meetText = ""
let image = ""
let relButton = ""
let className = "stranger"
let manWoman = "man"
let himHer = "him"
let hisHer = "his"
let portrait = "https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEitpnNumDWGdr4f0Qbe_JpToIQ1KgayZs5_p84sr7LKku_RJI5qa2VzItJKWg-R_KqYj2QDkeGmAFOMTDahwZct_OehsiNIhhexI0PdAE2baBx46sbgW3mcaZ2-MhhBMSg6WkcnPX6NgzN4A9vcE6WQK7yiNrDOl6C6G3bfMJiMsXTg6HsouNE6rBF_TDl4/s1600/male.png"
if(key.gender.toLowerCase()==="female"){
himHer = "her"
manWoman = "woman"
hisHer = "her"
portrait = "https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi5v3ErOihYcP4BPRnwWfVj3yQCRkNyf_wCOLKheNeHt4P5NHBr1d1WcqN9Jb_pBe_CasnC8E2Ca96Kvp5RQmcNe2A1PDrJJ2s08rqB7YIjAqInRMyVqivi0A3PrN3v8gCfwAIFcKDLl1NY_JRySbfGoCSXq4FV2sTO6Y95qiA1Gq1YfaX4OVEhepwjTcm1/s1600/female.png"
}
let image_clause = img({class:"portrait"})
if(key.imageURL){
if(key.imageURL==="female" || (key.imageURL==="male" && key.gender.toLowerCase()==="female")){
image_clause=div({class:"svg-pic"})
image_clause.innerHTML = localStorage.getItem("female")
}else if(key.imageURL==="male"){
image_clause=div({class:"svg-pic"})
image_clause.innerHTML = localStorage.getItem("male")
}else{
image_clause.src=key.imageURL// favor the imageURL if it exists
image_clause = a({href:image_clause.src},image_clause)
}
}else if(await logged_in()){
// there is no imgurl and we are logged in. use the image from FS
image_clause.src=`https://api.familysearch.org/platform/tree/persons/${key.pid}/portrait?default=${portrait}&access_token=${access_token}`
a({href:image_clause.src},image_clause)
}else{
// there is no imageURL and we are not logged in, use teh gender
if(key.gender.toLowerCase()==="male"){
image_clause=div({class:"svg-pic"})
image_clause.innerHTML = localStorage.getItem("male")
}else{
image_clause=div({class:"svg-pic"})
image_clause.innerHTML = localStorage.getItem("female")
}
}
if(key.desc){sketch = a({class:"pill", href:key.url},"Article")}
if(key.url){article = a({class:"pill", href:key.url},"Article")}
if(key.meetable){
key.level=1
}
if(key.meetable){
meetables.push(key.pid)
meet = button({class:"pill", onclick:show_meet},"Meet")
}
if(key.meetText){
meetText = div({style:"text-align:left"})
meetText.innerHTML = key.meetText
}
if(key.scheduleUrl){
schedule = button({class:"pill", onclick:()=>{location.href=`${decodeHtml(key.scheduleUrl)}`}},"Schedule")
}
if(related){
className = "person"
try{
// Get relationship title
type = rsp.persons[rsp.persons.length - 1].display.relationshipDescription.split("My ")[1];
}catch(e){
//console.log(rsp.persons[rsp.persons.length - 1].display)
console.error(e.message)
}
level=key.level||get_level(type)
treeDiv = div({class:"tree", style:"display:none"})
treeDiv.innerHTML=get_path(rsp)
relButton = button({class:"pill", onclick:show_path},"Relationship")
}
if(key.imageUrl){
image=div(
div({style:"text-align:left;margin-top:15px"},
`Here's an image of the ${manWoman} you seek:`
),
div(
img({src:key.imageUrl, style:"width:100%;max-width:300px;"}),
),
)
}
const relativeLi=div({class:"relative-tile", id:`rel-${key.pid}`},
div(
div({class:className},
table({style:"width:100%"},
tr(
td({valign:"top"},image_clause),
td({style:"width:100%;text-align:center;"},
div(
span({class:"name"},key.name)
),
div({class:"show-tree"},` ${type}`)
)
)
),
)
),
div( {class:"relative-body"},
div({style:"text-align:center"},
meet,
relButton,
button({class:"pill", onclick:show_sketch},"Sketch"),
a({class:"pill", href:"https://www.familysearch.org/en/tree/person/about/" + key.pid},"Bio"),
article
),
treeDiv,
div(span({class:"sketch", style:"display:none"},decodeHtml(key.desc))),
div({class:"meet", style:"display:none"},
`${key.name} is represented by a history interpreter at this event. You might be able to meet ${himHer}.`,
div({style:"text-align:center;margin-top:10px"},
div({id:"meet-" + key.pid, style:"text-align:left;margin-top:15px; display:none"},
`Here's a map that could help you find ${himHer}.`,
span({style:"display:none"},` The button will check for ${hisHer} new location every 90 seconds, so if you are unable for find ${himHer}, return here and click the button again.`),
div({style:"text-align:center;margin-top:10px"},
div({class:"pill", onclick:showMap, "data-pid":key.pid},"Show Map"),
),
),
meetText,
schedule,
image
)
),
)
)
relativeLi.dataset.id=key.pid
relativeLi.dataset.level=level
place_relative(id,level,relativeLi, related)
if(searches_complete===setData.people.length){
// we are done
//console.log("done", relatives_found)
/*
for(const pid of meetables){
const locaToWebId=global.data.event.interpreter[pid].meet.split("/").pop()
//console.log("==========================", pid ,searches_complete,setData.people.length)
fetch(global.locaToWebUrl + locaToWebId)
.then(response => response.json())
.then(mapData => {
//console.log(mapData)
if(mapData.IsActive){
//console.log("map is active")
const pos = mapData.Pins.pop().Pos
const button = tag("meet-" + pid).querySelector(".pill")
button.dataset.id = locaToWebId
button.dataset.lat = pos.Lat
button.dataset.lon = pos.Lon
button.dataset.time = Date.now()
tag("meet-" + pid).style.display="block"
}
});
}
*/
const message=tag("header-" + id)
if(relatives_found === 0){
message.replaceChildren(h2(`No Connections for ${searchPersonName(id)}`))
message.appendChild(div({class:"message"},"It could be that you really are not related or that the Family Search database just does not have enough information about your ancestors to connect you to this group."))
if(global.data.noneText){message.appendChild(div({class:"message"},global.data.noneText))}
if(global.data.event && global.data.event.noneText){message.appendChild(div({class:"message", style:"color:lemonchiffon"},global.data.event.noneText))}
let event_data = localStorage.getItem("eventData")
if(event_data){
event_data=JSON.parse(event_data)
if(event_data.notFound){
message += event_data.notFound
}
}
}else if(relatives_found === 1){
message.replaceChildren(h2(`${searchPersonName(id)} is related to the following person:`))
}else{
message.replaceChildren(h2(`${searchPersonName(id)} is related to the following ${relatives_found} people:`))
}
const unconnected = tag("setNotRelated").childElementCount
if(unconnected>0){
// we have some set members who are not related to the search person
let message = "There is one member of this group to whom we could not connect you."
if(unconnected > 1) {message = `There are ${unconnected} members of this group to whom we could not connect you.`}
tag("notRelatedMessage").innerHTML = message
tag("notRelated").style.display="block"
}
}
})
})
}
function showMap(evt){
const seconds = Math.floor((Date.now() - parseInt(evt.target.dataset.time))/ 1000)
//console.log("showing", evt.target, seconds, "seconds have elapsed")
//debugger
if(seconds < 90){
location.href=`${global.mapUrl}${evt.target.dataset.lat},${evt.target.dataset.lon}`
}else{
tag("meet-"+evt.target.dataset.pid).querySelector("span").style.display=""
fetch(global.locaToWebUrl + evt.target.dataset.id)
.then(response => response.json())
.then(mapData => {
//console.log(mapData)
if(mapData.IsActive){
//console.log("map is active")
const pos = mapData.Pins.pop().Pos
location.href=`${global.mapUrl}${pos.Lat},${pos.Lon}`
}else{
tag("meet-" + evt.target.dataset.pid).replaceChildren(div("This interpreter just went off duty and is no longer sharing location information."))
}
});
}
}
function showUnconnected(){
if(tag("setNotRelated").style.display==="none"){
tag("setNotRelated").style.display=""
tag("unconnected-button").replaceChildren("Hide Unconnected")
}else{
tag("setNotRelated").style.display="none"
tag("unconnected-button").replaceChildren("Show Unconnected")
}
}
function decodeHtml(html) {
textDecoder.innerHTML = html;
return textDecoder.value;
}
async function search() {
hide(".pane")
show(tag("search-results"))
//console.log(" At Search - - - trying to show results", setData)
const searchPersons = JSON.parse(localStorage.getItem("searchPersons"))
let token = localStorage.getItem("authenticatedToken")
const searchMethod = localStorage.getItem("searchMethod")
if(searchMethod==="ancestor"){
token = localStorage.getItem("unauthenticatedToken")
}
// Get event info. Need to reintegrate this
//let event = new URLSearchParams(window.location.search).get('event');
//google_form() // need to uncomment this
// if(event){
// if(!event.startsWith("http")){event = "events/"+event+".json"}
// fetch(event)
// .then(response => response.json())
// .then(event_data => {
// localStorage.eventData=JSON.stringify(event_data)
// });
// }
refresh_unauthenticated_token() // not sure we need this here.
const ancestors=get_remembered_ancestors()
tag('banner').style.backgroundImage = `url('${setData.banner}');`
tag('set-title').innerHTML = setData.title
tag('set-description').innerHTML = setData.desc
tag('banner').style.backgroundImage=`url(${setData.banner})`
// $('body').attr('style', 'background-color: ' + data.backgroundColor + '; color: ' + data.textColor + ';');
// if(localStorage.getItem("searchMethod")==="myself"){
// const user=JSON.parse(localStorage.getItem("user"))
// launch_relationships(user.person)
// }else{
// for(const ancestor of Object.values(ancestors) ){
// launch_relationships(ancestor)
// }
// }
// now launch the search for relationships.
for(const searchPerson of searchPersons){
//console.log("searchPerson", searchPerson)
//for(const person of setData.people){
launch_relationships(searchPerson)
//}
}
}
function launch_relationships(searchPerson){
//console.log("clicked", ancestor)
tag("setResults").appendChild(
div({id:"header-"+searchPerson.id, class:"relative-header"}, "Searching for connections to " + searchPerson.name + " . . . ")
)
tag("setResults").appendChild(
div({id:"results-"+searchPerson.id},
div({id:searchPerson.id, class:"person-container"})// removed person from class
)
)
//console.log("---finding relatoinships", searchPerson)
find_relationships(searchPerson.id)
}
function getSetNameFromSearch(){
// returns the frist name/value pair with no equal sign
const params = window.location.search.slice(1).split("&")
for(const param of params){
if(!param.includes("=")){return param}
}
return null
}
function nam(id){return document.getElementsByName(id)[0]}
function hide(elem_or_query_selector){show(elem_or_query_selector,"none")}
function show(elem_or_query_selector, display=""){
// takes an element or tag array or querySelector string and shows or hides all matching
let elems=elem_or_query_selector
if(typeof elems === 'string'){
elems=document.querySelectorAll(elem_or_query_selector)
}
//console.log("elems.length", elems.length, elems)
if(elems.length !== undefined){
for(const elem of elems){
elem.style.display=display
}
}else{
elems.style.display=display
}
}
async function searchAncestor() {
//console.log("searching for ancestors", nam('given'))
hide(".invalid-feedback")
const ancestors=get_remembered_ancestors()
// //if form is empty and we have a remembered ancestor, then search
// if(
// nam('given').value === "" &&
// nam('birthLikeDateBegin').value === "" &&
// nam('birthLikePlace').value === "" &&
// nam('deathLikeDateBegin').value === "" &&
// nam('deathLikePlace').value === "" &&
// nam('surname').value === "" &&
// ancestors &&
// Object.keys(ancestors).length>0
// ){
// location.href="/relatives.html"
// }
//validate
let invalid_count=0
if( nam('surname').value === "" ){
show(tag("surname-missing"))
invalid_count++
}
if(isNaN(nam('birthLikeDateBegin').value)){
show(tag("birth-year-invalid"))
invalid_count++
}
if(isNaN(nam('deathLikeDateBegin').value)){
show(tag("death-year-invalid"))
invalid_count++
}
if(invalid_count>0){
return
}
// $('.results, .related').empty();
// $('.result-list').empty();
// $('').show();
// $('.ancestor-list').html("Select your ancestor below");
URL = "q.surname=" + nam('surname').value
if (nam('given' ).value !== "") URL += '&q.givenName=' + nam('given' ).value
if (nam('birthLikeDateBegin').value !== "") URL += "&q.birthLikeDate=" + nam('birthLikeDateBegin').value
if (nam('birthLikePlace' ).value !== "") URL += "&q.birthLikePlace=" + nam('birthLikePlace' ).value
if (nam('deathLikeDateBegin').value !== "") URL += "&q.deathLikeDate=" + nam('deathLikeDateBegin').value
if (nam('deathLikePlace' ).value !== "") URL += "&q.deathLikePlace=" + nam('deathLikePlace' ).value
let authenticated=false
if(await logged_in()){
authenticated = localStorage.getItem("authenticatedToken")
}else{
//console.log("not logged in")
}
const search=await api('platform/tree/search?' + URL + "&count=20",authenticated, {headers:{Accept: "application/json"}})
//console.log("search", search)
for (let i = 0; i < search.entries.length; i++) {
//console.log("search.entries[i]",i, search.entries[i])
p = search.entries[i].content.gedcomx.persons[0].display;
p.id = search.entries[i].content.gedcomx.persons[0].id;
tag("results").replaceChildren()
place_ancestor(p, ancestors, authenticated)
}
}
function get_remembered_ancestors(){
ancestors=localStorage.getItem("ancestors")||"{}"
return JSON.parse(ancestors)
}
async function api(path, authenticated="either", options={method:"GET"}){
// path is the part of the URL that goes after familysearch.org/
//console.log("at api",path, authenticated, options)
const url="https://api.familysearch.org/" + path
if(!options.headers){
options.headers={}
}
if(!options.headers.authorization){// only set the authoriation if an authroization header is not passed in
let access_token = await get_access_token(authenticated)
//console.log("get_access_token",get_access_token)
options.headers.authorization = 'Bearer ' + access_token
}
//console.log(url, "88")
const rsp = await fetch(url,options)
//console.log("response.status",rsp.status)
if(rsp.status!=200){return{status:rsp.status}}
const data = await rsp.json()
data.status=200
//console.log("data", data)
return await data
}
async function get_access_token( authenticated="either"){
//console.log("authenticated",authenticated)
let token = null
if(authenticated===true){
if(await logged_in()){
token = localStorage.getItem("authenticatedToken")
}else{
return false
}
}else if(authenticated==="either"){
if(await logged_in()){
token = localStorage.getItem("authenticatedToken")
}else{
token =await get_unauthenticated_token()
}
}else if(authenticated){
token = authenticated
}else if(authenticated===false){
token =await get_unauthenticated_token()
}
return token
}
async function get_unauthenticated_token(){
let token = localStorage.getItem("unauthenticatedToken")
if(unauthenticated_token_is_valid()){
return token
}
await set_unauthenticated_token()
return localStorage.getItem("unauthenticatedToken")
}
async function set_unauthenticated_token(){
const rsp = await fetch('https://ident.familysearch.org/cis-web/oauth2/v3/token', {
method: "POST",
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: `grant_type=unauthenticated_session&ip_address=${window.location.host}&client_id=` + atob('YTAyajAwMDAwMEtUUmpwQUFI')
})
obj=await rsp.json()
localStorage.setItem("unauthenticatedToken", obj.token)
localStorage.setItem("unauthenticatedTokenTime", new Date().valueOf())
}
async function refresh_unauthenticated_token(){
if(!await unauthenticated_token_is_valid()){
await set_unauthenticated_token()
}
}
async function unauthenticated_token_is_valid(){
//debugger
const access_token = localStorage.getItem("unauthenticatedToken")
if(!access_token){
//console.log ("Not Valid: No Access Token")
return false
}
//check the age of the token
if(new Date().valueOf() - localStorage.getItem("unauthenticatedTokenTime") < 36000000){
// it's been less than an hour since checking
//console.log("less than an hour")
return true
}
// its been more than an hour since we last checked
const user = JSON.parse(localStorage.getItem("user"))
const url="https://api.familysearch.org/platform/tree/persons/KWHM-PDN"// + user.person.id
const options={
method:"head",
headers:{
authorization:'Bearer ' + access_token
}
}
//console.log("url rsp",url)
const rsp = await fetch(url,options)
if(rsp.status===200){
localStorage.setItem("unauthenticatedTokenTime", new Date().valueOf())
return true
}else{
localStorage.setItem("unauthenticatedTokenTime", 0)
localStorage.setItem("unauthenticatedToken", null)
return false
}
}
async function place_ancestor(p, ancestors, authenticated){
const access_token = await get_access_token()
//debugger
//console.log("at place ancestors",p)
if (p.birthPlace === undefined) p.birthPlace = "";
let birthYear = (p.birthDate) ? new Date(p.birthDate).getUTCFullYear() : "";
let deathYear = (p.deathDate) ? new Date(p.deathDate).getUTCFullYear() : "";
let age = (birthYear && deathYear) ? "(Age " + Math.abs(deathYear - birthYear) + ")" : "";
// Check for NaN (Safari won't parse dates like "October 1893")
if (isNaN(birthYear)) birthYear = p.birthDate;
if (isNaN(deathYear)) deathYear = p.deathDate;
// Get gender portrait
let portrait = "/images/male.svg";
if (p.gender == "Female") portrait = "/images/female.svg";
let image_clause = null
if(authenticated===true){
image_clause = img({class:"portrait", src:`https://api.familysearch.org/platform/tree/persons/${p.id}/portrait?default=${portrait}&access_token=${access_token}`,onerror:logit})
}else if(authenticated){
image_clause = img({class:"portrait", src:`https://api.familysearch.org/platform/tree/persons/${p.id}/portrait?default=${portrait}&access_token=${authenticated}`})
}else{
const {use,svg} = van.tags("http://www.w3.org/2000/svg")
image_clause = svg({height:'30', width:'30'})
image_clause.innerHTML=`<use xlink:href="#${p.gender.toLowerCase()}"></use>`
}
tag("results").appendChild(
li({onclick:setSearchAncestor, class:"result", "data-record":btoa(JSON.stringify(p)), "data-id":p.id},
div({class:"ancestor"},
div({class:"portrait-container"},image_clause),
div({class:"details"},
div({class:"ancestor-name"},`${p.name} ${age}`),
div({class:"date-place"},span(span({class:"born"},"Born:")),`${p.birthDate||""} ${p.birthPlace||""}`),
div({class:"date-place"},span({class:"died"},"Died:"),`${p.deathDate||""} ${p.deathPlace||""}`),
)
)
)
)
// " ${ancestors[p.id]?' style="background-color:#eee;padding:5px 10px;"':''}>
// <div class="person">${image_clause}
// <div><span class="name">${p.name} ${age}</span>
// <br /><span class="lifespan"><u>Born:</u> ${p.birthDate||""}${p.birthPlace?", ":""}${p.birthPlace||""}</span>
// <br /><span class="lifespan"><u>Died:</u> ${p.deathDate||""}${p.deathPlace?", ":""}${p.deathPlace||""}</span>
// <br /><br /><span class="msg"${ancestors[p.id]?"":' style="display:none"'}>This ancestor has been remembered (<span style="text-decoration: underline;color:blue;" onclick="forget(event)" >forget</style>)</span>
// </div></div>
// </li>`)
}
function setSearchAncestor(evt){
//console.log("treeSearch called",evt.target)
let elem=evt.target
while(elem.className !== "result"){
elem=elem.parentElement
}
const ancestor = JSON.parse(atob(elem.dataset.record))
//console.log("elem", ancestor)
localStorage.setItem("searchMethod", "ancestor")
let searchPersons = localStorage.getItem("searchPersons") || "[]"
searchPersons = JSON.parse(searchPersons)
searchPersons.push({name:ancestor.name,id:ancestor.id})
localStorage.setItem("searchPersons", JSON.stringify(searchPersons))
search()
}
function get_path_direct(elem){
const table=[`<table class="tree">`]
for(let x=elem.persons.length-1;x>0;x--){
const person = elem.persons[x]
//console.log("person", person.display.name)
table.push(`<tr><td class="${person.gender.type.endsWith("Female")?"female":"male"}"><a href="https://ancestors.familysearch.org/en/${person.id}">${person.display.name}</a></td></tr>`)
table.push(`<tr><td class="pipe">|</td></tr>`)
}
table.pop()
table.push("</table>")
return table.join("")
}
function get_path_cousin(elem){
const rels=elem.relationships
const lines=[[],[]]
let workingon=0
for(let x=0;x<rels.length-1;x++){
lines[workingon].push(elem.persons[x])
if(rels[x].person1.resourceId===rels[x+1].person1.resourceId){
// the common ancestor
//console.log(x, rels[x].person1.resourceId, elem.persons[x] )
//lines[workingon].push(elem.persons[x+1])
workingon=1
}
}
lines[1].push(elem.persons[rels.length-1])
lines[1].push(elem.persons[elem.persons.length-1])
//console.log(lines)
const pappy=lines[1].shift()
const table=[`<table class="tree"><tr><td class="${pappy.gender.type.endsWith("Female")?"female":"male"}" colspan="3" style="text-align:center"><a href="https://ancestors.familysearch.org/en/${pappy.id}">${pappy.display.name}</a></td></tr>`]
for(let x=0;x<lines[0].length;x++){
table.push(`<tr><td class="pipe">|</td><td class="pipe"> </td><td class="pipe">${x<lines[1].length?"|":""}</td></tr>`)
table.push(`<tr><td class="${lines[0][lines[0].length-1-x].gender.type.endsWith("Female")?"female":"male"}"><a href="https://ancestors.familysearch.org/en/${lines[0][lines[0].length-1-x].id}">${lines[0][lines[0].length-1-x].display.name}</a></td><td> </td>`)
if(x<lines[1].length){
table.push(`<td class="${lines[1][x].gender.type.endsWith("Female")?"female":"male"}"><a href="https://ancestors.familysearch.org/en/${lines[1][x].id}">${lines[1][x].display.name}</a>`)
}else{
table.push("<td>")
}
table.push("</td></tr>")
}
table.push("</table>")
return table.join("")
}
function get_path(elem){
const rel_name = elem.persons[elem.persons.length-1].display.relationshipDescription
if(rel_name.endsWith("father") || rel_name.endsWith("mother")){
return get_path_direct(elem)
}else{
return get_path_cousin(elem)
}
}
function show_path(evt){
let elem=evt.target
while(elem.className !== "relative-tile"){
elem=elem.parentElement
}
const tree_div=elem.querySelector(".tree")
const sketch_div=elem.querySelector(".sketch")
const meet_div=elem.querySelector(".meet")
if(tree_div.style.display==="none"){
//console.log("changing")
tree_div.style.display="block"
if(sketch_div){sketch_div.style.display="none"}
if(meet_div){ meet_div.style.display="none"}
}else{
tree_div.style.display="none"
}
elem.scrollIntoView({ behavior: "smooth", block: "start" })
}
function show_sketch(evt){
let elem=evt.target
while(elem.className !== "relative-tile"){
elem=elem.parentElement
}
const tree_div=elem.querySelector(".tree")
const sketch_div=elem.querySelector(".sketch")
const meet_div=elem.querySelector(".meet")
if(sketch_div.style.display==="none"){
//console.log("changing")
if(sketch_div){sketch_div.style.display="block"}
if(tree_div){tree_div.style.display="none"}
meet_div.style.display="none"
}else{
sketch_div.style.display="none"
}
elem.scrollIntoView({ behavior: "smooth", block: "start" })
}
function show_meet(evt){
let elem=evt.target
while(elem.className !== "relative-tile"){
elem=elem.parentElement
}
const tree_div=elem.querySelector(".tree")
const sketch_div=elem.querySelector(".sketch")
const meet_div=elem.querySelector(".meet")
if(meet_div.style.display==="none"){
//console.log("changing")
if(sketch_div){sketch_div.style.display="none"}
if(tree_div){tree_div.style.display="none"}
meet_div.style.display="block"
}else{
meet_div.style.display="none"
}
elem.scrollIntoView({ behavior: "smooth", block: "start" })
}
///////////////////////////////////////////////////////////////////////
// //
// Old Common Functions //
// //
///////////////////////////////////////////////////////////////////////
function remember_ancestors(ancestors){
localStorage.setItem("ancestors",JSON.stringify(ancestors))
google_form(ancestors)
}
async function logged_in(){
// cannot use api function because api calls this
const access_token = localStorage.getItem("authenticatedToken")
//console.log("access_token",access_token)
//debugger
if(!access_token){
//console.log ("Not logged in: No Access Token")
return false
}
let user = localStorage.getItem("user")
if(!user) {
//console.log ("Not logged in: No user")
return false
}
user=JSON.parse(user)
//check the age of the token
if(new Date().valueOf() - localStorage.getItem("authenticatedTokenTime") < 36000000){ //
// it's been less than an hour since checking
return true
}
// it's been more than an hour since checking, check again
const url="https://api.familysearch.org/platform/tree/persons/" + user.person.id
const options={
method:"head",
headers:{
authorization:'Bearer ' + access_token
}
}
//console.log("ee",url)
const rsp = await fetch(url, options)
//console.log("rsp.status",rsp.status, rsp.status===200)
if(rsp.status===200){
localStorage.setItem("authenticatedTokenTime", new Date().valueOf())
return true
}else{
localStorage.removeItem("authenticatedToken")
return false
}
}
function uuidv4() {
return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
);
}
function get_sesion_id(){
let session_id=localStorage.getItem("sessionId")
if(!session_id){
session_id=uuidv4()
localStorage.setItem("sessionId",session_id)
}
return session_id
}
function google_form(obj={}){
// form owned by gove.allen named foundersearch
obj.sessionId = get_sesion_id()
obj.set=localStorage.getItem("personSet")
obj.version = 2
const google_url="https://docs.google.com/forms/d/e/1FAIpQLScW5De35WzEkgV-iwGPHerRKqVG1hSN3HpAN20q9Dat5-sBTw/formResponse"
fetch(google_url, {
method: `POST`,
mode: 'no-cors',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'//;charset=UTF-8'
},
body:"entry.372178544=" + encodeURIComponent(btoa(JSON.stringify(obj)))
})
}
function place_relative(id,level, div, related){
if(related){
//find first id with higher number
//console.log("placing_relative", id, level)
let elem=null
let node_count=0
for(const div of tag(id).childNodes){
node_count ++
//console.log ("node level",div.dataset.level)
if(parseInt(div.dataset.level)>level){
elem=div
break
}
}
//console.log("nodes", node_count,tag(id))
if(elem===null){
//console.log("appending")
tag(id).appendChild(div)
}else{
//console.log("inserting",elem.dataset.id,tag(elem.dataset.id))
tag("rel-" + elem.dataset.id).before(div)
}
}else{
tag("setNotRelated").appendChild(div)
}
}
function get_level(rel_name){
const num=[]
for(const digit of rel_name.split("")){
if(isNaN(digit)){
break
}
num.push(digit)
}
if(rel_name.includes("mother")||rel_name.includes("father")){
return 0
}else if(num.length===0){
return 20
}
return (parseInt(num.join(""))+2)*10
}
function showMenu (evt){
evt.stopPropagation()
const menu = tag(`menu`)
menu.classList.add(`slide`)
menu.style.left=0
if(sessionStorage.getItem(`setList`)){
getSetList()
}else{
getSetFeed()
}
tag("menu-item-" + localStorage.getItem("personSet")).classList.add("selected")
}
function showQrCodes(){
const path=location.pathname.split("/")
path.push("qr.html")
location.href=[location.origin,path[1],path[2],"qr.html"].join("/")
}
function getSetList(){
const setList = JSON.parse(sessionStorage.getItem(`setList`))
//console.log("getSetList",setList)
const html=[]
for(const doc of setList){
//console.log("doc",doc)
html.push(`<div id='menu-item-${doc.setId}' class='menu-item'><a href='/${global.year}/${global.month}/index.html?${doc.setId}'><div class='hang'>${doc.title}</div></a></div>`)
}
tag(`set-list`).innerHTML = html.join(``)
}
function getSetFeed(){
//console.log(`getting set list`)
const url = `/feeds/posts/default/-/founderSearch?alt=json&max-results=500`
fetch(url)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => {
//console.log("data", data)
const setList=[]
for(const entry of data.feed.entry){
//console.log(entry)
const data=JSON.parse(entry.content.$t)
setList.push({
title:data.title,
desc:data.desc,
setId:data.set_id
})
}
setList.sort(compare)
sessionStorage.setItem(`setList`, JSON.stringify(setList))
getSetList()
})
}
function compare( a, b ) {
if ( a.title < b.title ){
return -1;
}
if ( a.title > b.title ){
return 1;
}
return 0;
}
function startOver(){
//console.log("starting over")
localStorage.clear()
sessionStorage.clear()
location.reload()
}
///////////////////////////////////////////////////////////////////////
// //
// Old config Functions //
// //
///////////////////////////////////////////////////////////////////////
function show_remembered_ancestors() {
const ancestors=get_remembered_ancestors()
$('.ancestor-list').html("Your remembered ancestors");
$('.result-list').hide();
$('.results, .related').empty();
$('.ancestor-list').show();
//console.log(ancestors)
for(const key of Object.keys(ancestors)){
//console.log("entry", ancestors[key])
place_ancestor(ancestors[key], ancestors)
}
}
function forget(evt){
evt.stopPropagation()
let elem = evt.currentTarget
elem.parentElement.style.display="none"
while(elem.tagName!=="LI"){
//console.log(elem.tagName)
elem = elem.parentElement
}
elem.style.backgroundColor=""
elem.style.padding=""
//console.log("e", elem.dataset.id)
const ancestors=get_remembered_ancestors()
delete ancestors[elem.dataset.id]
remember_ancestors(ancestors)
if(Object.keys(ancestors).length===0){
//tag("show-remembered-ancestors").style.display="none"
}
}
function go_to_relatives(evt){
const ancestors=get_remembered_ancestors()
const li = evt.currentTarget
let p = JSON.parse(atob(li.dataset.record))
ancestors[p.id]=p
remember_ancestors(ancestors)
//console.log("ancestore",ancestors)
location.href="relatives.html"
}
async function launch_relationships_config(evt) {
// show the relationships on the config page.
tag("show-remembered-ancestors").style.display=""
const li = evt.currentTarget
let p = JSON.parse(atob(li.dataset.record))
li.style.padding = "5px 10px"
li.style.backgroundColor = "#eee"
li.querySelector(".msg").style.display=""
$('.relationInfo').show();
$('.relationInfo').html(`<h3 class="searchInstructions">${p.name} is related to</h3><ul id="${p.id}" class="related"></ul>`);
$('.noRels').show();
$('.result-list').show();
$('.result-list').html(p.name + " " + " is realted to:")
ancestors=get_remembered_ancestors()
ancestors[p.id]=p
remember_ancestors(ancestors)
//console.log("p.id",p.id)
find_relationships(p.id)
}
function fill(){
return
//console.log("fill")
document.getElementsByName("given")[0].value="Gary"
document.getElementsByName("surname")[0].value="Allen"
document.getElementsByName("birthLikeDateBegin")[0].value="1937"
document.getElementsByName("deathLikeDateBegin")[0].value="1996"
}
async function set_search_ancestor(clicked=true){
//console.log(0)
if(localStorage.getItem("searchMethod")==="ancestor" &&
localStorage.getItem("ancestors") &&
Object.keys(localStorage.getItem("ancestors")).length>0 &&
tag("panel-ancestor").style.display===""
){
//console.log(1)
if(clicked){
//console.log(2)
location.href = 'relatives.html'
}
}else{
show_panel('panel-ancestor')
}
remember_search_method('ancestor')
}
async function set_search_myself(clicked=true){
// check to see if see we are logged in
const access_token = localStorage.getItem("accessToken")
if(await logged_in()){
//console.log("logged in =============================")
if(localStorage.getItem("searchMethod")==="myself"){
//we are logged in and we are searching as self, just search
if(clicked){
location.href = 'relatives.html'
}
// }else{
// show_panel('panel-myself');
// $("#myself-login").hide()
// $("#myself-logout").show()
// $("#myself-search").show()
}
}else{
//console.log("============================ logged out")
show_panel('panel-method');
}
remember_search_method('myself')
}
function remember_search_method(search_method){
//console.log("setting search method", search_method)
localStorage.setItem("searchMethod", search_method)
}
function logout_from_familysearch(){
//console.log("logging out")
api("platform/logout","none",{method:"POST"})
localStorage.setItem("unauthenticatedToken",localStorage.getItem("authenticatedToken"))
localStorage.removeItem("authenticatedToken")
$("#myself-login").show()
$("#myself-logout").hide()
$("#myself-search").hide()
}
initialize()
[--C0DE--]