Merge branch 'inventree:master' into matmair/issue2279

This commit is contained in:
Matthias Mair 2021-11-28 17:26:15 +01:00 committed by GitHub
commit e34de8204a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 319 additions and 292 deletions

View File

@ -21,7 +21,8 @@ from django.dispatch import receiver
from mptt.models import MPTTModel, TreeForeignKey from mptt.models import MPTTModel, TreeForeignKey
from mptt.exceptions import InvalidMove from mptt.exceptions import InvalidMove
from .validators import validate_tree_name from InvenTree.fields import InvenTreeURLField
from InvenTree.validators import validate_tree_name
logger = logging.getLogger('inventree') logger = logging.getLogger('inventree')
@ -89,12 +90,15 @@ class ReferenceIndexingMixin(models.Model):
class InvenTreeAttachment(models.Model): class InvenTreeAttachment(models.Model):
""" Provides an abstracted class for managing file attachments. """ Provides an abstracted class for managing file attachments.
An attachment can be either an uploaded file, or an external URL
Attributes: Attributes:
attachment: File attachment: File
comment: String descriptor for the attachment comment: String descriptor for the attachment
user: User associated with file upload user: User associated with file upload
upload_date: Date the file was uploaded upload_date: Date the file was uploaded
""" """
def getSubdir(self): def getSubdir(self):
""" """
Return the subdirectory under which attachments should be stored. Return the subdirectory under which attachments should be stored.
@ -103,11 +107,32 @@ class InvenTreeAttachment(models.Model):
return "attachments" return "attachments"
def save(self, *args, **kwargs):
# Either 'attachment' or 'link' must be specified!
if not self.attachment and not self.link:
raise ValidationError({
'attachment': _('Missing file'),
'link': _('Missing external link'),
})
super().save(*args, **kwargs)
def __str__(self): def __str__(self):
return os.path.basename(self.attachment.name) if self.attachment is not None:
return os.path.basename(self.attachment.name)
else:
return str(self.link)
attachment = models.FileField(upload_to=rename_attachment, verbose_name=_('Attachment'), attachment = models.FileField(upload_to=rename_attachment, verbose_name=_('Attachment'),
help_text=_('Select file to attach')) help_text=_('Select file to attach'),
blank=True, null=True
)
link = InvenTreeURLField(
blank=True, null=True,
verbose_name=_('Link'),
help_text=_('Link to external URL')
)
comment = models.CharField(blank=True, max_length=100, verbose_name=_('Comment'), help_text=_('File comment')) comment = models.CharField(blank=True, max_length=100, verbose_name=_('Comment'), help_text=_('File comment'))
@ -123,7 +148,10 @@ class InvenTreeAttachment(models.Model):
@property @property
def basename(self): def basename(self):
return os.path.basename(self.attachment.name) if self.attachment:
return os.path.basename(self.attachment.name)
else:
return None
@basename.setter @basename.setter
def basename(self, fn): def basename(self, fn):

View File

@ -239,22 +239,6 @@ class InvenTreeModelSerializer(serializers.ModelSerializer):
return data return data
class InvenTreeAttachmentSerializer(InvenTreeModelSerializer):
"""
Special case of an InvenTreeModelSerializer, which handles an "attachment" model.
The only real addition here is that we support "renaming" of the attachment file.
"""
# The 'filename' field must be present in the serializer
filename = serializers.CharField(
label=_('Filename'),
required=False,
source='basename',
allow_blank=False,
)
class InvenTreeAttachmentSerializerField(serializers.FileField): class InvenTreeAttachmentSerializerField(serializers.FileField):
""" """
Override the DRF native FileField serializer, Override the DRF native FileField serializer,
@ -284,6 +268,27 @@ class InvenTreeAttachmentSerializerField(serializers.FileField):
return os.path.join(str(settings.MEDIA_URL), str(value)) return os.path.join(str(settings.MEDIA_URL), str(value))
class InvenTreeAttachmentSerializer(InvenTreeModelSerializer):
"""
Special case of an InvenTreeModelSerializer, which handles an "attachment" model.
The only real addition here is that we support "renaming" of the attachment file.
"""
attachment = InvenTreeAttachmentSerializerField(
required=False,
allow_null=False,
)
# The 'filename' field must be present in the serializer
filename = serializers.CharField(
label=_('Filename'),
required=False,
source='basename',
allow_blank=False,
)
class InvenTreeImageSerializerField(serializers.ImageField): class InvenTreeImageSerializerField(serializers.ImageField):
""" """
Custom image serializer. Custom image serializer.

View File

@ -0,0 +1,25 @@
# Generated by Django 3.2.5 on 2021-11-28 01:51
import InvenTree.fields
import InvenTree.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('build', '0032_auto_20211014_0632'),
]
operations = [
migrations.AddField(
model_name='buildorderattachment',
name='link',
field=InvenTree.fields.InvenTreeURLField(blank=True, help_text='Link to external URL', null=True, verbose_name='Link'),
),
migrations.AlterField(
model_name='buildorderattachment',
name='attachment',
field=models.FileField(blank=True, help_text='Select file to attach', null=True, upload_to=InvenTree.models.rename_attachment, verbose_name='Attachment'),
),
]

View File

@ -16,7 +16,7 @@ from rest_framework import serializers
from rest_framework.serializers import ValidationError from rest_framework.serializers import ValidationError
from InvenTree.serializers import InvenTreeModelSerializer, InvenTreeAttachmentSerializer from InvenTree.serializers import InvenTreeModelSerializer, InvenTreeAttachmentSerializer
from InvenTree.serializers import InvenTreeAttachmentSerializerField, UserSerializerBrief from InvenTree.serializers import UserSerializerBrief
import InvenTree.helpers import InvenTree.helpers
from InvenTree.serializers import InvenTreeDecimalField from InvenTree.serializers import InvenTreeDecimalField
@ -516,8 +516,6 @@ class BuildAttachmentSerializer(InvenTreeAttachmentSerializer):
Serializer for a BuildAttachment Serializer for a BuildAttachment
""" """
attachment = InvenTreeAttachmentSerializerField(required=True)
class Meta: class Meta:
model = BuildOrderAttachment model = BuildOrderAttachment
@ -525,6 +523,7 @@ class BuildAttachmentSerializer(InvenTreeAttachmentSerializer):
'pk', 'pk',
'build', 'build',
'attachment', 'attachment',
'link',
'filename', 'filename',
'comment', 'comment',
'upload_date', 'upload_date',

View File

@ -431,53 +431,17 @@ enableDragAndDrop(
} }
); );
// Callback for creating a new attachment loadAttachmentTable('{% url "api-build-attachment-list" %}', {
$('#new-attachment').click(function() { filters: {
build: {{ build.pk }},
constructForm('{% url "api-build-attachment-list" %}', { },
fields: { fields: {
attachment: {}, build: {
comment: {}, value: {{ build.pk }},
build: { hidden: true,
value: {{ build.pk }},
hidden: true,
}
},
method: 'POST',
onSuccess: reloadAttachmentTable,
title: '{% trans "Add Attachment" %}',
});
});
loadAttachmentTable(
'{% url "api-build-attachment-list" %}',
{
filters: {
build: {{ build.pk }},
},
onEdit: function(pk) {
var url = `/api/build/attachment/${pk}/`;
constructForm(url, {
fields: {
filename: {},
comment: {},
},
onSuccess: reloadAttachmentTable,
title: '{% trans "Edit Attachment" %}',
});
},
onDelete: function(pk) {
constructForm(`/api/build/attachment/${pk}/`, {
method: 'DELETE',
confirmMessage: '{% trans "Confirm Delete Operation" %}',
title: '{% trans "Delete Attachment" %}',
onSuccess: reloadAttachmentTable,
});
} }
} }
); });
$('#edit-notes').click(function() { $('#edit-notes').click(function() {
constructForm('{% url "api-build-detail" build.pk %}', { constructForm('{% url "api-build-detail" build.pk %}', {

View File

@ -0,0 +1,35 @@
# Generated by Django 3.2.5 on 2021-11-28 01:51
import InvenTree.fields
import InvenTree.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('order', '0052_auto_20211014_0631'),
]
operations = [
migrations.AddField(
model_name='purchaseorderattachment',
name='link',
field=InvenTree.fields.InvenTreeURLField(blank=True, help_text='Link to external URL', null=True, verbose_name='Link'),
),
migrations.AddField(
model_name='salesorderattachment',
name='link',
field=InvenTree.fields.InvenTreeURLField(blank=True, help_text='Link to external URL', null=True, verbose_name='Link'),
),
migrations.AlterField(
model_name='purchaseorderattachment',
name='attachment',
field=models.FileField(blank=True, help_text='Select file to attach', null=True, upload_to=InvenTree.models.rename_attachment, verbose_name='Attachment'),
),
migrations.AlterField(
model_name='salesorderattachment',
name='attachment',
field=models.FileField(blank=True, help_text='Select file to attach', null=True, upload_to=InvenTree.models.rename_attachment, verbose_name='Attachment'),
),
]

View File

@ -24,7 +24,6 @@ from InvenTree.serializers import InvenTreeAttachmentSerializer
from InvenTree.serializers import InvenTreeModelSerializer from InvenTree.serializers import InvenTreeModelSerializer
from InvenTree.serializers import InvenTreeDecimalField from InvenTree.serializers import InvenTreeDecimalField
from InvenTree.serializers import InvenTreeMoneySerializer from InvenTree.serializers import InvenTreeMoneySerializer
from InvenTree.serializers import InvenTreeAttachmentSerializerField
from InvenTree.status_codes import StockStatus from InvenTree.status_codes import StockStatus
from part.serializers import PartBriefSerializer from part.serializers import PartBriefSerializer
@ -377,8 +376,6 @@ class POAttachmentSerializer(InvenTreeAttachmentSerializer):
Serializers for the PurchaseOrderAttachment model Serializers for the PurchaseOrderAttachment model
""" """
attachment = InvenTreeAttachmentSerializerField(required=True)
class Meta: class Meta:
model = PurchaseOrderAttachment model = PurchaseOrderAttachment
@ -386,6 +383,7 @@ class POAttachmentSerializer(InvenTreeAttachmentSerializer):
'pk', 'pk',
'order', 'order',
'attachment', 'attachment',
'link',
'filename', 'filename',
'comment', 'comment',
'upload_date', 'upload_date',
@ -597,8 +595,6 @@ class SOAttachmentSerializer(InvenTreeAttachmentSerializer):
Serializers for the SalesOrderAttachment model Serializers for the SalesOrderAttachment model
""" """
attachment = InvenTreeAttachmentSerializerField(required=True)
class Meta: class Meta:
model = SalesOrderAttachment model = SalesOrderAttachment
@ -607,6 +603,7 @@ class SOAttachmentSerializer(InvenTreeAttachmentSerializer):
'order', 'order',
'attachment', 'attachment',
'filename', 'filename',
'link',
'comment', 'comment',
'upload_date', 'upload_date',
] ]

View File

@ -124,51 +124,16 @@
} }
); );
loadAttachmentTable( loadAttachmentTable('{% url "api-po-attachment-list" %}', {
'{% url "api-po-attachment-list" %}', filters: {
{ order: {{ order.pk }},
filters: { },
order: {{ order.pk }}, fields: {
}, order: {
onEdit: function(pk) { value: {{ order.pk }},
var url = `/api/order/po/attachment/${pk}/`; hidden: true,
constructForm(url, {
fields: {
filename: {},
comment: {},
},
onSuccess: reloadAttachmentTable,
title: '{% trans "Edit Attachment" %}',
});
},
onDelete: function(pk) {
constructForm(`/api/order/po/attachment/${pk}/`, {
method: 'DELETE',
confirmMessage: '{% trans "Confirm Delete Operation" %}',
title: '{% trans "Delete Attachment" %}',
onSuccess: reloadAttachmentTable,
});
} }
} }
);
$("#new-attachment").click(function() {
constructForm('{% url "api-po-attachment-list" %}', {
method: 'POST',
fields: {
attachment: {},
comment: {},
order: {
value: {{ order.pk }},
hidden: true,
},
},
reload: true,
title: '{% trans "Add Attachment" %}',
});
}); });
loadStockTable($("#stock-table"), { loadStockTable($("#stock-table"), {

View File

@ -110,55 +110,21 @@
}, },
label: 'attachment', label: 'attachment',
success: function(data, status, xhr) { success: function(data, status, xhr) {
location.reload(); reloadAttachmentTable();
} }
} }
); );
loadAttachmentTable( loadAttachmentTable('{% url "api-so-attachment-list" %}', {
'{% url "api-so-attachment-list" %}', filters: {
{ order: {{ order.pk }},
filters: { },
order: {{ order.pk }}, fields: {
order: {
value: {{ order.pk }},
hidden: true,
}, },
onEdit: function(pk) {
var url = `/api/order/so/attachment/${pk}/`;
constructForm(url, {
fields: {
filename: {},
comment: {},
},
onSuccess: reloadAttachmentTable,
title: '{% trans "Edit Attachment" %}',
});
},
onDelete: function(pk) {
constructForm(`/api/order/so/attachment/${pk}/`, {
method: 'DELETE',
confirmMessage: '{% trans "Confirm Delete Operation" %}',
title: '{% trans "Delete Attachment" %}',
onSuccess: reloadAttachmentTable,
});
}
} }
);
$("#new-attachment").click(function() {
constructForm('{% url "api-so-attachment-list" %}', {
method: 'POST',
fields: {
attachment: {},
comment: {},
order: {
value: {{ order.pk }},
hidden: true
}
},
onSuccess: reloadAttachmentTable,
title: '{% trans "Add Attachment" %}'
});
}); });
loadBuildTable($("#builds-table"), { loadBuildTable($("#builds-table"), {

View File

@ -0,0 +1,25 @@
# Generated by Django 3.2.5 on 2021-11-28 01:51
import InvenTree.fields
import InvenTree.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('part', '0074_partcategorystar'),
]
operations = [
migrations.AddField(
model_name='partattachment',
name='link',
field=InvenTree.fields.InvenTreeURLField(blank=True, help_text='Link to external URL', null=True, verbose_name='Link'),
),
migrations.AlterField(
model_name='partattachment',
name='attachment',
field=models.FileField(blank=True, help_text='Select file to attach', null=True, upload_to=InvenTree.models.rename_attachment, verbose_name='Attachment'),
),
]

View File

@ -75,8 +75,6 @@ class PartAttachmentSerializer(InvenTreeAttachmentSerializer):
Serializer for the PartAttachment class Serializer for the PartAttachment class
""" """
attachment = InvenTreeAttachmentSerializerField(required=True)
class Meta: class Meta:
model = PartAttachment model = PartAttachment
@ -85,6 +83,7 @@ class PartAttachmentSerializer(InvenTreeAttachmentSerializer):
'part', 'part',
'attachment', 'attachment',
'filename', 'filename',
'link',
'comment', 'comment',
'upload_date', 'upload_date',
] ]

View File

@ -999,36 +999,17 @@
}); });
onPanelLoad("part-attachments", function() { onPanelLoad("part-attachments", function() {
loadAttachmentTable( loadAttachmentTable('{% url "api-part-attachment-list" %}', {
'{% url "api-part-attachment-list" %}', filters: {
{ part: {{ part.pk }},
filters: { },
part: {{ part.pk }}, fields: {
}, part: {
onEdit: function(pk) { value: {{ part.pk }},
var url = `/api/part/attachment/${pk}/`; hidden: true
constructForm(url, {
fields: {
filename: {},
comment: {},
},
title: '{% trans "Edit Attachment" %}',
onSuccess: reloadAttachmentTable,
});
},
onDelete: function(pk) {
var url = `/api/part/attachment/${pk}/`;
constructForm(url, {
method: 'DELETE',
confirmMessage: '{% trans "Confirm Delete Operation" %}',
title: '{% trans "Delete Attachment" %}',
onSuccess: reloadAttachmentTable,
});
} }
} }
); });
enableDragAndDrop( enableDragAndDrop(
'#attachment-dropzone', '#attachment-dropzone',
@ -1043,26 +1024,6 @@
} }
} }
); );
$("#new-attachment").click(function() {
constructForm(
'{% url "api-part-attachment-list" %}',
{
method: 'POST',
fields: {
attachment: {},
comment: {},
part: {
value: {{ part.pk }},
hidden: true,
}
},
onSuccess: reloadAttachmentTable,
title: '{% trans "Add Attachment" %}',
}
)
});
}); });

View File

@ -0,0 +1,25 @@
# Generated by Django 3.2.5 on 2021-11-28 01:51
import InvenTree.fields
import InvenTree.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('stock', '0069_auto_20211109_2347'),
]
operations = [
migrations.AddField(
model_name='stockitemattachment',
name='link',
field=InvenTree.fields.InvenTreeURLField(blank=True, help_text='Link to external URL', null=True, verbose_name='Link'),
),
migrations.AlterField(
model_name='stockitemattachment',
name='attachment',
field=models.FileField(blank=True, help_text='Select file to attach', null=True, upload_to=InvenTree.models.rename_attachment, verbose_name='Attachment'),
),
]

View File

@ -420,8 +420,6 @@ class StockItemAttachmentSerializer(InvenTree.serializers.InvenTreeAttachmentSer
user_detail = InvenTree.serializers.UserSerializerBrief(source='user', read_only=True) user_detail = InvenTree.serializers.UserSerializerBrief(source='user', read_only=True)
attachment = InvenTree.serializers.InvenTreeAttachmentSerializerField(required=True)
# TODO: Record the uploading user when creating or updating an attachment! # TODO: Record the uploading user when creating or updating an attachment!
class Meta: class Meta:
@ -432,6 +430,7 @@ class StockItemAttachmentSerializer(InvenTree.serializers.InvenTreeAttachmentSer
'stock_item', 'stock_item',
'attachment', 'attachment',
'filename', 'filename',
'link',
'comment', 'comment',
'upload_date', 'upload_date',
'user', 'user',

View File

@ -221,55 +221,16 @@
} }
); );
loadAttachmentTable( loadAttachmentTable('{% url "api-stock-attachment-list" %}', {
'{% url "api-stock-attachment-list" %}', filters: {
{ stock_item: {{ item.pk }},
filters: { },
stock_item: {{ item.pk }}, fields: {
}, stock_item: {
onEdit: function(pk) { value: {{ item.pk }},
var url = `/api/stock/attachment/${pk}/`; hidden: true,
constructForm(url, {
fields: {
filename: {},
comment: {},
},
title: '{% trans "Edit Attachment" %}',
onSuccess: reloadAttachmentTable
});
},
onDelete: function(pk) {
var url = `/api/stock/attachment/${pk}/`;
constructForm(url, {
method: 'DELETE',
confirmMessage: '{% trans "Confirm Delete Operation" %}',
title: '{% trans "Delete Attachment" %}',
onSuccess: reloadAttachmentTable,
});
} }
} }
);
$("#new-attachment").click(function() {
constructForm(
'{% url "api-stock-attachment-list" %}',
{
method: 'POST',
fields: {
attachment: {},
comment: {},
stock_item: {
value: {{ item.pk }},
hidden: true,
},
},
reload: true,
title: '{% trans "Add Attachment" %}',
}
);
}); });
loadStockTestResultsTable( loadStockTestResultsTable(

View File

@ -1,5 +1,8 @@
{% load i18n %} {% load i18n %}
<button type='button' class='btn btn-outline-success' id='new-attachment-link'>
<span class='fas fa-link'></span> {% trans "Add Link" %}
</button>
<button type='button' class='btn btn-success' id='new-attachment'> <button type='button' class='btn btn-success' id='new-attachment'>
<span class='fas fa-plus-circle'></span> {% trans "Add Attachment" %} <span class='fas fa-plus-circle'></span> {% trans "Add Attachment" %}
</button> </button>

View File

@ -6,10 +6,57 @@
*/ */
/* exported /* exported
addAttachmentButtonCallbacks,
loadAttachmentTable, loadAttachmentTable,
reloadAttachmentTable, reloadAttachmentTable,
*/ */
/*
* Add callbacks to buttons for creating new attachments.
*
* Note: Attachments can also be external links!
*/
function addAttachmentButtonCallbacks(url, fields={}) {
// Callback for 'new attachment' button
$('#new-attachment').click(function() {
var file_fields = {
attachment: {},
comment: {},
};
Object.assign(file_fields, fields);
constructForm(url, {
fields: file_fields,
method: 'POST',
onSuccess: reloadAttachmentTable,
title: '{% trans "Add Attachment" %}',
});
});
// Callback for 'new link' button
$('#new-attachment-link').click(function() {
var link_fields = {
link: {},
comment: {},
};
Object.assign(link_fields, fields);
constructForm(url, {
fields: link_fields,
method: 'POST',
onSuccess: reloadAttachmentTable,
title: '{% trans "Add Link" %}',
});
});
}
function reloadAttachmentTable() { function reloadAttachmentTable() {
$('#attachment-table').bootstrapTable('refresh'); $('#attachment-table').bootstrapTable('refresh');
@ -20,6 +67,8 @@ function loadAttachmentTable(url, options) {
var table = options.table || '#attachment-table'; var table = options.table || '#attachment-table';
addAttachmentButtonCallbacks(url, options.fields || {});
$(table).inventreeTable({ $(table).inventreeTable({
url: url, url: url,
name: options.name || 'attachments', name: options.name || 'attachments',
@ -34,56 +83,77 @@ function loadAttachmentTable(url, options) {
$(table).find('.button-attachment-edit').click(function() { $(table).find('.button-attachment-edit').click(function() {
var pk = $(this).attr('pk'); var pk = $(this).attr('pk');
if (options.onEdit) { constructForm(`${url}${pk}/`, {
options.onEdit(pk); fields: {
} link: {},
comment: {},
},
processResults: function(data, fields, opts) {
// Remove the "link" field if the attachment is a file!
if (data.attachment) {
delete opts.fields.link;
}
},
onSuccess: reloadAttachmentTable,
title: '{% trans "Edit Attachment" %}',
});
}); });
// Add callback for 'delete' button // Add callback for 'delete' button
$(table).find('.button-attachment-delete').click(function() { $(table).find('.button-attachment-delete').click(function() {
var pk = $(this).attr('pk'); var pk = $(this).attr('pk');
if (options.onDelete) { constructForm(`${url}${pk}/`, {
options.onDelete(pk); method: 'DELETE',
} confirmMessage: '{% trans "Confirm Delete" %}',
title: '{% trans "Delete Attachment" %}',
onSuccess: reloadAttachmentTable,
});
}); });
}, },
columns: [ columns: [
{ {
field: 'attachment', field: 'attachment',
title: '{% trans "File" %}', title: '{% trans "Attachment" %}',
formatter: function(value) { formatter: function(value, row) {
var icon = 'fa-file-alt'; if (row.attachment) {
var icon = 'fa-file-alt';
var fn = value.toLowerCase(); var fn = value.toLowerCase();
if (fn.endsWith('.csv')) { if (fn.endsWith('.csv')) {
icon = 'fa-file-csv'; icon = 'fa-file-csv';
} else if (fn.endsWith('.pdf')) { } else if (fn.endsWith('.pdf')) {
icon = 'fa-file-pdf'; icon = 'fa-file-pdf';
} else if (fn.endsWith('.xls') || fn.endsWith('.xlsx')) { } else if (fn.endsWith('.xls') || fn.endsWith('.xlsx')) {
icon = 'fa-file-excel'; icon = 'fa-file-excel';
} else if (fn.endsWith('.doc') || fn.endsWith('.docx')) { } else if (fn.endsWith('.doc') || fn.endsWith('.docx')) {
icon = 'fa-file-word'; icon = 'fa-file-word';
} else if (fn.endsWith('.zip') || fn.endsWith('.7z')) { } else if (fn.endsWith('.zip') || fn.endsWith('.7z')) {
icon = 'fa-file-archive'; icon = 'fa-file-archive';
} else {
var images = ['.png', '.jpg', '.bmp', '.gif', '.svg', '.tif'];
images.forEach(function(suffix) {
if (fn.endsWith(suffix)) {
icon = 'fa-file-image';
}
});
}
var split = value.split('/');
var filename = split[split.length - 1];
var html = `<span class='fas ${icon}'></span> ${filename}`;
return renderLink(html, value);
} else if (row.link) {
var html = `<span class='fas fa-link'></span> ${row.link}`;
return renderLink(html, row.link);
} else { } else {
var images = ['.png', '.jpg', '.bmp', '.gif', '.svg', '.tif']; return '-';
images.forEach(function(suffix) {
if (fn.endsWith(suffix)) {
icon = 'fa-file-image';
}
});
} }
var split = value.split('/');
var filename = split[split.length - 1];
var html = `<span class='fas ${icon}'></span> ${filename}`;
return renderLink(html, value);
} }
}, },
{ {