diff --git a/5.2curlcommands.md b/5.2curlcommands.md
index d5dd239..fb8c03f 100644
--- a/5.2curlcommands.md
+++ b/5.2curlcommands.md
@@ -754,7 +754,7 @@ curl -X POST -H 'Content-type: application/json' http://localhost:8080/api/admin
"toolName":"jupyterPreviewer",
"scope":"file",
"types":["preview"],
- "toolUrl":"https://gdcc.github.io/dataverse-previewers/previewers/betatest/JupyterPreview.html",
+ "toolUrl":"https://qualitativedatarepository/dataverse-previewers/previewers/betatest/JupyterPreview.html",
"toolParameters": {
"queryParameters":[
{"fileid":"{fileId}"},
@@ -781,7 +781,7 @@ curl -X POST -H 'Content-type: application/json' http://localhost:8080/api/admin
"toolName":"mapShpPreviewer",
"scope":"file",
"types":["preview"],
- "toolUrl":"qualitativedatarepository.github.io/dataverse-previewers/previewers/v1.5/MapShpPreview.html",
+ "toolUrl":"https://qualitativedatarepository.github.io/dataverse-previewers/previewers/v1.5/MapShpPreview.html",
"toolParameters": {
"queryParameters":[
{"fileid":"{fileId}"},
@@ -810,7 +810,7 @@ curl -X POST -H 'Content-type: application/json' http://localhost:8080/api/admin
"toolName":"mapShpPreviewer",
"scope":"file",
"types":["preview"],
- "toolUrl":"qualitativedatarepository.github.io/dataverse-previewers/previewers/v1.5/MapRasterPreview.html",
+ "toolUrl":"https://qualitativedatarepository.github.io/dataverse-previewers/previewers/v1.5/MapRasterPreview.html",
"toolParameters": {
"queryParameters":[
{"fileid":"{fileId}"},
diff --git a/6.1curlcommands.md b/6.1curlcommands.md
index ca3b245..6bd7ccb 100644
--- a/6.1curlcommands.md
+++ b/6.1curlcommands.md
@@ -2008,7 +2008,61 @@ curl -X POST -H 'Content-type: application/json' http://localhost:8080/api/admin
These previewers support the [REFI-QDA standard](https://www.qdasoftware.org/) format for qualitative research files.
They allow viewing of the authors, codes, sources, annotations, and relationships listed in the codebook,
with added functionality to view code use frequency and filter by the entries in these tables. The REFI-QDA Project previewer
-also allows viewing the annotation anchor text retrieved from the zipped source files.
+also allows viewing the annotation anchor text retrieved from the zipped source files and download of the original and text versions of source files.
+Further, for users who can edit the dataset, the Codebook and Project Previewers allow creation of "redacted" versions that become publicly previewable
+even when the datafiles themselves are restricted. The "View *" previewers will only show when the user can download the datafile, while the "View Redacted *" previewers
+will show all the time. As the first available previewer shows as the default one in the dataset files table, it is recommended that these previewers
+be installed in the order shown below (so the full previewers are the default for those who can download the datafile).
+
+
+
+```bash
+curl -X POST -H 'Content-type: application/json' http://localhost:8080/api/admin/externalTools -d \
+'{
+ "displayName":"View REFI-QDA Codebook",
+ "description":"View the contents of a REFI-QDA codebook file.",
+ "toolName":"refiqdaCodebookPreviewer",
+ "scope":"file",
+ "types":["preview"],
+ "toolUrl":"/dataverse-previewers/previewers/betatest/REFIQDAPreview.html",
+ "toolParameters": {
+ "queryParameters":[
+ {"fileid":"{fileId}"},
+ {"siteUrl":"{siteUrl}"},
+ {"datasetid":"{datasetId}"},
+ {"datasetversion":"{datasetVersion}"},
+ {"locale":"{localeCode}"}
+ ]
+ },
+ "contentType":"text/x-xml-refiqda",
+ "allowedApiCalls": [
+ {
+ "name": "retrieveFileContents",
+ "httpMethod": "GET",
+ "urlTemplate": "/api/v1/access/datafile/{fileId}?gbrecs=true",
+ "timeOut": 3600
+ },
+ {
+ "name": "downloadFile",
+ "httpMethod": "GET",
+ "urlTemplate": "/api/v1/access/datafile/{fileId}?gbrecs=false",
+ "timeOut": 3600
+ },
+ {
+ "name": "uploadRedactedFile",
+ "httpMethod": "POST",
+ "urlTemplate": "/api/v1/access/datafile/{fileId}/auxiliary/qdc/1.0",
+ "timeOut": 3600
+ },
+ {
+ "name": "getDatasetVersionMetadata",
+ "httpMethod": "GET",
+ "urlTemplate": "/api/v1/datasets/{datasetId}/versions/{datasetVersion}",
+ "timeOut": 3600
+ }
+ ]
+}'
+```
```bash
curl -X POST -H 'Content-type: application/json' http://localhost:8080/api/admin/externalTools -d \
@@ -2018,7 +2072,7 @@ curl -X POST -H 'Content-type: application/json' http://localhost:8080/api/admin
"toolName":"refiqdaProjectPreviewer",
"scope":"file",
"types":["preview"],
- "toolUrl":"https://gdcc.github.io/dataverse-previewers/previewers/betatest/REFIQDPXPreview.html",
+ "toolUrl":"/dataverse-previewers/previewers/betatest/REFIQDPXPreview.html",
"toolParameters": {
"queryParameters":[
{"fileid":"{fileId}"},
@@ -2042,6 +2096,12 @@ curl -X POST -H 'Content-type: application/json' http://localhost:8080/api/admin
"urlTemplate": "/api/v1/access/datafile/{fileId}?gbrecs=false",
"timeOut": 3600
},
+ {
+ "name": "uploadRedactedFile",
+ "httpMethod": "POST",
+ "urlTemplate": "/api/v1/access/datafile/{fileId}/auxiliary/qdpx/1.0",
+ "timeOut": 3600
+ },
{
"name": "getDatasetVersionMetadata",
"httpMethod": "GET",
@@ -2055,12 +2115,62 @@ curl -X POST -H 'Content-type: application/json' http://localhost:8080/api/admin
```bash
curl -X POST -H 'Content-type: application/json' http://localhost:8080/api/admin/externalTools -d \
'{
- "displayName":"View REFI-QDA Codebook",
- "description":"View the contents of a REFI-QDA codebook file.",
- "toolName":"refiqdaCodebookPreviewer",
+ "displayName":"View Redacted REFI-QDA Project",
+ "description":"View the contents of a redacted EFI-QDA project file.",
+ "toolName":"refiqdaRedactedProjectPreviewer",
"scope":"file",
"types":["preview"],
- "toolUrl":"https://gdcc.github.io/dataverse-previewers/previewers/betatest/REFIQDAPreview.html",
+ "toolUrl":"/dataverse-previewers/previewers/betatest/REFIQDPXPreview.html",
+ "toolParameters": {
+ "queryParameters":[
+ {"fileid":"{fileId}"},
+ {"siteUrl":"{siteUrl}"},
+ {"datasetid":"{datasetId}"},
+ {"datasetversion":"{datasetVersion}"},
+ {"locale":"{localeCode}"}
+ ]
+ },
+ "contentType":"application/x-zip-refiqda",
+ "requirements": {
+ "auxFilesExist": [
+ {
+ "formatTag": "qdpx",
+ "formatVersion": "1.0"
+ }
+ ]
+ },
+ "allowedApiCalls": [
+ {
+ "name": "retrieveFileContents",
+ "httpMethod": "GET",
+ "urlTemplate": "/api/v1/access/datafile/{fileId}/auxiliary/qdpx/1.0?gbrecs=true",
+ "timeOut": 3600
+ },
+ {
+ "name": "downloadFile",
+ "httpMethod": "GET",
+ "urlTemplate": "/api/v1/access/datafile/{fileId}/auxiliary/qdpx/1.0?gbrecs=false",
+ "timeOut": 3600
+ },
+ {
+ "name": "getDatasetVersionMetadata",
+ "httpMethod": "GET",
+ "urlTemplate": "/api/v1/datasets/{datasetId}/versions/{datasetVersion}",
+ "timeOut": 3600
+ }
+ ]
+}'
+```
+
+```bash
+curl -X POST -H 'Content-type: application/json' http://localhost:8080/api/admin/externalTools -d \
+'{
+ "displayName":"View Redacted REFI-QDA Codebook",
+ "description":"View the contents of a redacted REFI-QDA codebook file.",
+ "toolName":"refiqdaRedactedCodebookPreviewer",
+ "scope":"file",
+ "types":["preview"],
+ "toolUrl":"/dataverse-previewers/previewers/betatest/REFIQDAPreview.html",
"toolParameters": {
"queryParameters":[
{"fileid":"{fileId}"},
@@ -2071,17 +2181,25 @@ curl -X POST -H 'Content-type: application/json' http://localhost:8080/api/admin
]
},
"contentType":"text/x-xml-refiqda",
+ "requirements": {
+ "auxFilesExist": [
+ {
+ "formatTag": "qdc",
+ "formatVersion": "1.0"
+ }
+ ]
+ },
"allowedApiCalls": [
{
"name": "retrieveFileContents",
"httpMethod": "GET",
- "urlTemplate": "/api/v1/access/datafile/{fileId}?gbrecs=true",
+ "urlTemplate": "/api/v1/access/datafile/{fileId}/auxiliary/qdc/1.0?gbrecs=true",
"timeOut": 3600
},
{
"name": "downloadFile",
"httpMethod": "GET",
- "urlTemplate": "/api/v1/access/datafile/{fileId}?gbrecs=false",
+ "urlTemplate": "/api/v1/access/datafile/{fileId}/auxiliary/qdc/1.0?gbrecs=false",
"timeOut": 3600
},
{
diff --git a/previewers/betatest/REFIQDAPreview.html b/previewers/betatest/REFIQDAPreview.html
index 6afce8b..5f569ae 100644
--- a/previewers/betatest/REFIQDAPreview.html
+++ b/previewers/betatest/REFIQDAPreview.html
@@ -6,9 +6,8 @@
REFI-QDA Preview
-
-
-
+
+
diff --git a/previewers/betatest/REFIQDPXPreview.html b/previewers/betatest/REFIQDPXPreview.html
index 426d8cc..9d2c632 100644
--- a/previewers/betatest/REFIQDPXPreview.html
+++ b/previewers/betatest/REFIQDPXPreview.html
@@ -9,15 +9,15 @@
+
-
-
-
+
+
').attr('id', 'waiting');
- $('
').width('15%').attr('src','images/Loading_icon.gif').attr('id','throbber').appendTo(wait);
- $('').text('Retrieving File...').appendTo(wait);
- wait.appendTo($('.preview'));
+ addStandardPreviewHeader(file, title, authors);
+ options = {
+ "stripIgnoreTag": true,
+ "stripIgnoreTagBody": ['script', 'head']
+ }; // Custom rules
+ wait = $('').attr('id', 'waiting');
+ $('
').width('15%').attr('src', 'images/Loading_icon.gif').attr('id', 'throbber').appendTo(wait);
+ $('').text('Retrieving File...').appendTo(wait);
+ wait.appendTo($('.preview'));
- fetch(fileUrl)
- .then(response => response.text())
- .then(data => parseData(data));
+ if (fileUrl.includes('auxiliary/qdpx')) {
+ redactedMode = true;
+ } else {
+ redactedMode = false;
+ }
+ fetch(fileUrl)
+ .then(response => response.text())
+ .then(data => parseData(data, file));
}
diff --git a/previewers/betatest/js/refiqdacore.js b/previewers/betatest/js/refiqdacore.js
index 8d40f2a..749dbf9 100644
--- a/previewers/betatest/js/refiqdacore.js
+++ b/previewers/betatest/js/refiqdacore.js
@@ -12,6 +12,7 @@ var sourceDataTable;
var setDataTable;
var tables = new Array();
+let file;
var textSourceCache = new Map(); // Cache for loaded text files
$(document).ready(function() {
@@ -23,44 +24,6 @@ function translateBaseHtmlPage() {
$('.refiqdaPreviewText').text(refiqdaPreviewText);
}
-function addSelectAllAndUnselectButtons(dataTable, tableContainer, tableId) {
- // Add "Unselect All" button next to the table title
- const unselectAllButton = $('')
- .addClass('unselect-all-btn')
- .hide()
- .on('click', function() {
- dataTable.rows({ selected: true }).deselect();
- });
- tableContainer.find('h2').first().append(unselectAllButton);
-
- // Add "Select All" checkbox to the table header
- const selectAllCheckbox = $('');
- $('#' + tableId + ' thead tr').prepend($('').append(selectAllCheckbox));
- $('#' + tableId + ' tbody tr').prepend($(' | ')); // Placeholder for alignment
-
- selectAllCheckbox.on('click', function() {
- if (this.checked) {
- dataTable.rows().select();
- } else {
- dataTable.rows().deselect();
- }
- });
-
- // Show/hide "Unselect All" button and manage "Select All" checkbox state
- dataTable.on('select deselect', function() {
- const selectedRows = dataTable.rows({ selected: true }).count();
- const totalRows = dataTable.rows().count();
-
- if (selectedRows > 0) {
- unselectAllButton.show();
- } else {
- unselectAllButton.hide();
- }
-
- selectAllCheckbox.prop('checked', selectedRows === totalRows);
- });
-}
-
var zipUrl = '';
//zipUrl is set in refiqdpx.js - the zip file case
@@ -68,6 +31,8 @@ function isZipMode() {
return typeof zipUrl !== 'undefined' && zipUrl !== null && zipUrl !== '';
}
+var redactedMode;
+
var wait;
var cy;
@@ -84,7 +49,8 @@ function findDataAttribute(name, attrNamedNodeMap) {
// Start parsing project file
// This function just adds a loading icon and initial text to the page and then calls parseData2
-function parseData(data) {
+function parseData(data, filejson) {
+ file=filejson;
$('#waiting').remove();
wait = $('').attr('id', 'waiting');
$(' ').width('15%').attr('src', 'images/Loading_icon.gif').appendTo(wait);
@@ -101,7 +67,9 @@ function parseData2(data) {
parser = new DOMParser();
xmlDoc = parser.parseFromString(data, "text/xml");
-
+ if(redactedMode) {
+ let redactedNotice = $('').addClass('redacted-notice').text("Note: This is a redacted, public view of the restricted QDAS file").appendTo($(".preview"));
+ }
//Add a Filter By option
let filterBlock = $('').width(tableWidth).appendTo($(".preview"));
filterBlock.append($("").html("Enable Filtering By"));
@@ -295,26 +263,34 @@ function parseData2(data) {
let pdfSel = selection.pdfSelection;
let textSel = selection.plainTextSelection;
let selectionName = pdfSel.getAttribute("name");
+
+ if(!selectionName) {
+ selectionName = "(Hover for more info)";
+ }
guid = pdfSel.getAttribute("guid");
codes = getCodeNames(pdfSel); // Codes are on the PDF selection
+ let sourceGuid = source.getAttribute("guid");
+
selectionMatches = sourceMatches +
pdfSel.getAttribute("creatingUser") + pdfSel.getAttribute("modifyingUser") +
textSel.getAttribute("creatingUser") + textSel.getAttribute("modifyingUser") +
- getCodeRelatedGUIDs(pdfSel);
-
- let sourceGuid = source.getAttribute("guid");
+ getCodeRelatedGUIDs(pdfSel) + sourceGuid;
displayName = createMergedSelectionWithTooltip(selectionName, pdfSel, textSel, sourceGuid);
} else {
// Handle regular selection node
let selectionName = selection.getAttribute("name");
+ if(!selectionName) {
+ selectionName = "(Hover for more info)";
+ }
guid = selection.getAttribute("guid");
codes = getCodeNames(selection);
- selectionMatches = sourceMatches + selection.getAttribute("creatingUser") + selection.getAttribute("modifyingUser") + getCodeRelatedGUIDs(selection);
-
let sourceGuid = source.getAttribute("guid");
+ selectionMatches = sourceMatches + selection.getAttribute("creatingUser") + selection.getAttribute("modifyingUser") + getCodeRelatedGUIDs(selection) + sourceGuid;
+
+
displayName = selectionName; // Default display name
if (selection.nodeName === "PDFSelection") {
@@ -409,7 +385,19 @@ function parseData2(data) {
});
sourceDataTable = new DataTable(".sourcetable", {
- select: $('#filterby').val() == 'Sources'
+ select: $('#filterby').val() == 'Sources',
+ order: [[0, 'asc']],
+ columnDefs: [
+ {
+ render: function(data, type, row) {
+ if (type === 'display' && data !== null && data.length > 50) {
+ return '' + data.substr(0, 50) + '...';
+ }
+ return data;
+ },
+ targets: 1
+ }
+ ]
});
if (typeof downloadFile === 'function') {
@@ -446,14 +434,29 @@ function parseData2(data) {
desc = desc[0].childNodes[0];
}
let matches = '';
- if (note.getAttribute("creatingUser")) {
- matches = matches + note.getAttribute("creatingUser");
+ let name = '';
+ let creatingUserGuid = note.getAttribute("creatingUser");
+ let modifyingUserGuid = note.getAttribute("modifyingUser");
+ let userNames = new Set();
+
+ if (creatingUserGuid) {
+ matches += creatingUserGuid;
+ let user = userMap.get(creatingUserGuid);
+ if (user) {
+ userNames.add(user.getAttribute("name"));
+ }
+ }
+
+ if (modifyingUserGuid) {
+ matches += modifyingUserGuid;
+ let user = userMap.get(modifyingUserGuid);
+ if (user) {
+ userNames.add(user.getAttribute("name"));
}
- if (note.getAttribute("modifyingUser")) {
- matches = matches + note.getAttribute("modifyingUser");
}
+ name = Array.from(userNames).join(', ');
- let tr = addRow(noteTable, note.getAttribute("name"), ptc, desc, userMap.get(note.getAttribute("creatingUser")).getAttribute("name"));
+ let tr = addRow(noteTable, note.getAttribute("name"), ptc, desc, name);
tr.attr('data-guid', note.getAttribute("guid"));
tr.attr('data-matches', matches);
@@ -468,6 +471,62 @@ function parseData2(data) {
tables.push(noteDataTable);
}
+ let variables = xmlDoc.getElementsByTagName("Variable");
+ let cases = xmlDoc.getElementsByTagName("Case");
+
+ if (variables.length > 0 && cases.length > 0) {
+ let variableMap = new Map();
+ let variableHeaders = ["Source"]; // First column is the source document
+
+ for (let variable of variables) {
+ let guid = variable.getAttribute("guid");
+ let name = variable.getAttribute("name");
+ // Store the variable name and its column index in the table
+ variableMap.set(guid, { name: name, index: variableHeaders.length });
+ variableHeaders.push(name);
+ }
+
+ let caseBlock = $('').width(tableWidth).appendTo($(".preview"));
+ caseBlock.append($("").html("Cases"));
+ let caseTable = createTable("Cases", ...variableHeaders).appendTo(caseBlock);
+ caseTable.addClass("casetable compact stripe");
+
+ for (let caseNode of cases) {
+ let rowData = new Array(variableHeaders.length).fill(""); // Initialize row with empty strings
+
+ // Find the source document for the case
+ let sourceRef = caseNode.getElementsByTagName("SourceRef")[0];
+ if (sourceRef) {
+ let sourceGuid = sourceRef.getAttribute("targetGUID");
+ let source = sourceMap.get(sourceGuid);
+ if (source) {
+ rowData[0] = createSourceReference(source, zipUrl);
+ }
+ }
+
+ // Populate variable values for the case
+ let variableValues = caseNode.getElementsByTagName("VariableValue");
+ for (let varValue of variableValues) {
+ let varRef = varValue.getElementsByTagName("VariableRef")[0];
+ let textValue = varValue.getElementsByTagName("TextValue")[0];
+ if (varRef && textValue) {
+ let varGuid = varRef.getAttribute("targetGUID");
+ let variableInfo = variableMap.get(varGuid);
+ if (variableInfo) {
+ rowData[variableInfo.index] = textValue.textContent;
+ }
+ }
+ }
+ addRow(caseTable, ...rowData);
+ }
+
+ // Initialize DataTable for cases, but don't add to filterable tables
+ new DataTable(".casetable", {
+ select: false // This table should not be selectable
+ });
+ }
+
+
let sets = xmlDoc.getElementsByTagName("Set");
if (sets != null && sets.length > 0) {
$('#filterby').append($('').prop('value', 'Sets').text('Sets'));
@@ -487,7 +546,7 @@ function parseData2(data) {
let codeId = member.getAttribute('targetGUID');
let code = codeMap.get(codeId);
if (code != null) {
- codeNames = codeNames + ' ' + code.getAttribute("name");
+ codeNames = codeNames + '; ' + code.getAttribute("name");
}
matches += codeId;
}
@@ -685,11 +744,58 @@ $("#filterby")
$(".sourcetable").off('select.dt deselect.dt');
}
if ($(".sourcetable").length) {
- sourceDataTable = new DataTable(".sourcetable", {
- select: $('#filterby').val() == 'Sources'
- });
+ let dtOptions = {
+ select: $('#filterby').val() == 'Sources',
+ columnDefs: [
+ {
+ render: function(data, type, row) {
+ if (type === 'display' && data !== null && data.length > 50) {
+ return '' + data.substr(0, 50) + '...';
+ }
+ return data;
+ },
+ targets: 1
+ }
+ ]
+
+ };
+
+ if ($('#filterby').val() == 'Sources') {
+ dtOptions.layout = {
+ top2End: 'buttons'
+ };
+ dtOptions.buttons = [
+ 'selectAll',
+ 'selectNone',
+ {
+ text: 'Redact',
+ className: 'redact-btn',
+ titleAttr: 'Create a redacted version of the datafile with the selected sources, and any related annotations, removed.',
+ action: function ( e, dt, node, config ) {
+ let selectedRows = dt.rows( { selected: true } );
+ let sourceGuids = [];
+ selectedRows.nodes().to$().each(function() {
+ sourceGuids.push($(this).data('guid'));
+ });
+ redactSources(sourceGuids);
+ },
+ enabled: false
+ }
+ ];
+ }
+
+ sourceDataTable = new DataTable(".sourcetable", dtOptions);
+
+ if ($('#filterby').val() == 'Sources') {
+ sourceDataTable.on('select deselect', function () {
+ var selectedRows = sourceDataTable.rows({ selected: true }).count();
+ sourceDataTable.button('.redact-btn').enable(selectedRows > 0);
+ });
+ }
+
attachFilterHandler(sourceDataTable);
sourceDataTable.draw();
+
tables.push(sourceDataTable);
}
@@ -966,27 +1072,91 @@ function createPdfSelectionWithTooltip(selectionName, page, firstX, firstY, seco
return spanHtml;
}
-function createSourceReference(source) {
- let sourceName = source.getAttribute("name");
- let plainTextPath = source.getAttribute("plainTextPath");
-
- if (isZipMode()) {
- // In zip mode, create a link that uses the zip entry
- if (typeof entryMap !== 'undefined' && plainTextPath && entryMap[plainTextPath] !== undefined) {
- let entryIndex = entryMap[plainTextPath];
- return '' + sourceName + '';
- } else {
- // No link if file not found in zip
- return sourceName;
- }
- } else {
- // In non-zip mode, create a direct link to the file if it exists
- if (plainTextPath) {
- return '' + sourceName + '';
- } else {
- return sourceName;
+function createSourceReference(sourceElement) {
+ const sourceName = sourceElement.getAttribute("name");
+ const sourceGuid = sourceElement.getAttribute("guid");
+ const path = sourceElement.getAttribute("path");
+ const plainTextPath = sourceElement.getAttribute("plainTextPath");
+ const richTextPath = sourceElement.getAttribute("richTextPath");
+
+ const referenceDiv = document.createElement('div');
+ referenceDiv.className = 'source-reference';
+
+ const nameSpan = document.createElement('span');
+ nameSpan.textContent = `${sourceName} (`;
+ referenceDiv.appendChild(nameSpan);
+
+ const links = [];
+
+ // Handle binary files (like PDFs) that have a 'path'
+ if (path) {
+ const pdfLink = document.createElement('a');
+ pdfLink.href = '#';
+ pdfLink.textContent = 'PDF';
+ pdfLink.title = 'Download PDF';
+ pdfLink.onclick = (e) => {
+ e.preventDefault();
+ downloadSourceFile(sourceGuid, path);
+ };
+ links.push(pdfLink);
+
+ // Check for a plain text representation of the binary file
+ const representation = sourceElement.querySelector('Representation[plainTextPath]');
+ if (representation) {
+ const textRepresentationPath = representation.getAttribute('plainTextPath');
+ const textLink = document.createElement('a');
+ textLink.href = '#';
+ textLink.textContent = 'TXT';
+ textLink.title='Download Text';
+ textLink.onclick = (e) => {
+ e.preventDefault();
+ downloadSourceFile(sourceGuid, textRepresentationPath);
+ };
+ links.push(textLink);
}
}
+ // Handle plain text files that only have a 'plainTextPath'
+ else if (plainTextPath) {
+ const textLink = document.createElement('a');
+ textLink.href = '#';
+ textLink.textContent = 'TXT';
+ textLink.title = 'Download Text';
+ textLink.onclick = (e) => {
+ e.preventDefault();
+ downloadSourceFile(sourceGuid, plainTextPath);
+ };
+ links.push(textLink);
+ }
+
+ // Handle rich text files (like DOCX) that have a 'richTextPath'
+ if (richTextPath) {
+ const richTextLink = document.createElement('a');
+ richTextLink.href = '#';
+ const extension = richTextPath.split('.').pop().toUpperCase();
+ richTextLink.textContent = extension;
+ richTextLink.title = 'Download Rich Text';
+ richTextLink.onclick = (e) => {
+ e.preventDefault();
+ downloadSourceFile(sourceGuid, richTextPath);
+ };
+ links.push(richTextLink);
+ }
+
+ // Append all created links with separators
+ links.forEach((link, index) => {
+ referenceDiv.appendChild(link);
+ if (index < links.length - 1) {
+ const separator = document.createElement('span');
+ separator.textContent = ' | ';
+ referenceDiv.appendChild(separator);
+ }
+ });
+
+ const closingParen = document.createElement('span');
+ closingParen.textContent = ')';
+ referenceDiv.appendChild(closingParen);
+
+ return referenceDiv;
}
function formatExcerptTooltip(excerpt, startPos, endPos) {
@@ -1131,3 +1301,146 @@ async function loadTextExcerpt(plainTextPath, startPos, endPos, sourceGuid) {
return null;
}
}
+
+function redactSources(guidsToRedact) {
+ console.log("Redacting sources with GUIDs:", guidsToRedact);
+
+ // Find the paths of the source files to remove from the zip archive.
+ const pathsToRemove = new Set();
+ for (const guid of guidsToRedact) {
+ const sourceElement = xmlDoc.querySelector(`[guid="${guid}"]`);
+ if (sourceElement) {
+ const plainTextPath = sourceElement.getAttribute("plainTextPath");
+ if (plainTextPath) {
+ // The path in the zip is typically "sources/..."
+ pathsToRemove.add(plainTextPath.replace("internal://", ""));
+ }
+ }
+ }
+ console.log("Paths to remove from zip:", Array.from(pathsToRemove));
+
+
+ let redactedXmlDoc = xmlDoc.cloneNode(true);
+
+ for (const guid of guidsToRedact) {
+ // Find any source element by its GUID and remove it.
+ // This will also remove all its children, including annotations.
+ let sourceElement = redactedXmlDoc.querySelector('[guid="' + guid + '"]');
+ if (sourceElement && sourceElement.parentNode) {
+ sourceElement.parentNode.removeChild(sourceElement);
+ console.log("Removed source element and its annotations with GUID:", guid);
+ }
+ }
+
+ const redactedXmlString = new XMLSerializer().serializeToString(redactedXmlDoc);
+
+ if (isZipMode() && typeof zip !== 'undefined') {
+ // Use an async function to handle zip operations
+ (async () => {
+ try {
+ // Fetch the original zip file as a blob on-demand
+ const zipResponse = await fetch(zipUrl);
+ if (!zipResponse.ok) {
+ throw new Error(`HTTP error! status: ${zipResponse.status}`);
+ }
+ zipFileBlob = await zipResponse.blob();
+
+ // 1. Create a new zip archive
+ const zipWriter = new zip.ZipWriter(new zip.BlobWriter("application/x-zip-refiqda"));
+
+ // 2. Read entries from the original zip blob
+ const zipReader = new zip.ZipReader(new zip.BlobReader(zipFileBlob));
+ const entries = await zipReader.getEntries();
+
+ // 3. Copy entries to the new zip, excluding redacted files
+ for (const entry of entries) {
+ if (entry.filename === "project.qde") {
+ // Skip the old project file; we'll add the new one later
+ continue;
+ }
+ if (!pathsToRemove.has(entry.filename)) {
+ await zipWriter.add(entry.filename, new zip.BlobReader(await entry.getData(new zip.BlobWriter())));
+ } else {
+ console.log(`Excluding ${entry.filename} from new zip.`);
+ }
+ }
+
+ // 4. Add the new, redacted project.qde
+ await zipWriter.add("project.qde", new zip.TextReader(redactedXmlString));
+
+ // 5. Finalize the new zip file
+ const redactedZipBlob = await zipWriter.close();
+
+ // 6. Prepare for upload
+ const formData = new FormData();
+ const originalFilename = file.filename || "project.qdpx";
+ const redactedFilename = originalFilename.replace(/(\.qdpx)?$/, '-redacted.qdpx');
+
+ formData.append("file", redactedZipBlob, redactedFilename);
+ formData.append("origin", "qdas");
+ formData.append("isPublic", "true"); // Or handle dynamically if needed
+ formData.append("type", "qda");
+
+ // 7. POST the new file to Dataverse
+ console.log(`Uploading redacted file: ${redactedFilename}`);
+ const response = await fetch(queryParams.signedUrls.uploadRedactedFile, {
+ method: 'POST',
+ body: formData,
+ // Headers are not needed for FormData; browser sets them
+ });
+
+ if (!response.ok) {
+ const errorText = await response.text();
+ throw new Error(`Upload failed: ${response.statusText} - ${errorText}`);
+ }
+
+ const responseData = await response.json();
+ console.log("Upload successful:", responseData);
+ alert("Redacted file has been successfully uploaded to Dataverse.");
+
+ } catch (error) {
+ console.error("Error during redaction and upload:", error);
+ alert(`An error occurred during the redaction process: ${error.message}`);
+ }
+ })();
+ } else {
+ // Fallback for non-zip mode (single XML file)
+ (async () => {
+ try {
+ // 1. Create a blob from the redacted XML string
+ const redactedXmlBlob = new Blob([redactedXmlString], { type: 'text/x-xml-refiqda' });
+
+ // 2. Prepare for upload
+ const formData = new FormData();
+ const originalFilename = file.filename || "project.qdc";
+ const redactedFilename = originalFilename.replace(/(\.qdc)?$/, '-redacted.qdc');
+
+ formData.append("file", redactedXmlBlob, redactedFilename);
+ formData.append("origin", "qdas");
+ formData.append("isPublic", "true"); // Or handle dynamically if needed
+ formData.append("type", "qda");
+
+ // 3. POST the new file to Dataverse
+ console.log(`Uploading redacted file: ${redactedFilename}`);
+ const response = await fetch(queryParams.signedUrls.uploadRedactedFile, {
+ method: 'POST',
+ body: formData,
+ // Headers are not needed for FormData; browser sets them
+ });
+
+ if (!response.ok) {
+ const errorText = await response.text();
+ throw new Error(`Upload failed: ${response.statusText} - ${errorText}`);
+ }
+
+ const responseData = await response.json();
+ console.log("Upload successful:", responseData);
+ alert("Redacted file has been successfully uploaded to Dataverse.");
+
+ } catch (error) {
+ console.error("Error during redaction and upload:", error);
+ alert(`An error occurred during the redaction process: ${error.message}`);
+ }
+ })();
+ }
+}
diff --git a/previewers/betatest/js/refiqdpx.js b/previewers/betatest/js/refiqdpx.js
index ad73e83..faf6f66 100644
--- a/previewers/betatest/js/refiqdpx.js
+++ b/previewers/betatest/js/refiqdpx.js
@@ -4,13 +4,18 @@ function writeContent(fileUrl, file, title, authors) {
// Set the global zipUrl variable so other code knows we're in zip mode
zipUrl = fileUrl;
- readZip(fileUrl);
+ if(fileUrl.includes('auxiliary/qdpx')) {
+ redactedMode = true;
+ } else {
+ redactedMode = false;
+ }
+ readZip(fileUrl, file);
}
let entries;
const entryMap = {};
-async function readZip(fileUrl) {
+async function readZip(fileUrl, file) {
wait = $('').attr('id', 'waiting');
$(' ').width('15%').attr('src','images/Loading_icon.gif').attr('id','throbber').appendTo(wait);
$('').text(' Reading QPDX file. Parsing Contents...').appendTo(wait);
@@ -43,7 +48,7 @@ async function readZip(fileUrl) {
},
});
- projectBlob.then(text => parseData(text)).catch((err)=> {
+ projectBlob.then(text => parseData(text, file)).catch((err)=> {
document.getElementById('waiting').innerHTML= "Unable to continue: " + err + "";
});
@@ -123,12 +128,13 @@ async function downloadFile(event) {
}
async function download(entry, li, a) {
- if (!li.classList.contains("busy")) {
+ const parentCell = $(a).closest('td');
+ if (!parentCell.hasClass("busy")) {
const controller = new AbortController();
const signal = controller.signal;
- li.classList.add("busy");
+ parentCell.addClass("busy");
try {
const blobURL = URL.createObjectURL(await entry.getData(new zip.BlobWriter(), {
onprogress: (index, max) => {
@@ -141,8 +147,11 @@ async function download(entry, li, a) {
}))
var index = a.getAttribute("data-entry-index");
console.log("index: " + index);
+
+ const filename = a.getAttribute("data-entry-name");
+
$("a[data-entry-index='" + index + "']").attr('href',blobURL);
- $("a[data-entry-index='" + index + "']").attr('download',a.text);
+ $("a[data-entry-index='" + index + "']").attr('download', filename || a.text);
const clickEvent = new MouseEvent("click");
a.dispatchEvent(clickEvent);
} catch (error) {
@@ -150,7 +159,7 @@ async function download(entry, li, a) {
throw error;
}
} finally {
- li.classList.remove("busy");
+ parentCell.removeClass("busy");
}
}
}
diff --git a/previewers/betatest/js/retriever.js b/previewers/betatest/js/retriever.js
index 8d17611..a1741f6 100644
--- a/previewers/betatest/js/retriever.js
+++ b/previewers/betatest/js/retriever.js
@@ -38,6 +38,7 @@ function startPreview(retrieveFile) {
queryParams.preview = params.get('preview');
}
var urls = json.data.signedUrls;
+ queryParams.signedUrls = {};
for (var i in urls) {
var url = urls[i];
switch (url.name) {
@@ -51,6 +52,7 @@ function startPreview(retrieveFile) {
queryParams.versionUrl = url.signedUrl;
break;
default:
+ queryParams.signedUrls[url.name] = url.signedUrl;
}
}
continuePreview(retrieveFile, queryParams);
|