';
if (pokestop.name === null || pokestop.name === '') {
content += '
';
@@ -3073,6 +3201,7 @@ function getPokestopPopupContent (pokestop) {
}
content += getNavigation(pokestop);
return content;
+ */
}
function getPossibleInvasionRewards (pokestop) {
@@ -3127,17 +3256,14 @@ function getGymPopupContent (gym) {
const now = new Date();
const raidBattleDate = new Date(gym.raid_battle_timestamp * 1000);
const raidEndDate = new Date(gym.raid_end_timestamp * 1000);
+ const updatedDate = new Date(gym.updated * 1000);
+ const modifiedDate = new Date(gym.last_modified_timestamp * 1000);
const isRaid = raidEndDate >= now && parseInt(gym.raid_level) > 0;
const isRaidBattle = raidBattleDate <= now && isRaid;
+ const hasRaidBoss = gym.raid_pokemon_id > 0;
- let gymName = '';
- if (gym.name === null || gym.name === '') {
- gymName = 'Unknown Gym Name';
- } else {
- gymName = gym.name;
- }
-
+ let gymName = gym.name ? gym.name : 'Unknown Gym Name';
let titleSize = 16;//'medium';
if (gymName.length > 40) {
//titleSize = 'xx-small';
@@ -3150,6 +3276,58 @@ function getGymPopupContent (gym) {
titleSize = 14;
}
+ gym.gym_name = gymName;
+ gym.title_size = titleSize;
+ gym.is_raid = isRaid;
+ gym.is_raid_battle = isRaidBattle;
+ gym.available_slots = gym.availble_slots === 0 ? 'Full' : gym.availble_slots === 6 ? 'Empty' : gym.availble_slots;
+ gym.has_raid_boss = hasRaidBoss;
+ let pokemonName = '';
+ if (hasRaidBoss && isRaidBattle) {
+ pokemonName = getPokemonName(gym.raid_pokemon_id);
+ if (gym.raid_pokemon_form > 0) {
+ const formName = getFormName(gym.raid_pokemon_form);
+ pokemonName = formName + ' ' + pokemonName;
+ gym.form_name = formName;
+ }
+ pokemonName += ' ' + getGenderIcon(gym.raid_pokemon_gender);
+ } else if (isRaidBattle) {
+ pokemonName = 'Unknown Raid Boss';
+ } else {
+ pokemonName = 'Level ' + gym.raid_level + ' Egg';
+ }
+ let pokemonTypes = [];
+ if (hasRaidBoss && isRaidBattle) {
+ const pkmn = masterfile.pokemon[gym.raid_pokemon_id];
+ if (pkmn) {
+ const types = pkmn.types;
+ if (types && types.length > 0) {
+ if (types.length === 2) {
+ pokemonTypes.push(types[0].toLowerCase());
+ pokemonTypes.push(types[1].toLowerCase());
+ } else {
+ pokemonTypes.push(types[0].toLowerCase());
+ }
+ }
+ }
+ }
+ gym.pokemon_types = pokemonTypes;
+ if (hasRaidBoss) {
+ gym.pokemon_icon = `${availableIconStyles[selectedIconStyle].path}/${getPokemonIcon(gym.raid_pokemon_id, gym.raid_pokemon_form, gym.raid_pokemon_evolution, gym.raid_pokemon_gender, gym.raid_pokemon_costume)}.png`;
+ }
+ gym.pokemon_name = pokemonName;
+ gym.move_1_name = getMoveName(gym.raid_pokemon_move_1);
+ gym.move_2_name = getMoveName(gym.raid_pokemon_move_2);
+ gym.guarding_pokemon_name = getPokemonName(gym.guarding_pokemon_id);
+ gym.team_name = getTeamName(gym.team_id);
+ gym.icon_path = '/img';
+ gym.time_until_battle = getTimeUntill(raidBattleDate);
+ gym.time_until_end = getTimeUntill(raidEndDate);
+ gym.time_since_updated = getTimeSince(updatedDate);
+ gym.time_since_modified = getTimeSince(modifiedDate);
+ const templateData = getTemplateData('gym', gym);
+ return templateData;
+ /*
let content =
'
' + // START 1ST ROW
'
' +
@@ -3317,21 +3495,27 @@ function getGymPopupContent (gym) {
}
content += getNavigation(gym);
return content;
+ */
}
function getCellPopupContent (cell) {
- let content = '
';
- content += 'Level ' + cell.level + ' S2 Cell
';
- content += 'Id: ' + cell.id + '
';
-
- const updatedDate = new Date(cell.updated * 1000);
-
- content += 'Last Updated: ' + updatedDate.toLocaleTimeString() + ' (' + getTimeSince(updatedDate) + ')';
- content += '';
+ //cell.time_since = getTimeSince(new Date(cell.updated * 1000));
+ //const templateData = getTemplateData('cell', cell);
+ //return templateData;
+ const content = `
+
+ Level ${cell.level} S2 Cell
+ Id: ${cell.id}
+ Last Updated: ${new Date(cell.updated * 1000).toLocaleTimeString()} (${getTimeSince(new Date(cell.updated * 1000))})
+
+ `;
return content;
}
function getSubmissionTypeCellPopupContent (cell) {
+ //const templateData = getTemplateData('submission_cell', cell);
+ //return templateData;
+ const gymThreshold = [2, 6, 20];
let content = `
Level ${cell.level} S2 Cell
@@ -3340,19 +3524,19 @@ function getSubmissionTypeCellPopupContent (cell) {
Pokestop Count: ${cell.count_pokestops}
Gym Count: ${cell.count_gyms}
`;
-
- const gymThreshold = [2, 6, 20];
if (cell.count_gyms < 3) {
- content += 'Submissions until Gym: ' + (gymThreshold[cell.count_gyms] - cell.count);
+ content += `Submissions untill Gym: ${gymThreshold[cell.count_gyms] - cell.count}`;
} else {
content += 'Submissions until Gym: Never';
}
-
- if ((cell.count === 1 && cell.count_gyms < 1) || (cell.count === 5 && cell.count_gyms < 2) || (cell.count === 19 && cell.count_gyms < 3)) {
+ if ((cell.count === 1 && cell.count_gyms < 1) ||
+ (cell.count === 5 && cell.count_gyms < 2) ||
+ (cell.count === 19 && cell.count_gyms < 3)) {
content += '
Next submission will cause a Gym!';
}
-
- content += '';
+ content += `
+
+ `;
return content;
}
@@ -3364,47 +3548,19 @@ function degreesToCardinal (d) {
}
function getWeatherPopupContent (weather) {
- const weatherName = weatherTypes[weather.gameplay_condition].name;
- const weatherType = weatherTypes[weather.gameplay_condition].types;
- const updatedDate = new Date(weather.updated * 1000);
- const content = `
-
- ${weatherName}
- Boosted Types:
${weatherType}
- Cell ID: ${weather.id}
- Cell Level: ${weather.level}
- Lat: ${weather.latitude.toFixed(5)}
- Lon: ${weather.longitude.toFixed(5)}
- Gameplay Condition: ${getWeatherName(weather.gameplay_condition)}
- Wind Direction: ${weather.wind_direction}° (${degreesToCardinal(weather.wind_direction)})
- Cloud Level: ${weather.cloud_level}
- Rain Level: ${weather.rain_level}
- Wind Level: ${weather.wind_level}
- Snow Level: ${weather.snow_level}
- Fog Level: ${weather.fog_level}
- Special Effects Level: ${weather.special_effect_level}
- Severity: ${weather.severity}
- Weather Warning: ${weather.warn_weather}
- Last Updated: ${updatedDate.toLocaleTimeString()} (${getTimeSince(updatedDate)})
-
- `;
- return content;
+ weather.weather_name = weatherTypes[weather.gameplay_condition].name;
+ weather.weather_type = weatherTypes[weather.gameplay_condition].types;
+ weather.cardinal = degreesToCardinal(weather.wind_direction);
+ weather.time_since = getTimeSince(new Date(weather.updated * 1000));
+ const templateData = getTemplateData('weather', weather);
+ return templateData;
}
function getNestPopupContent(nest) {
- const lastUpdated = new Date(nest.updated * 1000);
- const pokemonName = getPokemonName(nest.pokemon_id);
- const content = `
-
- Park: ${nest.name}
- Pokemon: ${pokemonName}
- Average: ${nest.pokemon_avg.toLocaleString()}
- Count: ${nest.pokemon_count.toLocaleString()}
-
- Last Updated: ${lastUpdated.toLocaleString()}
-
- `;
- return content;
+ nest.pokemon_name = getPokemonName(nest.pokemon_id);
+ nest.last_updated = new Date(nest.updated * 1000);
+ const templateData = getTemplateData('nest', nest);
+ return templateData;
}
function getPortalPopupContent(portal) {
@@ -3425,13 +3581,8 @@ function getPortalPopupContent(portal) {
}
function getScanAreaPopupContent(name, size) {
- let content = `
-
- Area: ${name}
- Size: ${size} km2
-
- `;
- return content;
+ const templateData = getTemplateData('scanarea', { name, size });
+ return templateData;
}
function getNavigation(data) {
@@ -4065,6 +4216,10 @@ function getSpawnpointMarker (spawnpoint, ts) {
const timer = Math.round(spawnpoint.despawn_second / 60);
content += '
Despawn Timer: ' + timer + ' minutes';
}
+ /*
+ const hasTimer = spawnpoint.despawn_second !== null;
+ const templateData = getTemplateData('spawnpoint', spawnpoint);
+ */
const circle = L.circle([spawnpoint.lat, spawnpoint.lon], {
color: hasTimer ? 'green' : 'red',
fillColor: hasTimer ? 'green' : 'red',
@@ -4188,15 +4343,8 @@ function getDeviceMarker (device, ts) {
}
function getDevicePopupContent (device) {
- const lastSeenDate = new Date(device.last_seen * 1000);
- const lastSeen = lastSeenDate.toLocaleTimeString() + ' (' + getTimeSince(lastSeenDate) + ')';
- const ts = Math.round((new Date()).getTime() / 1000);
- const isOffline = isDeviceOffline(device, ts);
- const content = '
' + device.uuid + '
' +
- '
Instance: ' + device.instance_name + '
' +
- '
Last Seen: ' + lastSeen + '
' +
- '
Status: ' + (isOffline ? 'Offline' : 'Online');
- return content;
+ const data = getTemplateData('device', device);
+ return data;
}
function isDeviceOffline (device, ts) {
@@ -4261,6 +4409,32 @@ function setDespawnTimer (marker) {
}
}
+function getTemplateData(template, data) {
+ const { marker, ...dataWithoutMarker } = data;
+ let results = null;
+ $.ajax({
+ url: '/api/get_template/' + template,
+ type: 'POST',
+ data: JSON.stringify({
+ data: dataWithoutMarker,
+ _csrf: '{{csrf}}'
+ }),
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json'
+ },
+ async: false,
+ success: function (result) {
+ //console.log('result:', result);
+ results = result;
+ },
+ failure: function (err) {
+ console.error('Error:', err);
+ }
+ });
+ return results;
+}
+
// MARK: - Misc
diff --git a/templates/cell.example.ejs b/templates/cell.example.ejs
new file mode 100644
index 00000000..8be74e6f
--- /dev/null
+++ b/templates/cell.example.ejs
@@ -0,0 +1,5 @@
+
+Level <%= level %> S2 Cell
+Id: <%= id %>
+Last Updated: <%= new Date(updated * 1000).toLocaleTimeString() %> (<%= time_since %>)
+
\ No newline at end of file
diff --git a/templates/device.example.ejs b/templates/device.example.ejs
new file mode 100644
index 00000000..663dced7
--- /dev/null
+++ b/templates/device.example.ejs
@@ -0,0 +1,26 @@
+<%
+function getTimeSince (date) {
+ const diff = Math.round((new Date() - date) / 1000);
+ const h = Math.floor(diff / 3600);
+ const m = Math.floor(diff % 3600 / 60);
+ const s = Math.floor(diff % 3600 % 60);
+ let str;
+ if (h > 0) {
+ str = h + 'h ' + m + 'm ' + s + 's';
+ } else if (m > 0) {
+ str = m + 'm ' + s + 's';
+ } else {
+ str = s + 's';
+ }
+ return str;
+}
+%>
+
+
+ <%= uuid %>
+
+
+
Instance: <%= instance_name %>
+<% let lastSeenDate = new Date(last_seen * 1000); %>
+
Last Seen: <%= lastSeenDate.toLocaleTimeString() + ' (' + getTimeSince(lastSeenDate) + ')'; %>
+
Status: <%= last_seen > (Math.round(new Date().getTime() / 1000) - 15 * 60) ? 'Online' : 'Offline' %>
\ No newline at end of file
diff --git a/templates/gym.example.ejs b/templates/gym.example.ejs
new file mode 100644
index 00000000..b49031c2
--- /dev/null
+++ b/templates/gym.example.ejs
@@ -0,0 +1,133 @@
+
+
+ <%= gym_name %>
+
+
+
+

+
+
+
+
+<% if (is_raid) { %>
+
+
+
+ <% if (has_raid_boss && is_raid_battle) { %>
+

+ <% } else { %>
+

+ <% } %>
+
+
+ <% if (has_raid_boss && is_raid_battle) { %>
+ <% if (pokemon_types && pokemon_types.length > 0) { %>
+
+ <% pokemon_types.forEach(function(type) { %>
+

+ <% }); %>
+
+ <% } %>
+ <% } %>
+
+
+
+
<%= pokemon_name %>
+ <% if (has_raid_boss && is_raid_battle) { %>
+ <% if (raid_pokemon_cp) { %>
+ <% if (raid_is_exclusive) { %>
+
Level: EX
+ <% } else { %>
+
Level: <%= raid_level %>
+ <% } %>
+ <% } %>
+ <% if (raid_pokemon_move_1) { %>
+
Fast: <%= move_1_name %>
+ <% } %>
+ <% if (raid_pokemon_move_2) { %>
+
Charge: <%= move_2_name %>
+ <% } %>
+ <% if (in_battle) { %>
+
Gym last seen in battle!
+ <% } %>
+ <% if (raid_pokemon_form > 0) { %>
+
Form: <%= form_name %>
+ <% } %>
+ <% } %>
+ <% if (ex_raid_eligible) { %>
+

+ <% } %>
+
+
+
+<% } else { %>
+
+ <% if (url) {
+ let teamClass = team_id === 0
+ ? 'team-neutral'
+ : team_id === 1
+ ? 'team-mystic'
+ : team_id === 2
+ ? 'team-valor'
+ : team_id === 3
+ ? 'team-instinct'
+ : '';
+ url.replace('http://', 'https://');
+ %>
+
+

+
+ <% } %>
+
+
Team: <%= team_name %>
+
Slots Available: <%= available_slots %>
+ <% if (guarding_pokemon_id > 0) { %>
+
Guard: <%= guarding_pokemon_name %>
+ <% } %>
+ <% if (total_cp > 0) { %>
+
Total CP: <%= total_cp.toLocaleString() %>
+ <% } %>
+ <% if (in_battle > 0) { %>
+
Gym is under attack!
+ <% } %>
+ <% if (ex_raid_eligible > 0) { %>
+

+ <% } %>
+
+
+
+<% } %>
+
+
+ <% if (is_raid && !is_raid_battle) { %>
+ Raid Start: <%= new Date(raid_battle_timestamp * 1000).toLocaleString() %> (<%= time_until_battle %>)
+ <% } %>
+ <% if (is_raid) { %>
+ Raid End: <%= new Date(raid_end_timestamp * 1000).toLocaleString() %> (<%= time_until_end %>)
+ <% } %>
+
+ <% if (updated) { %> %>
+
Last Updated: <%= new Date(updated * 1000).toLocaleString() %> (<%= time_since_updated %>)
+ <% } %>
+ <% if (last_modified_timestamp) { %>
+
Last Modified: <%= new Date(last_modified_timestamp * 1000).toLocaleString() %> (<%= time_since_modified %>)
+ <% } %>
+
+
+
\ No newline at end of file
diff --git a/templates/nest.example.ejs b/templates/nest.example.ejs
new file mode 100644
index 00000000..a1d58351
--- /dev/null
+++ b/templates/nest.example.ejs
@@ -0,0 +1,8 @@
+
+ Park: <%= name %>
+ Pokemon: <%= pokemon_name %>
+ Average: <%= pokemon_avg.toLocaleString() %>
+ Count: <%= pokemon_count.toLocaleString() %>
+
+ Last Updated: <%= last_updated.toLocaleString() %>
+
\ No newline at end of file
diff --git a/templates/pokemon.example.ejs b/templates/pokemon.example.ejs
new file mode 100644
index 00000000..6421be20
--- /dev/null
+++ b/templates/pokemon.example.ejs
@@ -0,0 +1,126 @@
+
+
+
<%= pokemon_name %> <%= gender_icon %>
+
+
+
+ <% if (!(display_pokemon_id > 0) && weather > 0) { %>
+

+ <% } %>
+
+
+
+
+
+
+
+

+
+
+ <% if (pokemon_types && pokemon_types.length > 0) { %>
+ <% pokemon_types.forEach(function(type) { %>
+

+ <% }); %>
+ <% } %>
+
+
+
+ <% if (has_iv) { %>
+ <% const ivPercent = Math.round((atk_iv + def_iv + sta_iv) / 45 * 1000) / 10; %>
+
IV: <%= ivPercent %>% (A<%= atk_iv %>|D<%= def_iv %>|S<%= sta_iv%>)
+ <% } %>
+ <% if (cp) { %>
+
CP: <%= cp %> (Lvl. <%= level %>)
+ <% } %>
+ <% if (move_1) { %>
+
Fast: <%= move_1_name %>
+ <% } %>
+ <% if (move_2) { %>
+
Charge: <%= move_2_name %>
+ <% } %>
+ <% if (size > 0 && weight > 0 && (pokemon_id === 19 || pokemon_id === 129)) { %>
+
Size: <%= size %> |
Weight: <%= Math.round(weight) %>kg
+ <% } else if (weight > 0) { %>
+
Weight: <%= Math.round(weight) %>kg
+ <% } %>
+ <% if (capture_1 && capture_2 && capture_3) { %>
+
Catch Chances:
+

<%= (capture_1 * 100).toFixed(1) %>%
+

<%= (capture_2 * 100).toFixed(1) %>%
+

<%= (capture_3 * 100).toFixed(1) %>%
+ <% } %>
+
+
+
+
+
+ <% if (expire_timestamp_verified) { %>
+ Despawn Time:
+ <% } else { %>
+ Despawn Time: ~
+ <% } %>
+ <%= new Date(expire_timestamp * 1000).toLocaleString() %> (<%= time_utill_despawn %>)
+
+
+ <% if (first_seen_timestamp > 0) { %>
+ <% const firstSeenDate = new Date(first_seen_timestamp * 1000); %>
+ First Seen: <%= firstSeenDate.toLocaleString() %> (<%= time_since_first_seen %>)
+ <% } %>
+
+
+ <% if (updated > 0) { %>
+ <% const updatedDate = new Date(updated * 1000); %>
+ Latest Seen: <%= updatedDate.toLocaleString() %> (<%= time_since_updated %>)
+ <% } %>
+
+ <% if (great_league_rankings && great_league_rankings.length > 0) { %>
+
+ Great League:
+ <% for (let i = 0; i < great_league_rankings.length; i++) { %>
+ <% let ranking = great_league_rankings[i]; %>
+ <%= ranking.pokemon_name %>: #<%= ranking.rank %> (<%= ranking.percent %>%) @ <%= ranking.cp %> CP (Lvl. <%= ranking.level %>)
+ <% } %>
+ <% } %>
+ <% if (ultra_league_rankings && ultra_league_rankings.length > 0) { %>
+
+ Ultra League:
+ <% for (let i = 0; i < ultra_league_rankings.length; i++) { %>
+ <% let ranking = ultra_league_rankings[i]; %>
+ <%= ranking.pokemon_name %>: #<%= ranking.rank %> (<%= ranking.percent %>%) @ <%= ranking.cp %> CP (Lvl. <%= ranking.level %>)
+ <% } %>
+ <% } %>
+
+
+
+
+
+
[Hide]
+
[Exclude]
+
+
+
+ <% if (enable_scouting) { %>
+
+
+ <% } %>
+
\ No newline at end of file
diff --git a/templates/pokestop.example.ejs b/templates/pokestop.example.ejs
new file mode 100644
index 00000000..58ea3915
--- /dev/null
+++ b/templates/pokestop.example.ejs
@@ -0,0 +1,75 @@
+
+ <% if (name) { %>
+
<%= name %>
+ <% } else { %>
+
Unknown Pokestop Name
+ <% } %>
+
+ <% if (url) {
+ let lureClass = is_lure_active && lure_id !== 0
+ ? (lure_id === 501
+ ? 'lure-normal'
+ : lure_id === 502
+ ? 'lure-glacial'
+ : lure_id === 503
+ ? 'lure-mossy'
+ : lure_id === 504
+ ? 'lure-magnetic'
+ : 'lure-normal')
+ : '';
+ %>
+
 %>)
+ <% } %>
+
+
+
+
+ <% if (is_lure_active) { %>
+
Lure Type: <%= lure_name %>
+
Lure End Time: <%= lure_expire_time %> (<%= lure_time_since %>)
+ <% } %>
+
+ <% if (is_invasion_active) { %>
+
Team Rocket Invasion
+
Grunt Type: <%= grunt_name %>
+
End Time: <%= invasion_expire_time %> (<%= invasion_time_since %>)
+ <% } %>
+
+ <% if (quest_type) { %>
+
Quest Condition: <%= quest_name %> <% if (quest_conditions_formatted) { %><%= quest_conditions_formatted %><% } %>
+ <% quest_rewards_formatted.forEach(function(reward) { %>
+
Quest Reward: <%= reward %>
+ <% }); %>
+
+ <% } %>
+
+ <% if (updated) { %>
+
Last Updated: <%= new Date(updated * 1000).toLocaleString() %> (<%= time_since %>)
+ <% } %>
+
+
+ <% if (quest_type) { %>
+ <% let questReward = quest_rewards ? quest_rewards[0] : {} %>
+
[Exclude]
+ <% } %>
+
+
+
+
\ No newline at end of file
diff --git a/templates/scanarea.example.ejs b/templates/scanarea.example.ejs
new file mode 100644
index 00000000..3bb05a74
--- /dev/null
+++ b/templates/scanarea.example.ejs
@@ -0,0 +1,4 @@
+
+ Area: <%= name %>
+ Size: <%= size %> km2
+
\ No newline at end of file
diff --git a/templates/spawnpoint.example.ejs b/templates/spawnpoint.example.ejs
new file mode 100644
index 00000000..72248ad9
--- /dev/null
+++ b/templates/spawnpoint.example.ejs
@@ -0,0 +1,4 @@
+
Spawnpoint
+<% if (despawn_second) { %>
+
Despawn Timer: <%= Math.round(despawn_second / 60) %> minutes
+<% } %>
\ No newline at end of file
diff --git a/templates/submission_cell.example.ejs b/templates/submission_cell.example.ejs
new file mode 100644
index 00000000..0239cb20
--- /dev/null
+++ b/templates/submission_cell.example.ejs
@@ -0,0 +1,17 @@
+
+Level <%= level %> S2 Cell
+Id: <%= id %>
+Total Count: <%= count %>
+Pokestop Count: <%= count_pokestops %>
+Gym Count: <%= count_gyms %>
+
+<% const gymThreshold = [2, 6, 20]; %>
+<% if (count_gyms < 3) { %>
+ Submissions untill Gym: <%= gymThreshold[count_gyms] - count %>
+<% } else { %>
+ Submissions untill Gym: Never
+<% } %>
+<% if ((count === 1 && count_gyms < 1) || (count === 5 && count_gyms < 2) || (count === 19 && count_gyms < 3)) { %>
+
Next submission will cause a Gym!
+<% } %>
+
\ No newline at end of file
diff --git a/templates/weather.example.ejs b/templates/weather.example.ejs
new file mode 100644
index 00000000..0bb11a49
--- /dev/null
+++ b/templates/weather.example.ejs
@@ -0,0 +1,19 @@
+
+<%= weather_name %>
+Boosted Types:
<%= weather_type %>
+Cell ID: <%= id %>
+Cell Level: <%= level %>
+Lat: <%= latitude.toFixed(5) %>
+Lon: <%= longitude.toFixed(5) %>
+Gameplay Condition: <%= gameplay_condition %>
+Wind Direction: <%= wind_direction %>° (<%= cardinal %>)
+Cloud Level: <%= cloud_level %>
+Rain Level: <%= rain_level %>
+Wind Level: <%= wind_level %>
+Snow Level: <%= snow_level %>
+Fog Level: <%= fog_level %>
+Special Effects Level: <%= special_effect_level %>
+Severity: <%= severity %>
+Weather Warning: <%= warn_weather %>
+Last Updated: <%= new Date(updated * 1000).toLocaleTimeString() %> (<%= time_since %>)
+
\ No newline at end of file