-
Notifications
You must be signed in to change notification settings - Fork 6
/
iNat_journal_posts.html
203 lines (193 loc) · 8.68 KB
/
iNat_journal_posts.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, minimum-scale=1.0" />
<meta name="description" content="iNaturalist Journal Posts" />
<title>iNaturalist Journal Posts</title>
<style>
:root {
background: var(--color-base);
color: var(--color-text);
font: 14px Sans-Serif;
--color-base: white;
--color-alt: whitesmoke;
--color-brand: forestgreen;
--color-text: black;
--color-text-invert: white;
--color-text-link: royalblue;
--color-border: lightgray;
--color-hover: lightgray;
--color-base-translucent: rgba(255,255,255,0.85);
}
@media (prefers-color-scheme: dark) {
:root {
--color-base: black;
--color-alt: #171717;
--color-brand: forestgreen;
--color-text: #bababa;
--color-text-invert: black;
--color-text-link: cornflowerblue;
--color-border: #444;
--color-hover: #444;
--color-base-translucent: rgba(0,0,0,0.85);
}
}
#main { width:100%; }
table, td, th { border-collapse:collapse; margin:0; padding:4px; }
th { position:-webkit-sticky /*Safari*/; position:sticky; top:0; font-weight:600; background:var(--color-brand); color:var(--color-text-invert); text-align:left; vertical-align:bottom; }
tbody>tr { border-width:1px 0px; border-style:solid; border-color:var(--color-border); background:var(--color-alt);}
tr:nth-child(even) { background:var(--color-base); }
.tar { text-align:right; }
.icon { height:48px; width:48px; border-radius:50%; }
.photo { height:64px; width:64px; }
img { margin:0; padding:0; border:0; }
a { text-decoration:none; color:var(--color-text-link); }
a:hover { background:var(--color-hover); }
</style>
</head>
<body>
<script>
let winurlstr = window.location.href;
let winurlsearchstr = window.location.search;
let winurlexsearchstr = winurlstr.replace(winurlsearchstr,'');
let winurlparams = new URLSearchParams(winurlsearchstr.substring(1));
let p_user_id = winurlparams.get('login');
if (p_user_id===null) { p_user_id = winurlparams.get('user_id'); };
let p_project_id = winurlparams.get('project_id');
let p_options = winurlparams.get('options') || [];
function fdate(str,dateonly=false) {
str = str.replace(/t/i,' '); //replaces T (case insensitive) with a space
if (dateonly) { str = str.split(' ')[0]; }
else {
str = str.replace(/([+-]\d{2}\:?\d{2})/,' ($1)'); //puts parenthesis around time zone offset
str = str.replace(/z/i,' (+00:00)'); //replaces Z (case insensitve) with UTC
str = str.replace('+00:00','±00:00');
};
return str;
};
function furl(url,txt=url) { return '<a href="'+url+'">'+txt+'</a>'; };
function famp(str) { return str.replace(/&/g,'&'); };
function fcomnum(n) { return n.toString().replace(/\B(?=(\d{3})+(?!\d))/g,',') };
function faddelem(etype,eparent=null,eattributes={}) {
let eobj = document.createElement(etype);
for (let [key,value] of Object.entries(eattributes)) {
if ( typeof value === 'object' && value !== null ) {
for (let [subkey,subvalue] of Object.entries(value)) { eobj[key][subkey] = subvalue; };
}
else { eobj[key] = value; };
};
if (eparent) { eparent.appendChild(eobj); };
return eobj;
};
function faddelems(etype,eparent=null,eattributes=[]) { for (let e of eattributes) { faddelem(etype,eparent,e); }; };
function fround(num,p=0) {
let n = num*1;
return n.toFixed(p);
};
function frenum(columnNo=0) {
let tb = document.getElementById('tbody');
for (let r = 0, row; row = tb.rows[r]; r++) { row.children[columnNo].innerText = r+1; };
};
async function fresults(fetchurlbase) {
// table and headers
let table = faddelem('table',document.body,{id:'main'});
let thead = faddelem('thead',table);
let hrow = faddelem('tr',thead);
let labels = [
{innerText:'#'},
{innerText:'ID'},
{innerText:'Title'},
{innerText:'Published Date'},
{innerText:'Created Date'},
{innerText:'Updated Date'},
{innerText:'User Icon'},
{innerText:'User Login'},
];
if (!p_options.includes('nosource')) {
labels = [...labels,
{innerText:'Source Type'},
{innerText:'Source ID'},
{innerText:'Source Name'},
];
};
if (!p_options.includes('nobody')) { labels.push({innerText:'Body'}); };
faddelems('th',hrow,labels);
// table body
let tbody = faddelem('tbody',table,{id:'tbody'});
// there shouldn't be that many posts. i'll set an arbitrary page limit here just in case something goes wrong.
let recnum = 0;
let page = 0;
let per_page = 200;
let maxpage = 15;
while (page>=0 && page<maxpage) {
page++;
let fetchURL = fetchurlbase+'&per_page='+per_page+'&page='+page;
await fetch(fetchURL)
.then((response) => {
if (!response.ok) {
throw new Error(response.status+' ('+response.statusText+') returned from '+response.url);
page = -999;
};
return response.json();
})
.then((results) => {
if (results.length>0) {
// add table rows
for (let rec of results) {
recnum++;
let brow = faddelem('tr',tbody);
let values = [
{innerText:recnum},
{innerHTML:furl('https://www.inaturalist.org/posts/'+rec.id,rec.id)},
{innerText:rec.title},
{innerText:fdate(rec.published_at)},
{innerText:fdate(rec.created_at)},
{innerText:fdate(rec.updated_at)},
{innerHTML:'<img class="icon" src="'+rec.user.user_icon_url+'" />'},
{innerHTML:furl('https://www.inaturalist.org/users/'+rec.user.login,rec.user.login)},
];
if (!p_options.includes('nosource')) {
values = [...values,
{innerText:rec.parent_type},
{innerText:rec.parent_id},
{innerText:rec.parent.name??rec.parent.title??''},
];
};
if (!p_options.includes('nobody')) { values.push({innerText:rec.body}); };
faddelems('td',brow,values);
};
};
if (results.length<per_page) { page = -1; };
})
.catch((err) => {
console.error(err.message);
faddelem('p',document.body,null,null,'There was a problem retrieving journal posts. Error '+err.message+'.')
});
};
// there's no total count returned from the API. so this is just a workaround to get a proper count for now.
let reccount = document.getElementById('reccount');
reccount.innerText = fcomnum(recnum);
};
faddelem('h1',document.body,{innerText:'iNaturalist Journal Posts'});
if (p_user_id===null&&p_project_id===null) {
let instructions = [
{innerHTML:'This page lists journal posts for a single user OR project. It may be useful when you have a made a lot of journal posts and want to see them all on one page.'},
{innerHTML:'To use this page, add a user_id / login OR project_id parameter and value to the URL. For example, if you want to get posts for user loarie, then you would open '+furl(famp(winurlexsearchstr+'?login=loarie'))+' in your browser. If you input both a user_id and a project_id, the page will ignore the project_id parameter. If you input an invalid user_id or project_id, the page will attempt to display the official iNaturalist site posts. Note that when you search for posts by user_id, you will get back only personal journal posts, not project journal posts.'},
{innerHTML:'A special &options parameter allows you to exclude the source and body columns from the results. To exclude source columns, use &options=nosource. To exclude body, use &options=nobody. To exclude source and body, use &options=nobody,nosource. Just for simplicity, the body text returned by this page will show all the markup syntax rather than formatted text.'},
];
faddelems('p',document.body,instructions);
}
else {
let apibase = 'https://api.inaturalist.org/v1/posts';
let apiurlparams = [];
if (p_user_id) { apiurlparams.push('login='+p_user_id); };
if (p_project_id) { apiurlparams.push('project_id='+p_project_id); };
let apiurl = apibase+'?'+apiurlparams.join('&');
faddelem('p',document.body,{innerHTML:'base API request URL: '+furl(apiurl)+'<br />'
+'total journal posts: <span id="reccount">0</span>'});
fresults(apiurl);
};
</script>
</body>
</html>