mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-11-04 15:15:42 +00:00 
			
		
		
		
	Link changes and in-table clipboards (#4697)
* Add clipboard to tables and external link changes * Clipboard icon added to tables for screens >1200px wide. Enables copying of SKU/MPN/IPN from table cells where these otherwise are hyperlinks * External links now open in new tabs with noreferrer * Move external links into separate template * All statically rendered external links have been moved out to a new template.
This commit is contained in:
		@@ -1094,4 +1094,10 @@ a {
 | 
				
			|||||||
.sso-provider-link a {
 | 
					.sso-provider-link a {
 | 
				
			||||||
    width:  100%;
 | 
					    width:  100%;
 | 
				
			||||||
    text-align: left;
 | 
					    text-align: left;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.flex-cell {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    justify-content: space-between;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -106,7 +106,7 @@
 | 
				
			|||||||
                <tr>
 | 
					                <tr>
 | 
				
			||||||
                    <td><span class='fas fa-link'></span></td>
 | 
					                    <td><span class='fas fa-link'></span></td>
 | 
				
			||||||
                    <td>{% trans "External Link" %}</td>
 | 
					                    <td>{% trans "External Link" %}</td>
 | 
				
			||||||
                    <td><a href="{{ build.link }}">{{ build.link }}</a>{% include "clip.html"%}</td>
 | 
					                    <td>{% include 'clip_link.html' with link=build.link %}</td>
 | 
				
			||||||
                </tr>
 | 
					                </tr>
 | 
				
			||||||
                {% endif %}
 | 
					                {% endif %}
 | 
				
			||||||
                {% if build.issued_by %}
 | 
					                {% if build.issued_by %}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -105,7 +105,7 @@ src="{% static 'img/blank_image.png' %}"
 | 
				
			|||||||
    <tr>
 | 
					    <tr>
 | 
				
			||||||
        <td><span class='fas fa-link'></span></td>
 | 
					        <td><span class='fas fa-link'></span></td>
 | 
				
			||||||
        <td>{% trans "External Link" %}</td>
 | 
					        <td>{% trans "External Link" %}</td>
 | 
				
			||||||
        <td><a href="{{ part.link }}">{{ part.link }}</a>{% include "clip.html"%}</td>
 | 
					        <td>{% include 'clip_link.html' with link=part.link %}</td>
 | 
				
			||||||
    </tr>
 | 
					    </tr>
 | 
				
			||||||
    {% endif %}
 | 
					    {% endif %}
 | 
				
			||||||
</table>
 | 
					</table>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -180,7 +180,7 @@ src="{% static 'img/blank_image.png' %}"
 | 
				
			|||||||
        <tr>
 | 
					        <tr>
 | 
				
			||||||
            <td><span class='fas fa-link'></span></td>
 | 
					            <td><span class='fas fa-link'></span></td>
 | 
				
			||||||
            <td>{% trans "External Link" %}</td>
 | 
					            <td>{% trans "External Link" %}</td>
 | 
				
			||||||
            <td><a href="{{ part.link }}">{{ part.link }}</a>{% include "clip.html"%}</td>
 | 
					            <td>{% include 'clip_link.html' with link=part.link %}</td>
 | 
				
			||||||
        </tr>
 | 
					        </tr>
 | 
				
			||||||
        {% endif %}
 | 
					        {% endif %}
 | 
				
			||||||
</table>
 | 
					</table>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -168,8 +168,8 @@ src="{% static 'img/blank_image.png' %}"
 | 
				
			|||||||
    {% if order.link %}
 | 
					    {% if order.link %}
 | 
				
			||||||
    <tr>
 | 
					    <tr>
 | 
				
			||||||
        <td><span class='fas fa-link'></span></td>
 | 
					        <td><span class='fas fa-link'></span></td>
 | 
				
			||||||
        <td>External Link</td>
 | 
					        <td>{% trans "External Link" %}</td>
 | 
				
			||||||
        <td><a href="{{ order.link }}">{{ order.link }}</a>{% include "clip.html"%}</td>
 | 
					        <td>{% include 'clip_link.html' with link=order.link %}</td>
 | 
				
			||||||
    </tr>
 | 
					    </tr>
 | 
				
			||||||
    {% endif %}
 | 
					    {% endif %}
 | 
				
			||||||
    <tr>
 | 
					    <tr>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -143,8 +143,8 @@ src="{% static 'img/blank_image.png' %}"
 | 
				
			|||||||
    {% if order.link %}
 | 
					    {% if order.link %}
 | 
				
			||||||
    <tr>
 | 
					    <tr>
 | 
				
			||||||
        <td><span class='fas fa-link'></span></td>
 | 
					        <td><span class='fas fa-link'></span></td>
 | 
				
			||||||
        <td>External Link</td>
 | 
					        <td>{% trans "External Link" %}</td>
 | 
				
			||||||
        <td><a href="{{ order.link }}">{{ order.link }}</a>{% include "clip.html"%}</td>
 | 
					        <td>{% include 'clip_link.html' with link=order.link %}</td>
 | 
				
			||||||
    </tr>
 | 
					    </tr>
 | 
				
			||||||
    {% endif %}
 | 
					    {% endif %}
 | 
				
			||||||
    <tr>
 | 
					    <tr>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -178,8 +178,8 @@ src="{% static 'img/blank_image.png' %}"
 | 
				
			|||||||
    {% if order.link %}
 | 
					    {% if order.link %}
 | 
				
			||||||
    <tr>
 | 
					    <tr>
 | 
				
			||||||
        <td><span class='fas fa-link'></span></td>
 | 
					        <td><span class='fas fa-link'></span></td>
 | 
				
			||||||
        <td>External Link</td>
 | 
					        <td>{% trans "External Link" %}</td>
 | 
				
			||||||
        <td><a href="{{ order.link }}">{{ order.link }}</a>{% include "clip.html"%}</td>
 | 
					        <td>{% include 'clip_link.html' with link=order.link %}</td>
 | 
				
			||||||
    </tr>
 | 
					    </tr>
 | 
				
			||||||
    {% endif %}
 | 
					    {% endif %}
 | 
				
			||||||
    <tr>
 | 
					    <tr>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -383,7 +383,7 @@
 | 
				
			|||||||
                <tr>
 | 
					                <tr>
 | 
				
			||||||
                    <td><span class='fas fa-link'></span></td>
 | 
					                    <td><span class='fas fa-link'></span></td>
 | 
				
			||||||
                    <td>{% trans "External Link" %}</td>
 | 
					                    <td>{% trans "External Link" %}</td>
 | 
				
			||||||
                    <td><a href="{{ part.link }}">{{ part.link }}</a>{% include "clip.html"%}</td>
 | 
					                    <td>{% include 'clip_link.html' with link=part.link %}</td>
 | 
				
			||||||
                </tr>
 | 
					                </tr>
 | 
				
			||||||
                {% endif %}
 | 
					                {% endif %}
 | 
				
			||||||
                {% if part.responsible %}
 | 
					                {% if part.responsible %}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -199,7 +199,7 @@
 | 
				
			|||||||
    <tr>
 | 
					    <tr>
 | 
				
			||||||
        <td><span class='fas fa-link'></span>
 | 
					        <td><span class='fas fa-link'></span>
 | 
				
			||||||
        <td>{% trans "External Link" %}</td>
 | 
					        <td>{% trans "External Link" %}</td>
 | 
				
			||||||
        <td><a href="{{ item.link }}">{{ item.link }}</a></td>
 | 
					        <td>{% include 'clip_link.html' with link=item.link %}</td>
 | 
				
			||||||
    </tr>
 | 
					    </tr>
 | 
				
			||||||
    {% endif %}
 | 
					    {% endif %}
 | 
				
			||||||
    {% if item.supplier_part.manufacturer_part %}
 | 
					    {% if item.supplier_part.manufacturer_part %}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										5
									
								
								InvenTree/templates/clip_link.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								InvenTree/templates/clip_link.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					{% load i18n %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% if link %}
 | 
				
			||||||
 | 
					<a href="{{ link }}">{{ link }}</a>{% include 'clip.html' %}
 | 
				
			||||||
 | 
					{% endif %}
 | 
				
			||||||
@@ -954,7 +954,7 @@ function loadManufacturerPartTable(table, url, options) {
 | 
				
			|||||||
                field: 'MPN',
 | 
					                field: 'MPN',
 | 
				
			||||||
                title: '{% trans "MPN" %}',
 | 
					                title: '{% trans "MPN" %}',
 | 
				
			||||||
                formatter: function(value, row) {
 | 
					                formatter: function(value, row) {
 | 
				
			||||||
                    return renderLink(value, `/manufacturer-part/${row.pk}/`);
 | 
					                    return renderClipboard(renderLink(value, `/manufacturer-part/${row.pk}/`));
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@@ -962,7 +962,7 @@ function loadManufacturerPartTable(table, url, options) {
 | 
				
			|||||||
                title: '{% trans "Link" %}',
 | 
					                title: '{% trans "Link" %}',
 | 
				
			||||||
                formatter: function(value) {
 | 
					                formatter: function(value) {
 | 
				
			||||||
                    if (value) {
 | 
					                    if (value) {
 | 
				
			||||||
                        return renderLink(value, value);
 | 
					                        return renderLink(value, value, {external: true});
 | 
				
			||||||
                    } else {
 | 
					                    } else {
 | 
				
			||||||
                        return '';
 | 
					                        return '';
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
@@ -1194,7 +1194,7 @@ function loadSupplierPartTable(table, url, options) {
 | 
				
			|||||||
                field: 'SKU',
 | 
					                field: 'SKU',
 | 
				
			||||||
                title: '{% trans "Supplier Part" %}',
 | 
					                title: '{% trans "Supplier Part" %}',
 | 
				
			||||||
                formatter: function(value, row) {
 | 
					                formatter: function(value, row) {
 | 
				
			||||||
                    return renderLink(value, `/supplier-part/${row.pk}/`);
 | 
					                    return renderClipboard(renderLink(value, `/supplier-part/${row.pk}/`));
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@@ -1225,7 +1225,7 @@ function loadSupplierPartTable(table, url, options) {
 | 
				
			|||||||
                title: '{% trans "MPN" %}',
 | 
					                title: '{% trans "MPN" %}',
 | 
				
			||||||
                formatter: function(value, row) {
 | 
					                formatter: function(value, row) {
 | 
				
			||||||
                    if (value && row.manufacturer_part) {
 | 
					                    if (value && row.manufacturer_part) {
 | 
				
			||||||
                        return renderLink(value, `/manufacturer-part/${row.manufacturer_part}/`);
 | 
					                        return renderClipboard(renderLink(value, `/manufacturer-part/${row.manufacturer_part}/`));
 | 
				
			||||||
                    } else {
 | 
					                    } else {
 | 
				
			||||||
                        return '-';
 | 
					                        return '-';
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
@@ -1261,7 +1261,7 @@ function loadSupplierPartTable(table, url, options) {
 | 
				
			|||||||
                title: '{% trans "Link" %}',
 | 
					                title: '{% trans "Link" %}',
 | 
				
			||||||
                formatter: function(value) {
 | 
					                formatter: function(value) {
 | 
				
			||||||
                    if (value) {
 | 
					                    if (value) {
 | 
				
			||||||
                        return renderLink(value, value);
 | 
					                        return renderLink(value, value, {external: true});
 | 
				
			||||||
                    } else {
 | 
					                    } else {
 | 
				
			||||||
                        return '';
 | 
					                        return '';
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,6 +23,7 @@
 | 
				
			|||||||
    yesNoLabel,
 | 
					    yesNoLabel,
 | 
				
			||||||
    withTitle,
 | 
					    withTitle,
 | 
				
			||||||
    wrapButtons,
 | 
					    wrapButtons,
 | 
				
			||||||
 | 
					    renderClipboard,
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* exported
 | 
					/* exported
 | 
				
			||||||
@@ -376,7 +377,13 @@ function renderLink(text, url, options={}) {
 | 
				
			|||||||
        extras += ` download`;
 | 
					        extras += ` download`;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return `<a href="${url}" ${extras}>${text}</a>`;
 | 
					    let suffix = '';
 | 
				
			||||||
 | 
					    if (options.external) {
 | 
				
			||||||
 | 
					        extras += ` target="_blank" rel="noopener noreferrer"`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        suffix = ` <i class="fas fa-external-link-alt fa-xs d-none d-xl-inline"></i>`;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return `<a href="${url}" ${extras}>${text}${suffix}</a>`;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -516,3 +523,25 @@ function sanitizeInputString(s, options={}) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    return s;
 | 
					    return s;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Inserts HTML data equal to clip.html into input string
 | 
				
			||||||
 | 
					 * Enables insertion of clipboard icons in dynamic tables
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * clipString relies on ClipboardJS in the same manner as clip.html
 | 
				
			||||||
 | 
					 * Thus, this functionality will break if the call to
 | 
				
			||||||
 | 
					 * attachClipboard('.clip-btn') in script/inventree/inventree.js is altered
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					function renderClipboard(s, prepend=false) {
 | 
				
			||||||
 | 
					    if (!s || typeof s != 'string') {
 | 
				
			||||||
 | 
					        return s;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let clipString = `<span class="d-none d-xl-inline"><button class="btn clip-btn" type="button" data-bs-toggle='tooltip' title='{% trans "copy to clipboard" %}'><em class="fas fa-copy"></em></button></span>`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (prepend === true) {
 | 
				
			||||||
 | 
					        return `<div class="flex-cell">${clipString+s}</div>`;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        return `<div class="flex-cell">${s+clipString}</div>`;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1447,7 +1447,7 @@ function loadPartPurchaseOrderTable(table, part_id, options={}) {
 | 
				
			|||||||
                    if (row.supplier_part_detail) {
 | 
					                    if (row.supplier_part_detail) {
 | 
				
			||||||
                        var supp = row.supplier_part_detail;
 | 
					                        var supp = row.supplier_part_detail;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        return renderLink(supp.SKU, `/supplier-part/${supp.pk}/`);
 | 
					                        return renderClipboard(renderLink(supp.SKU, `/supplier-part/${supp.pk}/`));
 | 
				
			||||||
                    } else {
 | 
					                    } else {
 | 
				
			||||||
                        return '-';
 | 
					                        return '-';
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
@@ -1460,7 +1460,7 @@ function loadPartPurchaseOrderTable(table, part_id, options={}) {
 | 
				
			|||||||
                formatter: function(value, row) {
 | 
					                formatter: function(value, row) {
 | 
				
			||||||
                    if (row.supplier_part_detail && row.supplier_part_detail.manufacturer_part_detail) {
 | 
					                    if (row.supplier_part_detail && row.supplier_part_detail.manufacturer_part_detail) {
 | 
				
			||||||
                        var manu = row.supplier_part_detail.manufacturer_part_detail;
 | 
					                        var manu = row.supplier_part_detail.manufacturer_part_detail;
 | 
				
			||||||
                        return renderLink(manu.MPN, `/manufacturer-part/${manu.pk}/`);
 | 
					                        return renderClipboard(renderLink(manu.MPN, `/manufacturer-part/${manu.pk}/`));
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1955,7 +1955,7 @@ function loadPurchaseOrderLineItemTable(table, options={}) {
 | 
				
			|||||||
                title: '{% trans "SKU" %}',
 | 
					                title: '{% trans "SKU" %}',
 | 
				
			||||||
                formatter: function(value, row, index, field) {
 | 
					                formatter: function(value, row, index, field) {
 | 
				
			||||||
                    if (value) {
 | 
					                    if (value) {
 | 
				
			||||||
                        return renderLink(value, `/supplier-part/${row.part}/`);
 | 
					                        return renderClipboard(renderLink(value, `/supplier-part/${row.part}/`));
 | 
				
			||||||
                    } else {
 | 
					                    } else {
 | 
				
			||||||
                        return '-';
 | 
					                        return '-';
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
@@ -1967,7 +1967,7 @@ function loadPurchaseOrderLineItemTable(table, options={}) {
 | 
				
			|||||||
                title: '{% trans "Link" %}',
 | 
					                title: '{% trans "Link" %}',
 | 
				
			||||||
                formatter: function(value, row, index, field) {
 | 
					                formatter: function(value, row, index, field) {
 | 
				
			||||||
                    if (value) {
 | 
					                    if (value) {
 | 
				
			||||||
                        return renderLink(value, value);
 | 
					                        return renderLink(value, value, {external: true});
 | 
				
			||||||
                    } else {
 | 
					                    } else {
 | 
				
			||||||
                        return '';
 | 
					                        return '';
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
@@ -1980,7 +1980,7 @@ function loadPurchaseOrderLineItemTable(table, options={}) {
 | 
				
			|||||||
                title: '{% trans "MPN" %}',
 | 
					                title: '{% trans "MPN" %}',
 | 
				
			||||||
                formatter: function(value, row, index, field) {
 | 
					                formatter: function(value, row, index, field) {
 | 
				
			||||||
                    if (row.supplier_part_detail && row.supplier_part_detail.manufacturer_part) {
 | 
					                    if (row.supplier_part_detail && row.supplier_part_detail.manufacturer_part) {
 | 
				
			||||||
                        return renderLink(value, `/manufacturer-part/${row.supplier_part_detail.manufacturer_part}/`);
 | 
					                        return renderClipboard(renderLink(value, `/manufacturer-part/${row.supplier_part_detail.manufacturer_part}/`));
 | 
				
			||||||
                    } else {
 | 
					                    } else {
 | 
				
			||||||
                        return '-';
 | 
					                        return '-';
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1782,7 +1782,7 @@ function loadStockTable(table, options) {
 | 
				
			|||||||
        formatter: function(value, row) {
 | 
					        formatter: function(value, row) {
 | 
				
			||||||
            var ipn = row.part_detail.IPN;
 | 
					            var ipn = row.part_detail.IPN;
 | 
				
			||||||
            if (ipn) {
 | 
					            if (ipn) {
 | 
				
			||||||
                return withTitle(shortenString(ipn), ipn);
 | 
					                return renderClipboard(withTitle(shortenString(ipn), ipn));
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                return '-';
 | 
					                return '-';
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@@ -2014,7 +2014,7 @@ function loadStockTable(table, options) {
 | 
				
			|||||||
                text = `<i>{% trans "Supplier part not specified" %}</i>`;
 | 
					                text = `<i>{% trans "Supplier part not specified" %}</i>`;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return renderLink(text, link);
 | 
					            return renderClipboard(renderLink(text, link));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user