[openstreetmap/openstreetmap-website] Tags diff in object history (Issue #4765)
Altilunium
notifications at github.com
Tue May 7 14:07:52 UTC 2024
### Problem
There is no diff visualization in the [current OSM object's history section](https://www.openstreetmap.org/node/1/history).
### Description

Embed this CSS and JS to the "/history" page
CSS :
~~~css
.history-diff-new-tag {
background: rgba(17,238,9,0.6) !important;
}
.history-diff-modified-tag {
background: rgba(223,238,9,0.6) !important;
}
.history-diff-deleted-tag {
background: rgba(238,51,9,0.6) !important;
}
.non-modified-tag .empty-version {
}
.hidden-non-modified-tag, .hidden-empty-version {
display: none;
}
.hidden-version, .hidden-h4 {
display: none;
}
#sidebar_content h2:not(.changeset-header){
font-size: 1rem;
}
h4 {
font-size: 1rem;
}
.find-deleted-user-btn {
cursor: pointer !important;
}
.copied {
background-color: red !important;
transition:all 0.3s;
}
.was-copied {
background-color: none !important;
transition:all 0.3s;
}
~~~
JS :
~~~javascript
function addHistoryLink() {
if (!location.pathname.includes("/node")
&& !location.pathname.includes("/way")
&& !location.pathname.includes("/relation")
|| location.pathname.includes("/history")
) return;
if (document.querySelector('.history_button_class')) return true;
let versionInSidebar = document.querySelector("#sidebar_content h4 a")
if (!versionInSidebar) {
return
}
let a = document.createElement("a")
let curHref = document.querySelector("#sidebar_content h4 a").href.match(/(.*)\/(\d+)$/)
a.href = curHref[1]
a.textContent = "🕒"
a.classList.add("history_button_class")
if (curHref[2] !== "1") {
versionInSidebar.after(a)
versionInSidebar.after(document.createTextNode("\xA0"))
}
}
function makeHistoryCompact() {
// todo -> toogleAttribute
if (document.querySelector(".compact-toggle-btn").textContent === "><") {
document.querySelectorAll(".non-modified-tag").forEach((el) => {
el.classList.replace("non-modified-tag", "hidden-non-modified-tag")
})
document.querySelectorAll(".empty-version").forEach((el) => {
el.classList.replace("empty-version", "hidden-empty-version")
})
document.querySelector(".compact-toggle-btn").textContent = "<>"
} else {
document.querySelectorAll(".hidden-non-modified-tag").forEach((el) => {
el.classList.replace("hidden-non-modified-tag", "non-modified-tag")
})
document.querySelectorAll(".hidden-empty-version").forEach((el) => {
el.classList.replace("hidden-empty-version", "empty-version")
})
document.querySelector(".compact-toggle-btn").textContent = "><"
}
}
function addDiffInHistory() {
addHistoryLink();
if (!location.pathname.includes("/history")
|| location.pathname === "/history"
|| location.pathname.includes("/history/")
) return;
if (document.querySelector(".compact-toggle-btn")) {
return;
}
if (!location.pathname.includes("/user/")) {
let compactToggle = document.createElement("button")
compactToggle.textContent = "><"
compactToggle.classList.add("compact-toggle-btn")
compactToggle.onclick = makeHistoryCompact
let sidebar = document.querySelector("#sidebar_content h2")
if (!sidebar) {
return
}
sidebar.appendChild(compactToggle)
}
let versions = [{tags: [], coordinates: "", wasModified: false, nodes: [], members: [], visible: true}];
// add/modification
let versionsHTML = Array.from(document.querySelectorAll(".browse-section.browse-node, .browse-section.browse-way, .browse-section.browse-relation"))
for (let ver of versionsHTML.toReversed()) {
let wasModifiedObject = false;
let version = ver.children[0].childNodes[1].href.match(/\/(\d+)$/)[1]
let kv = ver.querySelectorAll("tbody > tr") ?? [];
let tags = [];
let metainfoHTML = ver.querySelector('ul:nth-child(3) > li:nth-child(1)');
let changesetHTML = ver.querySelector('ul:nth-child(3) > li:nth-child(2)');
let changesetA = ver.querySelector('ul:nth-child(3) > li:nth-child(2) > a');
const changesetID = changesetA.textContent
let time = Array.from(metainfoHTML.children).find(i => i.localName === "time")
if (Array.from(metainfoHTML.children).some(e => e.localName === "a")) {
let a = Array.from(metainfoHTML.children).find(i => i.localName === "a")
metainfoHTML.innerHTML = ""
metainfoHTML.appendChild(time)
metainfoHTML.appendChild(document.createTextNode(" "))
metainfoHTML.appendChild(a)
metainfoHTML.appendChild(document.createTextNode(" "))
} else {
metainfoHTML.innerHTML = ""
metainfoHTML.appendChild(time)
let findBtn = document.createElement("a")
findBtn.textContent = " 🔍 "
findBtn.value = changesetID
findBtn.datetime = time.dateTime
findBtn.classList.add("find-deleted-user-btn")
findBtn.onclick = findChangesetInDiff
metainfoHTML.appendChild(findBtn)
}
changesetHTML.innerHTML = ''
let hashtag = document.createTextNode("#")
metainfoHTML.appendChild(hashtag)
metainfoHTML.appendChild(changesetA)
let visible = true
let coordinates = null
if (location.pathname.includes("/node")) {
coordinates = ver.querySelector("li:nth-child(3) > a")
if (coordinates) {
let locationHTML = ver.querySelector('ul:nth-child(3) > li:nth-child(3)');
let locationA = ver.querySelector('ul:nth-child(3) > li:nth-child(3) > a');
locationHTML.innerHTML = ''
locationHTML.appendChild(locationA)
} else {
visible = false
wasModifiedObject = true // because sometimes deleted object has tags
time.before(document.createTextNode("🗑 "))
}
}
kv.forEach(
(i) => {
let k = i.querySelector("th > a")?.textContent ?? i.querySelector("th")?.textContent;
let v = i.querySelector("td .wdplugin")?.textContent ?? i.querySelector("td > a")?.textContent ?? i.querySelector("td")?.textContent;
if (!k) {
// Human-readable Wikidata extension compatibility
return
}
tags.push([k, v])
let lastTags = versions.slice(-1)[0].tags
let tagWasModified = false
if (!lastTags.some((elem) => elem[0] === k)) {
i.querySelector("th").classList.add("history-diff-new-tag")
i.querySelector("td").classList.add("history-diff-new-tag")
wasModifiedObject = tagWasModified = true
} else if (lastTags.some((elem) => elem[0] === k)) {
lastTags.forEach((el) => {
if (el[0] === k && el[1] !== v) {
i.querySelector("th").classList.add("history-diff-modified-key")
i.querySelector("td").classList.add("history-diff-modified-tag")
i.title = `was: "${el[1]}"`;
wasModifiedObject = tagWasModified = true
}
})
}
if (!tagWasModified) {
i.querySelector("th").classList.add("non-modified-tag")
i.querySelector("td").classList.add("non-modified-tag")
}
}
)
let lastCoordinates = versions.slice(-1)[0].coordinates
if (visible && coordinates && versions.length > 1 && coordinates.href !== lastCoordinates) {
if (lastCoordinates) {
const curLat = coordinates.querySelector(".latitude").textContent.replace(",", ".");
const curLon = coordinates.querySelector(".longitude").textContent.replace(",", ".");
const lastLat = lastCoordinates.match(/#map=.+\/(.+)\/(.+)$/)[1];
const lastLon = lastCoordinates.match(/#map=.+\/(.+)\/(.+)$/)[2];
const distInMeters = getDistanceFromLatLonInKm(
Number.parseFloat(lastLat),
Number.parseFloat(lastLon),
Number.parseFloat(curLat),
Number.parseFloat(curLon)
) * 1000;
debugger
const distTxt = document.createElement("span")
distTxt.textContent = `${distInMeters.toFixed(1)}m`
distTxt.classList.add("history-diff-modified-tag")
coordinates.after(distTxt);
coordinates.after(document.createTextNode(" "));
}
wasModifiedObject = true
}
let childNodes = null
if (location.pathname.includes("/way") || location.pathname.includes("/relation")) {
childNodes = Array.from(ver.querySelectorAll("details ul.list-unstyled li a:first-child")).map((el) => el.href)
let lastChildNodes = versions.slice(-1)[0].nodes
if (version > 1 &&
(childNodes.length !== lastChildNodes.length
|| childNodes.some((el, index) => lastChildNodes[index] !== childNodes[index]))) {
ver.querySelector("details > summary")?.classList.add("history-diff-modified-tag")
wasModifiedObject = true
}
ver.querySelector("details")?.removeAttribute("open")
}
versions.push({
tags: tags,
coordinates: coordinates?.href ?? lastCoordinates,
wasModified: wasModifiedObject,
nodes: childNodes,
members: [],
visible: visible
})
ver.querySelectorAll("h4").forEach((el, index) => (index !== 0) ? el.classList.add("hidden-h4") : null)
}
// deletion
Array.from(versionsHTML).forEach((x, index) => {
if (versionsHTML.length <= index + 1) return;
versions.toReversed()[index + 1].tags.forEach((tag) => {
let k = tag[0]
let v = tag[1]
if (!versions.toReversed()[index].tags.some((elem) => elem[0] === k)) {
let tr = document.createElement("tr")
let th = document.createElement("th")
th.textContent = k
th.classList.add("history-diff-deleted-tag", "py-1", "border-grey", "table-light", "fw-normal")
let td = document.createElement("td")
td.textContent = v
td.classList.add("history-diff-deleted-tag", "py-1", "border-grey", "table-light", "fw-normal")
tr.appendChild(th)
tr.appendChild(td)
if (!x.querySelector("tbody")) {
let tableDiv = document.createElement("table")
tableDiv.classList.add("mb-3", "border", "border-secondary-subtle", "rounded", "overflow-hidden")
let table = document.createElement("table")
table.classList.add("mb-0", "browse-tag-list", "table", "align-middle")
let tbody = document.createElement("tbody")
table.appendChild(tbody)
tableDiv.appendChild(table)
x.appendChild(tableDiv)
}
x.querySelector("tbody").prepend(tr)
versions[versions.length - index - 1].wasModified = true
}
})
if (!versions[versions.length - index - 1].wasModified) {
let spoiler = document.createElement("details")
let summary = document.createElement("summary")
summary.textContent = x.querySelector("a").textContent
spoiler.innerHTML = x.innerHTML
spoiler.prepend(summary)
spoiler.classList.add("empty-version")
x.replaceWith(spoiler)
}
})
Array.from(document.getElementsByClassName("browse-section browse-redacted")).forEach(
(elem) => {
elem.classList.add("hidden-version")
}
)
makeHistoryCompact();
}
addDiffInHistory();
~~~
[Test via Tampermonkey](https://github.com/deevroman/better-osm-org?tab=readme-ov-file)
### Screenshots
_No response_
--
Reply to this email directly or view it on GitHub:
https://github.com/openstreetmap/openstreetmap-website/issues/4765
You are receiving this because you are subscribed to this thread.
Message ID: <openstreetmap/openstreetmap-website/issues/4765 at github.com>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.openstreetmap.org/pipermail/rails-dev/attachments/20240507/d7273158/attachment-0001.htm>
More information about the rails-dev
mailing list