add: inlink and outlink section
add: link relation graph direction
This commit is contained in:
parent
b16d0e13c3
commit
a22dad3250
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4670" width="16" height="16">
|
||||
<path
|
||||
d="m4.23,2.66c0,.52-.43.94-.95.93-.52,0-.94-.42-.94-.94s.42-.94.94-.94c.52,0,.94.42.95.93v.02h0Zm4.11-.11c1.14.51,2.18,1.29,2.66,2.4,1.27-.33,2.61.17,3.36,1.25.75,1.08.75,2.51,0,3.59-.75,1.08-2.09,1.58-3.36,1.25-.47,1.11-1.52,1.9-2.66,2.4-.94.42-2,.68-3.04.78-.44,1-1.54,1.52-2.59,1.24-1.05-.29-1.74-1.3-1.61-2.38.13-1.08,1.03-1.91,2.11-1.94,1.09-.04,2.04.73,2.24,1.8.82-.11,1.65-.33,2.37-.65,1.03-.46,1.75-1.08,2.03-1.81-.6-.46-1.01-1.12-1.17-1.87h-3.3c-.31,1.05-1.35,1.71-2.43,1.55-1.08-.16-1.88-1.09-1.88-2.18,0-1.09.8-2.02,1.88-2.17,1.08-.16,2.11.5,2.43,1.54h3.3c.15-.74.57-1.4,1.17-1.87-.29-.73-1-1.35-2.03-1.81-.72-.32-1.55-.54-2.37-.65-.2,1.07-1.15,1.84-2.24,1.8-1.09-.03-1.99-.86-2.12-1.94-.13-1.08.56-2.09,1.61-2.38,1.05-.29,2.15.24,2.59,1.24,1.03.11,2.1.37,3.04.78h0Zm-4.11,10.79c0-.52-.43-.94-.95-.93-.52,0-.94.42-.94.94,0,.52.42.94.94.94.52,0,.94-.42.95-.93v-.02Zm9.43-5.34c0-1.04-.84-1.89-1.89-1.89s-1.89.84-1.89,1.89.84,1.89,1.89,1.89,1.89-.84,1.89-1.89h0Zm-9.43,0c0-.52-.42-.94-.94-.94s-.94.42-.94.94.42.94.94.94.94-.42.94-.94h0Zm0,0"
|
||||
fill="#e8af59" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4670" width="20" height="20">
|
||||
<path
|
||||
d="m5.27,3.31c0,.65-.54,1.17-1.19,1.17-.65,0-1.18-.53-1.18-1.18s.53-1.18,1.18-1.18c.65,0,1.18.52,1.19,1.17v.02h0Zm5.15-.14c1.43.63,2.74,1.62,3.33,3.01,1.6-.41,3.28.22,4.22,1.57.94,1.35.94,3.15,0,4.5-.94,1.35-2.62,1.98-4.22,1.57-.6,1.4-1.9,2.38-3.33,3.01-1.18.52-2.51.85-3.81.98-.55,1.25-1.93,1.91-3.25,1.55-1.32-.36-2.18-1.63-2.02-2.98.16-1.36,1.29-2.39,2.65-2.44,1.37-.04,2.56.92,2.8,2.26,1.03-.14,2.07-.41,2.98-.81,1.29-.57,2.19-1.35,2.55-2.26-.75-.58-1.27-1.41-1.46-2.34h-4.14c-.39,1.31-1.69,2.14-3.04,1.94-1.36-.2-2.36-1.36-2.36-2.73,0-1.37,1.01-2.53,2.36-2.72,1.35-.2,2.65.63,3.04,1.94h4.14c.19-.93.71-1.76,1.46-2.34-.36-.91-1.26-1.69-2.55-2.26-.91-.4-1.95-.68-2.98-.81-.25,1.34-1.44,2.31-2.8,2.26-1.37-.04-2.49-1.08-2.65-2.44-.16-1.36.7-2.63,2.02-2.98,1.32-.36,2.7.3,3.25,1.55,1.29.13,2.63.46,3.81.98h0Zm-5.15,13.52c0-.65-.54-1.17-1.19-1.17-.65,0-1.18.53-1.18,1.18s.53,1.18,1.18,1.18c.65,0,1.18-.52,1.19-1.17v-.02Zm11.83-6.69c0-1.31-1.06-2.37-2.37-2.37s-2.37,1.06-2.37,2.37c0,1.31,1.06,2.37,2.37,2.37s2.37-1.06,2.37-2.37h0Zm-11.83,0c0-.65-.53-1.18-1.18-1.18s-1.18.53-1.18,1.18.53,1.18,1.18,1.18,1.18-.53,1.18-1.18h0Zm0,0"
|
||||
fill="#e8af59" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4670" width="16" height="16">
|
||||
<path
|
||||
d="m11.77,2.66v-.02c0-.52.43-.94.95-.93.52,0,.94.42.94.94s-.42.94-.94.94c-.52,0-.94-.42-.95-.93h0Zm-4.11-.11c.94-.42,2-.68,3.04-.78.44-1,1.54-1.52,2.59-1.24,1.05.29,1.73,1.3,1.61,2.38-.13,1.08-1.03,1.91-2.12,1.94-1.09.03-2.04-.73-2.24-1.8-.82.11-1.65.33-2.37.65-1.03.46-1.75,1.08-2.03,1.81.6.46,1.01,1.12,1.17,1.87h3.3c.31-1.04,1.35-1.7,2.43-1.54,1.08.16,1.88,1.08,1.88,2.17,0,1.09-.8,2.02-1.88,2.18-1.08.16-2.12-.5-2.43-1.55h-3.3c-.15.74-.57,1.4-1.17,1.87.29.73,1,1.35,2.03,1.81.72.32,1.55.54,2.37.65.2-1.07,1.15-1.84,2.24-1.8,1.09.04,1.99.86,2.11,1.94.13,1.08-.56,2.09-1.61,2.38-1.05.29-2.15-.24-2.59-1.24-1.03-.11-2.1-.37-3.04-.78-1.14-.51-2.18-1.29-2.66-2.4-1.27.33-2.61-.17-3.36-1.25-.75-1.08-.75-2.51,0-3.59.75-1.08,2.09-1.58,3.36-1.25.47-1.11,1.52-1.9,2.66-2.4h0Zm4.11,10.8c0,.52.43.94.95.93.52,0,.94-.42.94-.94s-.42-.94-.94-.94c-.52,0-.94.42-.95.93v.02ZM2.34,8c0,1.04.84,1.89,1.89,1.89s1.89-.84,1.89-1.89c0-1.04-.84-1.89-1.89-1.89s-1.89.84-1.89,1.89h0Zm9.43,0c0,.52.42.94.94.94s.94-.42.94-.94-.42-.94-.94-.94-.94.42-.94.94h0Zm0,0"
|
||||
fill="#e8af59" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4670" width="20" height="20">
|
||||
<path
|
||||
d="m14.71,3.28v-.02c0-.65.54-1.17,1.19-1.17.65,0,1.18.53,1.18,1.18s-.53,1.18-1.18,1.18c-.65,0-1.18-.52-1.19-1.17h0Zm-5.15-.14c1.18-.52,2.51-.85,3.81-.98.55-1.25,1.93-1.91,3.25-1.55,1.32.36,2.17,1.63,2.02,2.98-.16,1.36-1.29,2.39-2.65,2.44-1.37.04-2.56-.92-2.8-2.26-1.03.14-2.07.41-2.98.81-1.29.57-2.19,1.35-2.55,2.26.75.58,1.27,1.41,1.46,2.34h4.14c.39-1.31,1.69-2.13,3.04-1.94,1.35.2,2.36,1.36,2.36,2.72,0,1.37-1,2.54-2.36,2.73-1.36.2-2.65-.63-3.04-1.94h-4.14c-.19.93-.71,1.76-1.46,2.34.36.91,1.26,1.69,2.55,2.26.91.4,1.95.68,2.98.81.25-1.34,1.44-2.3,2.8-2.26,1.37.04,2.49,1.08,2.65,2.44.16,1.36-.7,2.63-2.02,2.98-1.32.36-2.7-.3-3.25-1.55-1.29-.13-2.63-.46-3.81-.98-1.43-.63-2.74-1.62-3.33-3.01-1.6.41-3.28-.22-4.22-1.57-.94-1.35-.94-3.15,0-4.5.94-1.35,2.62-1.98,4.22-1.57.6-1.4,1.9-2.38,3.33-3.01h0Zm5.15,13.55c0,.65.54,1.17,1.19,1.17.65,0,1.18-.53,1.18-1.18s-.53-1.18-1.18-1.18c-.65,0-1.18.52-1.19,1.17v.02ZM2.88,9.97c0,1.31,1.06,2.37,2.37,2.37s2.37-1.06,2.37-2.37c0-1.31-1.06-2.37-2.37-2.37s-2.37,1.06-2.37,2.37h0Zm11.83,0c0,.65.53,1.18,1.18,1.18s1.18-.53,1.18-1.18-.53-1.18-1.18-1.18-1.18.53-1.18,1.18h0Zm0,0"
|
||||
fill="#e8af59" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
|
|
@ -12,13 +12,6 @@
|
|||
background: var(--material-background);
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.node:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
|
@ -54,13 +47,13 @@
|
|||
}
|
||||
|
||||
// Specify the color scale.
|
||||
const color = d3.scaleOrdinal([1, 2], ["grey", "#e8af59"]);
|
||||
const color = d3.scaleOrdinal([1, 2], ["grey", "#A88F6A"]);
|
||||
|
||||
// The force simulation mutates links and nodes, so create a copy
|
||||
// so that re-evaluating this cell produces the same result.
|
||||
const links = data.links.map((d) => ({ ...d }));
|
||||
const nodes = data.nodes.map((d) => ({ ...d }));
|
||||
|
||||
const linkColor = "#e8af59";
|
||||
// Create a simulation with several forces.
|
||||
const simulation = d3
|
||||
.forceSimulation(nodes)
|
||||
|
|
@ -83,15 +76,34 @@
|
|||
.attr("viewBox", [-width / 2, -height / 2, width, height])
|
||||
.attr("style", "max-width: 100%; height: auto;");
|
||||
|
||||
// Add a line for each link, and a circle for each node.
|
||||
svg
|
||||
.append("defs")
|
||||
.append("marker")
|
||||
.attr("id", "arrowhead")
|
||||
.attr("refX", 25)
|
||||
.attr("refY", 6)
|
||||
.attr("orient", "auto-start-reverse")
|
||||
.attr("markerWidth", 20)
|
||||
.attr("markerHeight", 12)
|
||||
.attr("markerUnits", "userSpaceOnUse")
|
||||
.append("path")
|
||||
.attr("d", "M 1 1 L 18 6 L 1 11 Z")
|
||||
.attr("fill", linkColor)
|
||||
.attr("stroke", linkColor)
|
||||
.attr("class", "arrow-head");
|
||||
|
||||
const link = svg
|
||||
.append("g")
|
||||
.attr("stroke", "#999")
|
||||
.attr("stroke-opacity", 0.6)
|
||||
.selectAll("line")
|
||||
.attr("fill", "none")
|
||||
.selectAll("path")
|
||||
.data(links)
|
||||
.join("line")
|
||||
.attr("stroke-width", (d) => Math.sqrt(d.value));
|
||||
.attr("stroke-width", (d) => Math.sqrt(d.value))
|
||||
.attr("stroke", linkColor)
|
||||
.attr("marker-start", (d) =>
|
||||
d.type === "both" ? "url(#arrowhead)" : "",
|
||||
)
|
||||
.attr("marker-end", "url(#arrowhead)");
|
||||
|
||||
const node = svg
|
||||
.append("g")
|
||||
|
|
@ -114,7 +126,7 @@
|
|||
.attr("text-anchor", "middle") // Ensure the text is centered
|
||||
.attr("fill", "var(--text-color)")
|
||||
.attr("stroke", "none")
|
||||
.text((d) => d.title);
|
||||
.text((d) => (d.group === 1 ? "" : d.shortTitle));
|
||||
|
||||
node.append("title").text((d) => d.title);
|
||||
|
||||
|
|
@ -168,7 +180,9 @@
|
|||
.transition()
|
||||
.duration(200)
|
||||
.attr("r", 10); // New, larger radius
|
||||
d3.select(".tooltip").style("display", "").html(d.title);
|
||||
d3.select(this)
|
||||
.select("text")
|
||||
.text((d) => d.title);
|
||||
})
|
||||
.on("mouseout", function (event, d) {
|
||||
// Shrink the node circle back to original size
|
||||
|
|
@ -177,7 +191,9 @@
|
|||
.transition()
|
||||
.duration(500)
|
||||
.attr("r", 7); // Original radius
|
||||
d3.select(".tooltip").style("display", "none");
|
||||
d3.select(this)
|
||||
.select("text")
|
||||
.text((d) => (d.group === 1 ? "" : d.shortTitle));
|
||||
})
|
||||
.on("click", function (event, d) {
|
||||
window.postMessage(
|
||||
|
|
@ -186,14 +202,7 @@
|
|||
);
|
||||
});
|
||||
|
||||
document.body.replaceChildren(
|
||||
svg.node(),
|
||||
d3
|
||||
.create("div")
|
||||
.attr("class", "tooltip")
|
||||
.style("display", "none")
|
||||
.node(),
|
||||
);
|
||||
document.body.replaceChildren(svg.node());
|
||||
}
|
||||
|
||||
d3.select(window).on("resize", function () {
|
||||
|
|
|
|||
|
|
@ -2,60 +2,76 @@ bn-related-box {
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
|
||||
&[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&[readonly] {
|
||||
.add {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
bn-related-box[hidden] {
|
||||
display: none;
|
||||
}
|
||||
bn-related-box[readonly] .add {
|
||||
display: none;
|
||||
}
|
||||
bn-related-box .body {
|
||||
|
||||
bn-related-box .body,
|
||||
item-pane-custom-section .bn-link-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-inline-start: 12px;
|
||||
}
|
||||
bn-related-box .body .row {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
[zoteroUIDensity="comfortable"] bn-related-box .body .row {
|
||||
padding-block: 2px;
|
||||
}
|
||||
bn-related-box .body .row .box {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 4px;
|
||||
padding-inline-start: 4px;
|
||||
overflow: hidden;
|
||||
border-radius: 5px;
|
||||
flex: 1;
|
||||
}
|
||||
bn-related-box .body .row .box:not([disabled]):hover {
|
||||
background-color: var(--fill-quinary);
|
||||
}
|
||||
bn-related-box .body .row .box:not([disabled]):active {
|
||||
background-color: var(--fill-quarternary);
|
||||
}
|
||||
bn-related-box .body .row .box .icon {
|
||||
height: calc(1.3333333333 * var(--zotero-font-size));
|
||||
}
|
||||
bn-related-box .body .row .box .label {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 10;
|
||||
width: 0;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
bn-related-box .body .row .box .icon,
|
||||
bn-related-box .body .row .box .label {
|
||||
padding-block: 2px;
|
||||
}
|
||||
bn-related-box .body .row toolbarbutton {
|
||||
margin-inline-start: auto;
|
||||
visibility: hidden;
|
||||
}
|
||||
bn-related-box .body .row:is(:hover, :focus-within) toolbarbutton {
|
||||
visibility: visible;
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: flex-start;
|
||||
|
||||
[zoteroUIDensity="comfortable"] & {
|
||||
padding-block: 2px;
|
||||
}
|
||||
|
||||
.box {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 4px;
|
||||
padding-inline-start: 4px;
|
||||
overflow: hidden;
|
||||
border-radius: 5px;
|
||||
|
||||
&:not([disabled]):hover {
|
||||
background-color: var(--fill-quinary);
|
||||
}
|
||||
|
||||
&:not([disabled]):active {
|
||||
background-color: var(--fill-quarternary);
|
||||
}
|
||||
|
||||
.icon {
|
||||
height: calc(1.3333333333 * var(--zotero-font-size));
|
||||
}
|
||||
|
||||
.label {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 10;
|
||||
width: 0;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.icon,
|
||||
.label {
|
||||
padding-block: 2px;
|
||||
}
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
toolbarbutton {
|
||||
margin-inline-start: auto;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
&:is(:hover, :focus-within) toolbarbutton {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,3 +4,25 @@ note-relation-sidenav =
|
|||
.tooltiptext = Relation Graph
|
||||
note-relation-refresh =
|
||||
.tooltiptext = Refresh
|
||||
|
||||
note-inlink-header =
|
||||
.label =
|
||||
{ $count ->
|
||||
[one] { $count } Inbound Link
|
||||
*[other] { $count } Inbound Links
|
||||
}
|
||||
note-inlink-sidenav =
|
||||
.tooltiptext = Inbound Links
|
||||
note-inlink-refresh =
|
||||
.tooltiptext = Refresh
|
||||
|
||||
note-outlink-header =
|
||||
.label =
|
||||
{ $count ->
|
||||
[one] { $count } Outbound Link
|
||||
*[other] { $count } Outbound Links
|
||||
}
|
||||
note-outlink-sidenav =
|
||||
.tooltiptext = Outbound Links
|
||||
note-outlink-refresh =
|
||||
.tooltiptext = Refresh
|
||||
|
|
|
|||
|
|
@ -4,3 +4,25 @@ note-relation-sidenav =
|
|||
.tooltiptext = Relation Graph
|
||||
note-relation-refresh =
|
||||
.tooltiptext = Refresh
|
||||
|
||||
note-inlink-header =
|
||||
.label =
|
||||
{ $count ->
|
||||
[one] { $count } Inbound Link
|
||||
*[other] { $count } Inbound Links
|
||||
}
|
||||
note-inlink-sidenav =
|
||||
.tooltiptext = Inbound Links
|
||||
note-inlink-refresh =
|
||||
.tooltiptext = Refresh
|
||||
|
||||
note-outlink-header =
|
||||
.label =
|
||||
{ $count ->
|
||||
[one] { $count } Outbound Link
|
||||
*[other] { $count } Outbound Links
|
||||
}
|
||||
note-outlink-sidenav =
|
||||
.tooltiptext = Outbound Links
|
||||
note-outlink-refresh =
|
||||
.tooltiptext = Refresh
|
||||
|
|
|
|||
|
|
@ -4,3 +4,25 @@ note-relation-sidenav =
|
|||
.tooltiptext = Relation Graph
|
||||
note-relation-refresh =
|
||||
.tooltiptext = Refresh
|
||||
|
||||
note-inlink-header =
|
||||
.label =
|
||||
{ $count ->
|
||||
[one] { $count } Inbound Link
|
||||
*[other] { $count } Inbound Links
|
||||
}
|
||||
note-inlink-sidenav =
|
||||
.tooltiptext = Inbound Links
|
||||
note-inlink-refresh =
|
||||
.tooltiptext = Refresh
|
||||
|
||||
note-outlink-header =
|
||||
.label =
|
||||
{ $count ->
|
||||
[one] { $count } Outbound Link
|
||||
*[other] { $count } Outbound Links
|
||||
}
|
||||
note-outlink-sidenav =
|
||||
.tooltiptext = Outbound Links
|
||||
note-outlink-refresh =
|
||||
.tooltiptext = Refresh
|
||||
|
|
|
|||
|
|
@ -4,3 +4,25 @@ note-relation-sidenav =
|
|||
.tooltiptext = Relation Graph
|
||||
note-relation-refresh =
|
||||
.tooltiptext = Refresh
|
||||
|
||||
note-inlink-header =
|
||||
.label =
|
||||
{ $count ->
|
||||
[one] { $count } Inbound Link
|
||||
*[other] { $count } Inbound Links
|
||||
}
|
||||
note-inlink-sidenav =
|
||||
.tooltiptext = Inbound Links
|
||||
note-inlink-refresh =
|
||||
.tooltiptext = Refresh
|
||||
|
||||
note-outlink-header =
|
||||
.label =
|
||||
{ $count ->
|
||||
[one] { $count } Outbound Link
|
||||
*[other] { $count } Outbound Links
|
||||
}
|
||||
note-outlink-sidenav =
|
||||
.tooltiptext = Outbound Links
|
||||
note-outlink-refresh =
|
||||
.tooltiptext = Refresh
|
||||
|
|
|
|||
|
|
@ -4,3 +4,25 @@ note-relation-sidenav =
|
|||
.tooltiptext = 关系图
|
||||
note-relation-refresh =
|
||||
.tooltiptext = 刷新
|
||||
|
||||
note-inlink-header =
|
||||
.label =
|
||||
{ $count ->
|
||||
[one] { $count }条入链
|
||||
*[other] { $count }条入链
|
||||
}
|
||||
note-inlink-sidenav =
|
||||
.tooltiptext = 入链
|
||||
note-inlink-refresh =
|
||||
.tooltiptext = 刷新
|
||||
|
||||
note-outlink-header =
|
||||
.label =
|
||||
{ $count ->
|
||||
[one] { $count }条出链
|
||||
*[other] { $count }条出链
|
||||
}
|
||||
note-outlink-sidenav =
|
||||
.tooltiptext = 出链
|
||||
note-outlink-refresh =
|
||||
.tooltiptext = 刷新
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
// @ts-nocheck
|
||||
import { config } from "../../package.json";
|
||||
import { getPref } from "../utils/prefs";
|
||||
|
||||
const RelatedBox = customElements.get("related-box")! as typeof XULElementBase;
|
||||
|
||||
|
|
@ -68,14 +67,14 @@ export class NoteRelatedBox extends RelatedBox {
|
|||
|
||||
// Extra button to open note
|
||||
if (relatedItem.isNote()) {
|
||||
const note = document.createXULElement("toolbarbutton");
|
||||
note.addEventListener("command", (event) => {
|
||||
const openNote = document.createXULElement("toolbarbutton");
|
||||
openNote.addEventListener("command", (event) => {
|
||||
const position = event.shiftKey ? "window" : "tab";
|
||||
Zotero[config.addonRef].hooks.onOpenNote(id, position);
|
||||
});
|
||||
note.className = "zotero-clicky zotero-clicky-open-link";
|
||||
note.setAttribute("tabindex", "0");
|
||||
row.append(note);
|
||||
openNote.className = "zotero-clicky zotero-clicky-open-link";
|
||||
openNote.setAttribute("tabindex", "0");
|
||||
row.append(openNote);
|
||||
}
|
||||
|
||||
if (this.editable) {
|
||||
|
|
|
|||
|
|
@ -37,8 +37,10 @@ import { initSyncList } from "./modules/sync/api";
|
|||
import { patchViewItems } from "./modules/viewItems";
|
||||
import { getFocusedWindow } from "./utils/window";
|
||||
import { registerNoteRelation } from "./modules/workspace/relation";
|
||||
import { getPref } from "./utils/prefs";
|
||||
import { getPref, setPref } from "./utils/prefs";
|
||||
import { closeRelationWorker } from "./utils/relation";
|
||||
import { registerNoteInboundLink } from "./modules/workspace/inLink";
|
||||
import { registerNoteOutboundLink } from "./modules/workspace/outLink";
|
||||
|
||||
async function onStartup() {
|
||||
await Promise.all([
|
||||
|
|
@ -46,6 +48,7 @@ async function onStartup() {
|
|||
Zotero.unlockPromise,
|
||||
Zotero.uiReadyPromise,
|
||||
]);
|
||||
Zotero.Prefs.set("layout.css.nesting.enabled", true, true);
|
||||
initLocale();
|
||||
ztoolkit.ProgressWindow.setIconURI(
|
||||
"default",
|
||||
|
|
@ -62,6 +65,10 @@ async function onStartup() {
|
|||
|
||||
registerNoteRelation();
|
||||
|
||||
registerNoteOutboundLink();
|
||||
|
||||
registerNoteInboundLink();
|
||||
|
||||
initSyncList();
|
||||
|
||||
setSyncing();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,125 @@
|
|||
import { config } from "../../../package.json";
|
||||
|
||||
export function registerNoteInboundLink() {
|
||||
const key = Zotero.ItemPaneManager.registerSection({
|
||||
paneID: `bn-note-inbound-link`,
|
||||
pluginID: config.addonID,
|
||||
header: {
|
||||
icon: `chrome://${config.addonRef}/content/icons/in-link-16.svg`,
|
||||
l10nID: `${config.addonRef}-note-inlink-header`,
|
||||
},
|
||||
sidenav: {
|
||||
icon: `chrome://${config.addonRef}/content/icons/in-link-20.svg`,
|
||||
l10nID: `${config.addonRef}-note-inlink-sidenav`,
|
||||
},
|
||||
sectionButtons: [
|
||||
{
|
||||
type: "refreshGraph",
|
||||
icon: "chrome://zotero/skin/16/universal/sync.svg",
|
||||
l10nID: `${config.addonRef}-note-inlink-refresh`,
|
||||
onClick: ({ body, item, setL10nArgs }) => {
|
||||
renderSection(body, item, makeSetCount(setL10nArgs));
|
||||
},
|
||||
},
|
||||
],
|
||||
onInit({ body, refresh }) {
|
||||
const notifierKey = Zotero.Notifier.registerObserver(
|
||||
{
|
||||
notify: (event, type, ids, extraData) => {
|
||||
const item = Zotero.Items.get(body.dataset.itemID || "");
|
||||
if (
|
||||
item &&
|
||||
// @ts-ignore
|
||||
event === "updateBNRelation" &&
|
||||
type === "item" &&
|
||||
(ids as number[]).includes(item.id)
|
||||
) {
|
||||
ztoolkit.log("relation notify refresh", item.id);
|
||||
refresh();
|
||||
}
|
||||
},
|
||||
},
|
||||
["item"],
|
||||
);
|
||||
body.classList.add("bn-link-body");
|
||||
body.dataset.notifierKey = notifierKey;
|
||||
},
|
||||
onDestroy({ body }) {
|
||||
const notifierKey = body.dataset.notifierKey;
|
||||
if (notifierKey) {
|
||||
Zotero.Notifier.unregisterObserver(notifierKey);
|
||||
}
|
||||
},
|
||||
onItemChange: ({ body, item, setEnabled }) => {
|
||||
if (body.closest("bn-workspace") as HTMLElement | undefined) {
|
||||
setEnabled(true);
|
||||
body.dataset.itemID = String(item.id);
|
||||
return;
|
||||
}
|
||||
setEnabled(false);
|
||||
},
|
||||
onRender: () => {},
|
||||
onAsyncRender: async ({ body, item, setL10nArgs }) => {
|
||||
await renderSection(body, item, makeSetCount(setL10nArgs));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function renderSection(
|
||||
body: HTMLElement,
|
||||
item: Zotero.Item,
|
||||
setCount: (count: number) => void,
|
||||
) {
|
||||
body.replaceChildren();
|
||||
const doc = body.ownerDocument;
|
||||
const inLinks = await addon.api.relation.getNoteLinkInboundRelation(item.id);
|
||||
for (const linkData of inLinks) {
|
||||
const fromItem = (await Zotero.Items.getByLibraryAndKeyAsync(
|
||||
linkData.fromLibID,
|
||||
linkData.fromKey,
|
||||
)) as Zotero.Item;
|
||||
|
||||
const row = doc.createElement("div");
|
||||
row.className = "row";
|
||||
|
||||
const icon = ztoolkit
|
||||
.getGlobal("require")("components/icons")
|
||||
.getCSSItemTypeIcon("note");
|
||||
|
||||
const label = doc.createElement("span");
|
||||
label.className = "label";
|
||||
label.append(fromItem.getNoteTitle());
|
||||
|
||||
const box = doc.createElement("div");
|
||||
box.addEventListener("click", () => handleShowItem(fromItem.id));
|
||||
box.className = "box keyboard-clickable";
|
||||
box.setAttribute("tabindex", "0");
|
||||
box.append(icon, label);
|
||||
|
||||
row.append(box);
|
||||
|
||||
const note = (doc as any).createXULElement("toolbarbutton");
|
||||
note.addEventListener("command", (event: MouseEvent) => {
|
||||
const position = event.shiftKey ? "window" : "tab";
|
||||
addon.hooks.onOpenNote(fromItem.id, position);
|
||||
});
|
||||
note.className = "zotero-clicky zotero-clicky-open-link";
|
||||
note.setAttribute("tabindex", "0");
|
||||
row.append(note);
|
||||
|
||||
body.append(row);
|
||||
}
|
||||
|
||||
const count = inLinks.length;
|
||||
setCount(count);
|
||||
}
|
||||
|
||||
function handleShowItem(id: number) {
|
||||
ZoteroPane.selectItem(id);
|
||||
}
|
||||
|
||||
function makeSetCount(setL10nArgs: (str: string) => void) {
|
||||
return (count: number) => {
|
||||
setL10nArgs(`{"count": "${count}"}`);
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
import { config } from "../../../package.json";
|
||||
|
||||
export function registerNoteOutboundLink() {
|
||||
const key = Zotero.ItemPaneManager.registerSection({
|
||||
paneID: `bn-note-outbound-link`,
|
||||
pluginID: config.addonID,
|
||||
header: {
|
||||
icon: `chrome://${config.addonRef}/content/icons/out-link-16.svg`,
|
||||
l10nID: `${config.addonRef}-note-outlink-header`,
|
||||
},
|
||||
sidenav: {
|
||||
icon: `chrome://${config.addonRef}/content/icons/out-link-20.svg`,
|
||||
l10nID: `${config.addonRef}-note-outlink-sidenav`,
|
||||
},
|
||||
sectionButtons: [
|
||||
{
|
||||
type: "refreshGraph",
|
||||
icon: "chrome://zotero/skin/16/universal/sync.svg",
|
||||
l10nID: `${config.addonRef}-note-outlink-refresh`,
|
||||
onClick: ({ body, item, setL10nArgs }) => {
|
||||
renderSection(body, item, makeSetCount(setL10nArgs));
|
||||
},
|
||||
},
|
||||
],
|
||||
onInit({ body, refresh, getData }) {
|
||||
const notifierKey = Zotero.Notifier.registerObserver(
|
||||
{
|
||||
notify: (event, type, ids, extraData) => {
|
||||
const item = Zotero.Items.get(body.dataset.itemID || "");
|
||||
if (
|
||||
item &&
|
||||
// @ts-ignore
|
||||
event === "updateBNRelation" &&
|
||||
type === "item" &&
|
||||
(ids as number[]).includes(item.id)
|
||||
) {
|
||||
ztoolkit.log("relation notify refresh", item.id);
|
||||
refresh();
|
||||
}
|
||||
},
|
||||
},
|
||||
["item"],
|
||||
);
|
||||
body.classList.add("bn-link-body");
|
||||
body.dataset.notifierKey = notifierKey;
|
||||
},
|
||||
onDestroy({ body }) {
|
||||
const notifierKey = body.dataset.notifierKey;
|
||||
if (notifierKey) {
|
||||
Zotero.Notifier.unregisterObserver(notifierKey);
|
||||
}
|
||||
},
|
||||
onItemChange: ({ body, item, setEnabled }) => {
|
||||
if (body.closest("bn-workspace") as HTMLElement | undefined) {
|
||||
setEnabled(true);
|
||||
body.dataset.itemID = String(item.id);
|
||||
return;
|
||||
}
|
||||
setEnabled(false);
|
||||
},
|
||||
onRender: () => {},
|
||||
onAsyncRender: async ({ body, item, setL10nArgs }) => {
|
||||
await renderSection(body, item, makeSetCount(setL10nArgs));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function renderSection(
|
||||
body: HTMLElement,
|
||||
item: Zotero.Item,
|
||||
setCount: (count: number) => void,
|
||||
) {
|
||||
body.replaceChildren();
|
||||
const doc = body.ownerDocument;
|
||||
const inLinks = await addon.api.relation.getNoteLinkOutboundRelation(item.id);
|
||||
for (const linkData of inLinks) {
|
||||
const toItem = (await Zotero.Items.getByLibraryAndKeyAsync(
|
||||
linkData.toLibID,
|
||||
linkData.toKey,
|
||||
)) as Zotero.Item;
|
||||
|
||||
const row = doc.createElement("div");
|
||||
row.className = "row";
|
||||
|
||||
const icon = ztoolkit
|
||||
.getGlobal("require")("components/icons")
|
||||
.getCSSItemTypeIcon("note");
|
||||
|
||||
const label = doc.createElement("span");
|
||||
label.className = "label";
|
||||
label.append(toItem.getNoteTitle());
|
||||
|
||||
const box = doc.createElement("div");
|
||||
box.addEventListener("click", () => handleShowItem(toItem.id));
|
||||
box.className = "box keyboard-clickable";
|
||||
box.setAttribute("tabindex", "0");
|
||||
box.append(icon, label);
|
||||
|
||||
row.append(box);
|
||||
|
||||
const note = (doc as any).createXULElement("toolbarbutton");
|
||||
note.addEventListener("command", (event: MouseEvent) => {
|
||||
const position = event.shiftKey ? "window" : "tab";
|
||||
addon.hooks.onOpenNote(toItem.id, position);
|
||||
});
|
||||
note.className = "zotero-clicky zotero-clicky-open-link";
|
||||
note.setAttribute("tabindex", "0");
|
||||
row.append(note);
|
||||
|
||||
body.append(row);
|
||||
}
|
||||
|
||||
const count = inLinks.length;
|
||||
setCount(count);
|
||||
}
|
||||
|
||||
function handleShowItem(id: number) {
|
||||
ZoteroPane.selectItem(id);
|
||||
}
|
||||
|
||||
function makeSetCount(setL10nArgs: (str: string) => void) {
|
||||
return (count: number) => {
|
||||
setL10nArgs(`{"count": "${count}"}`);
|
||||
};
|
||||
}
|
||||
|
|
@ -106,9 +106,10 @@ async function getRelationData(note: Zotero.Item) {
|
|||
const inLink = await addon.api.relation.getNoteLinkInboundRelation(note.id);
|
||||
const outLink = await addon.api.relation.getNoteLinkOutboundRelation(note.id);
|
||||
|
||||
const links = [];
|
||||
const noteSet: Set<number> = new Set();
|
||||
|
||||
const linkModels: Record<number, NoteLinkModal> = {};
|
||||
|
||||
for (const linkData of inLink) {
|
||||
const noteItem = await Zotero.Items.getByLibraryAndKeyAsync(
|
||||
linkData.fromLibID,
|
||||
|
|
@ -116,11 +117,18 @@ async function getRelationData(note: Zotero.Item) {
|
|||
);
|
||||
if (!noteItem) continue;
|
||||
noteSet.add(noteItem.id);
|
||||
links.push({
|
||||
source: noteItem.id,
|
||||
target: note.id,
|
||||
value: 1,
|
||||
});
|
||||
let noteLinks = linkModels[noteItem.id];
|
||||
if (!noteLinks) {
|
||||
noteLinks = {
|
||||
source: noteItem.id,
|
||||
target: note.id,
|
||||
value: 1,
|
||||
type: "in",
|
||||
};
|
||||
linkModels[noteItem.id] = noteLinks;
|
||||
} else {
|
||||
noteLinks.value++;
|
||||
}
|
||||
}
|
||||
|
||||
for (const linkData of outLink) {
|
||||
|
|
@ -130,28 +138,52 @@ async function getRelationData(note: Zotero.Item) {
|
|||
);
|
||||
if (!noteItem) continue;
|
||||
noteSet.add(noteItem.id);
|
||||
links.push({
|
||||
source: note.id,
|
||||
target: noteItem.id,
|
||||
value: 1,
|
||||
});
|
||||
let noteLinks = linkModels[noteItem.id];
|
||||
if (!noteLinks) {
|
||||
noteLinks = {
|
||||
source: note.id,
|
||||
target: noteItem.id,
|
||||
value: 1,
|
||||
type: "out",
|
||||
};
|
||||
linkModels[noteItem.id] = noteLinks;
|
||||
} else {
|
||||
noteLinks.value++;
|
||||
if (noteLinks.type === "in") {
|
||||
noteLinks.type = "both";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
noteSet.delete(note.id);
|
||||
const nodes = Array.from(noteSet).map((id) => {
|
||||
const item = Zotero.Items.get(id);
|
||||
const title = item.getNoteTitle();
|
||||
return {
|
||||
id: item.id,
|
||||
title: slice(item.getNoteTitle(), 15),
|
||||
title,
|
||||
shortTitle: slice(title, 15),
|
||||
group: 2,
|
||||
};
|
||||
});
|
||||
|
||||
const title = note.getNoteTitle();
|
||||
nodes.push({
|
||||
id: note.id,
|
||||
title: slice(note.getNoteTitle(), 15),
|
||||
title,
|
||||
shortTitle: slice(title, 15),
|
||||
group: 1,
|
||||
});
|
||||
|
||||
return { nodes, links };
|
||||
return {
|
||||
nodes,
|
||||
links: Object.values(linkModels),
|
||||
};
|
||||
}
|
||||
|
||||
interface NoteLinkModal {
|
||||
source: number;
|
||||
target: number;
|
||||
value: number;
|
||||
type: "in" | "out" | "both";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ async function updateNoteLinkRelation(noteID: number) {
|
|||
for (const link of linkMatches) {
|
||||
const { noteItem, libraryID, noteKey, lineIndex, sectionName } =
|
||||
getNoteLinkParams(link);
|
||||
if (noteItem && noteItem.isNote()) {
|
||||
if (noteItem && noteItem.isNote() && noteItem.id !== note.id) {
|
||||
affectedNoteIDs.add(noteItem.id);
|
||||
linkToData.push({
|
||||
fromLibID,
|
||||
|
|
|
|||
Loading…
Reference in New Issue