Initial release v1.0.0 — Nextcloud Collabora Node-RED sidecar
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('collabora-bridge', {
|
||||
category: 'nextcloud collabora',
|
||||
color: '#e67e22',
|
||||
defaults: {
|
||||
name: { value: '' },
|
||||
config: { type: 'nc-collab-config', required: true },
|
||||
operation: { value: 'ncFileToPdf' }
|
||||
},
|
||||
inputs: 1,
|
||||
outputs: 1,
|
||||
icon: 'font-awesome/fa-exchange',
|
||||
label: function () {
|
||||
return this.name || this.operation;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-template-name="collabora-bridge">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Namn</label>
|
||||
<input type="text" id="node-input-name">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-config"><i class="fa fa-cog"></i> Config</label>
|
||||
<input type="text" id="node-input-config">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-operation"><i class="fa fa-wrench"></i> Pipeline</label>
|
||||
<select id="node-input-operation">
|
||||
<option value="ncFileToPdf">ncFileToPdf</option>
|
||||
<option value="ncFileToPng">ncFileToPng</option>
|
||||
<option value="ncFileToHtml">ncFileToHtml</option>
|
||||
<option value="htmlToOffice">htmlToOffice</option>
|
||||
<option value="fillTemplateSave">fillTemplateSave</option>
|
||||
<option value="compareVersionsPdf">compareVersionsPdf</option>
|
||||
<option value="extractToPayload">extractToPayload</option>
|
||||
<option value="createAssetLink">createAssetLink</option>
|
||||
</select>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="collabora-bridge">
|
||||
<p>Agent-pipelines: NC-fil → konvertering → WebDAV PUT under <code>/Dokument/OfficeOut/</code>.</p>
|
||||
<p>Skriver aldrig över original. Sätt <code>msg.allowOverwrite=true</code> för annan destNcPath.</p>
|
||||
<p>Överstyr via <code>msg.operation</code>. Standardfält: <code>ncPath</code>, <code>destNcPath</code>, <code>format</code>, <code>fields</code>, <code>compareNcPath</code>.</p>
|
||||
</script>
|
||||
@@ -0,0 +1,64 @@
|
||||
'use strict';
|
||||
|
||||
const bridge = require('../lib/bridge');
|
||||
const { getConfig, setError, setSuccess, clearStatus, pickMsgField } = require('../lib/utils');
|
||||
|
||||
const OPERATIONS = {
|
||||
ncFileToPdf: bridge.ncFileToPdf,
|
||||
ncFileToPng: bridge.ncFileToPng,
|
||||
ncFileToHtml: bridge.ncFileToHtml,
|
||||
htmlToOffice: bridge.htmlToOffice,
|
||||
fillTemplateSave: bridge.fillTemplateSave,
|
||||
compareVersionsPdf: bridge.compareVersionsPdf,
|
||||
extractToPayload: bridge.extractToPayload,
|
||||
createAssetLink: bridge.createAssetLink
|
||||
};
|
||||
|
||||
module.exports = function (RED) {
|
||||
function CollaboraBridgeNode(config) {
|
||||
RED.nodes.createNode(this, config);
|
||||
const node = this;
|
||||
node.configNode = RED.nodes.getNode(config.config);
|
||||
node.operation = config.operation || 'ncFileToPdf';
|
||||
|
||||
node.on('input', async (msg, send, done) => {
|
||||
send = send || function () { node.send.apply(node, arguments); };
|
||||
done = done || function (err) { if (err) { node.error(err, msg); } };
|
||||
clearStatus(node);
|
||||
|
||||
try {
|
||||
const cfg = getConfig(node);
|
||||
const op = pickMsgField(msg, 'operation') || node.operation;
|
||||
const fn = OPERATIONS[op];
|
||||
if (!fn) {
|
||||
throw new Error(`Okänd bridge-operation: ${op}`);
|
||||
}
|
||||
|
||||
const result = await fn(cfg, msg);
|
||||
Object.assign(msg, result);
|
||||
if (result.payload !== undefined) {
|
||||
msg.payload = result.payload;
|
||||
} else {
|
||||
msg.payload = {
|
||||
destNcPath: result.destNcPath,
|
||||
filename: result.filename,
|
||||
format: result.format,
|
||||
assetUrl: result.assetUrl,
|
||||
fields: result.fields,
|
||||
structure: result.structure
|
||||
};
|
||||
}
|
||||
|
||||
setSuccess(node, msg, op);
|
||||
send(msg);
|
||||
done();
|
||||
} catch (err) {
|
||||
setError(node, msg, err);
|
||||
send(msg);
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
RED.nodes.registerType('collabora-bridge', CollaboraBridgeNode);
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('collabora-convert', {
|
||||
category: 'nextcloud collabora',
|
||||
color: '#0082c9',
|
||||
defaults: {
|
||||
name: { value: '' },
|
||||
config: { type: 'nc-collab-config', required: true },
|
||||
operation: { value: 'convert' },
|
||||
format: { value: 'pdf' }
|
||||
},
|
||||
inputs: 1,
|
||||
outputs: 1,
|
||||
icon: 'font-awesome/fa-file-pdf-o',
|
||||
label: function () {
|
||||
return this.name || `convert → ${this.format}`;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-template-name="collabora-convert">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Namn</label>
|
||||
<input type="text" id="node-input-name">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-config"><i class="fa fa-cog"></i> Config</label>
|
||||
<input type="text" id="node-input-config">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-format"><i class="fa fa-file"></i> Format</label>
|
||||
<select id="node-input-format">
|
||||
<option value="pdf">pdf</option>
|
||||
<option value="png">png</option>
|
||||
<option value="docx">docx</option>
|
||||
<option value="odt">odt</option>
|
||||
<option value="html">html</option>
|
||||
<option value="txt">txt</option>
|
||||
<option value="xlsx">xlsx</option>
|
||||
<option value="pptx">pptx</option>
|
||||
</select>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="collabora-convert">
|
||||
<p>Låg nivå: <code>/cool/convert-to</code> mot CODE.</p>
|
||||
<p>Ingång: <code>msg.ncPath</code> eller <code>msg.payload</code> (Buffer). Valfritt <code>msg.format</code>, <code>msg.templateNcPath</code>, <code>msg.compareNcPath</code>.</p>
|
||||
<p>Utdata: <code>msg.payload</code> (Buffer). Sparar inte till Nextcloud — använd bridge eller WebDAV PUT efteråt.</p>
|
||||
</script>
|
||||
@@ -0,0 +1,60 @@
|
||||
'use strict';
|
||||
|
||||
const { getConfig, setError, setSuccess, clearStatus, pickMsgField } = require('../lib/utils');
|
||||
const { convertTo } = require('../lib/collabora');
|
||||
const { loadSourceBuffer } = require('../lib/bridge');
|
||||
const { webdavGet } = require('../lib/webdav');
|
||||
|
||||
module.exports = function (RED) {
|
||||
function CollaboraConvertNode(config) {
|
||||
RED.nodes.createNode(this, config);
|
||||
const node = this;
|
||||
node.configNode = RED.nodes.getNode(config.config);
|
||||
node.operation = config.operation || 'convert';
|
||||
node.format = config.format || 'pdf';
|
||||
|
||||
node.on('input', async (msg, send, done) => {
|
||||
send = send || function () { node.send.apply(node, arguments); };
|
||||
done = done || function (err) { if (err) { node.error(err, msg); } };
|
||||
clearStatus(node);
|
||||
|
||||
try {
|
||||
const cfg = getConfig(node);
|
||||
const format = (pickMsgField(msg, 'format') || node.format).toLowerCase();
|
||||
const source = await loadSourceBuffer(cfg, msg);
|
||||
const extraFields = [];
|
||||
|
||||
if (msg.templateNcPath) {
|
||||
const tpl = await webdavGet(cfg, msg.templateNcPath);
|
||||
extraFields.push({ name: 'template', buffer: tpl.buffer, filename: tpl.filename });
|
||||
}
|
||||
if (msg.compareNcPath) {
|
||||
const cmp = await webdavGet(cfg, msg.compareNcPath);
|
||||
extraFields.push({ name: 'compare', buffer: cmp.buffer, filename: cmp.filename });
|
||||
}
|
||||
|
||||
const result = await convertTo(cfg, {
|
||||
buffer: source.buffer,
|
||||
filename: source.filename,
|
||||
format,
|
||||
extraFields
|
||||
});
|
||||
|
||||
msg.payload = result.buffer;
|
||||
msg.format = format;
|
||||
msg.contentType = result.contentType;
|
||||
msg.statusCode = result.statusCode;
|
||||
msg.filename = source.filename;
|
||||
setSuccess(node, msg, `convert → ${format}`);
|
||||
send(msg);
|
||||
done();
|
||||
} catch (err) {
|
||||
setError(node, msg, err);
|
||||
send(msg);
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
RED.nodes.registerType('collabora-convert', CollaboraConvertNode);
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('collabora-extract', {
|
||||
category: 'nextcloud collabora',
|
||||
color: '#0082c9',
|
||||
defaults: {
|
||||
name: { value: '' },
|
||||
config: { type: 'nc-collab-config', required: true },
|
||||
filter: { value: 'contentcontrol' }
|
||||
},
|
||||
inputs: 1,
|
||||
outputs: 1,
|
||||
icon: 'font-awesome/fa-sitemap',
|
||||
label: function () {
|
||||
return this.name || 'extract structure';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-template-name="collabora-extract">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Namn</label>
|
||||
<input type="text" id="node-input-name">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-config"><i class="fa fa-cog"></i> Config</label>
|
||||
<input type="text" id="node-input-config">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-filter"><i class="fa fa-filter"></i> Filter</label>
|
||||
<input type="text" id="node-input-filter" placeholder="contentcontrol">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="collabora-extract">
|
||||
<p><code>/cool/extract-document-structure</code></p>
|
||||
<p>Ingång: <code>msg.ncPath</code> eller Buffer i <code>msg.payload</code>. Valfritt <code>msg.filter</code>.</p>
|
||||
</script>
|
||||
@@ -0,0 +1,43 @@
|
||||
'use strict';
|
||||
|
||||
const { getConfig, setError, setSuccess, clearStatus, pickMsgField } = require('../lib/utils');
|
||||
const { extractStructure } = require('../lib/collabora');
|
||||
const { loadSourceBuffer } = require('../lib/bridge');
|
||||
|
||||
module.exports = function (RED) {
|
||||
function CollaboraExtractNode(config) {
|
||||
RED.nodes.createNode(this, config);
|
||||
const node = this;
|
||||
node.configNode = RED.nodes.getNode(config.config);
|
||||
node.filter = config.filter || 'contentcontrol';
|
||||
|
||||
node.on('input', async (msg, send, done) => {
|
||||
send = send || function () { node.send.apply(node, arguments); };
|
||||
done = done || function (err) { if (err) { node.error(err, msg); } };
|
||||
clearStatus(node);
|
||||
|
||||
try {
|
||||
const cfg = getConfig(node);
|
||||
const source = await loadSourceBuffer(cfg, msg);
|
||||
const filter = pickMsgField(msg, 'filter') || node.filter;
|
||||
const structure = await extractStructure(cfg, {
|
||||
buffer: source.buffer,
|
||||
filename: source.filename,
|
||||
filter
|
||||
});
|
||||
|
||||
msg.payload = structure;
|
||||
msg.structure = structure;
|
||||
setSuccess(node, msg, 'extract');
|
||||
send(msg);
|
||||
done();
|
||||
} catch (err) {
|
||||
setError(node, msg, err);
|
||||
send(msg);
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
RED.nodes.registerType('collabora-extract', CollaboraExtractNode);
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('collabora-transform', {
|
||||
category: 'nextcloud collabora',
|
||||
color: '#0082c9',
|
||||
defaults: {
|
||||
name: { value: '' },
|
||||
config: { type: 'nc-collab-config', required: true }
|
||||
},
|
||||
inputs: 1,
|
||||
outputs: 1,
|
||||
icon: 'font-awesome/fa-magic',
|
||||
label: function () {
|
||||
return this.name || 'transform structure';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-template-name="collabora-transform">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Namn</label>
|
||||
<input type="text" id="node-input-name">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-config"><i class="fa fa-cog"></i> Config</label>
|
||||
<input type="text" id="node-input-config">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="collabora-transform">
|
||||
<p><code>/cool/transform-document-structure</code></p>
|
||||
<p>Ingång: källa via <code>msg.ncPath</code> eller <code>msg.payload</code>, plus <code>msg.transform</code> (JSON).</p>
|
||||
</script>
|
||||
@@ -0,0 +1,46 @@
|
||||
'use strict';
|
||||
|
||||
const { getConfig, setError, setSuccess, clearStatus, pickMsgField } = require('../lib/utils');
|
||||
const { transformStructure } = require('../lib/collabora');
|
||||
const { loadSourceBuffer } = require('../lib/bridge');
|
||||
|
||||
module.exports = function (RED) {
|
||||
function CollaboraTransformNode(config) {
|
||||
RED.nodes.createNode(this, config);
|
||||
const node = this;
|
||||
node.configNode = RED.nodes.getNode(config.config);
|
||||
|
||||
node.on('input', async (msg, send, done) => {
|
||||
send = send || function () { node.send.apply(node, arguments); };
|
||||
done = done || function (err) { if (err) { node.error(err, msg); } };
|
||||
clearStatus(node);
|
||||
|
||||
try {
|
||||
const cfg = getConfig(node);
|
||||
const transform = pickMsgField(msg, 'transform') || msg.fields;
|
||||
if (!transform) {
|
||||
throw new Error('Ange msg.transform eller msg.fields');
|
||||
}
|
||||
|
||||
const source = await loadSourceBuffer(cfg, msg);
|
||||
const result = await transformStructure(cfg, {
|
||||
buffer: source.buffer,
|
||||
filename: source.filename,
|
||||
transform
|
||||
});
|
||||
|
||||
msg.payload = result.buffer;
|
||||
msg.contentType = result.contentType;
|
||||
setSuccess(node, msg, 'transform');
|
||||
send(msg);
|
||||
done();
|
||||
} catch (err) {
|
||||
setError(node, msg, err);
|
||||
send(msg);
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
RED.nodes.registerType('collabora-transform', CollaboraTransformNode);
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('collabora-wopi', {
|
||||
category: 'nextcloud collabora',
|
||||
color: '#0082c9',
|
||||
defaults: {
|
||||
name: { value: '' },
|
||||
config: { type: 'nc-collab-config', required: true },
|
||||
operation: { value: 'capabilities' }
|
||||
},
|
||||
inputs: 1,
|
||||
outputs: 1,
|
||||
icon: 'font-awesome/fa-search',
|
||||
label: function () {
|
||||
return this.name || `wopi ${this.operation}`;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-template-name="collabora-wopi">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Namn</label>
|
||||
<input type="text" id="node-input-name">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-config"><i class="fa fa-cog"></i> Config</label>
|
||||
<input type="text" id="node-input-config">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-operation"><i class="fa fa-wrench"></i> Operation</label>
|
||||
<select id="node-input-operation">
|
||||
<option value="capabilities">capabilities</option>
|
||||
<option value="discovery">discovery</option>
|
||||
<option value="buildEditor">buildEditor</option>
|
||||
<option value="checkFileInfo">checkFileInfo (debug)</option>
|
||||
<option value="getFile">getFile (debug)</option>
|
||||
</select>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="collabora-wopi">
|
||||
<p>WOPI discovery/capabilities och editor-URL. Agenten ska nästan aldrig PutFile — använd WebDAV PUT.</p>
|
||||
<p><code>buildEditor</code>: <code>msg.wopiSrc</code> + <code>msg.token</code> → <code>msg.editorUrl</code>.</p>
|
||||
</script>
|
||||
@@ -0,0 +1,69 @@
|
||||
'use strict';
|
||||
|
||||
const { getConfig, setError, setSuccess, clearStatus } = require('../lib/utils');
|
||||
const { getCapabilities, getDiscovery } = require('../lib/collabora');
|
||||
const { requestBuffer } = require('../lib/http-client');
|
||||
|
||||
module.exports = function (RED) {
|
||||
function CollaboraWopiNode(config) {
|
||||
RED.nodes.createNode(this, config);
|
||||
const node = this;
|
||||
node.configNode = RED.nodes.getNode(config.config);
|
||||
node.operation = config.operation || 'capabilities';
|
||||
|
||||
node.on('input', async (msg, send, done) => {
|
||||
send = send || function () { node.send.apply(node, arguments); };
|
||||
done = done || function (err) { if (err) { node.error(err, msg); } };
|
||||
clearStatus(node);
|
||||
|
||||
try {
|
||||
const cfg = getConfig(node);
|
||||
const op = msg.operation || node.operation;
|
||||
|
||||
if (op === 'capabilities') {
|
||||
msg.payload = await getCapabilities(cfg);
|
||||
} else if (op === 'discovery') {
|
||||
msg.payload = await getDiscovery(cfg);
|
||||
} else if (op === 'buildEditor') {
|
||||
const publicBase = cfg.collaboraPublicUrl || cfg.collaboraUrl;
|
||||
const wopiSrc = encodeURIComponent(msg.wopiSrc || msg.WOPISrc || '');
|
||||
const token = encodeURIComponent(msg.token || msg.access_token || '');
|
||||
if (!wopiSrc || !token) {
|
||||
throw new Error('buildEditor kräver msg.wopiSrc och msg.token');
|
||||
}
|
||||
msg.editorUrl = `${publicBase}/browser/dist/cool.html?WOPISrc=${wopiSrc}&access_token=${token}`;
|
||||
msg.payload = { editorUrl: msg.editorUrl };
|
||||
} else if (op === 'checkFileInfo' || op === 'getFile') {
|
||||
const wopiUrl = msg.wopiUrl;
|
||||
if (!wopiUrl) {
|
||||
throw new Error(`${op} kräver msg.wopiUrl (felsökning)`);
|
||||
}
|
||||
const token = msg.token || msg.access_token || '';
|
||||
const res = await requestBuffer({
|
||||
url: wopiUrl,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'X-WOPI-Override': op === 'getFile' ? 'GET' : undefined
|
||||
},
|
||||
rejectUnauthorized: cfg.verifyTls !== false
|
||||
});
|
||||
msg.statusCode = res.statusCode;
|
||||
msg.payload = op === 'getFile' ? res.body : JSON.parse(res.body.toString('utf8'));
|
||||
} else {
|
||||
throw new Error(`Okänd wopi-operation: ${op}`);
|
||||
}
|
||||
|
||||
setSuccess(node, msg, op);
|
||||
send(msg);
|
||||
done();
|
||||
} catch (err) {
|
||||
setError(node, msg, err);
|
||||
send(msg);
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
RED.nodes.registerType('collabora-wopi', CollaboraWopiNode);
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"nextcloud-collabora": {
|
||||
"nextcloud collabora": "Nextcloud Collabora"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('nc-collab-config', {
|
||||
category: 'config',
|
||||
defaults: {
|
||||
name: { value: '' },
|
||||
nextcloudUrl: { value: 'https://cloud.hannesahlin.se', required: true },
|
||||
collaboraUrl: { value: 'http://host.docker.internal:9980', required: true },
|
||||
collaboraPublicUrl: { value: '' },
|
||||
username: { value: '', required: true },
|
||||
verifyTls: { value: false }
|
||||
},
|
||||
credentials: {
|
||||
password: { type: 'password' }
|
||||
},
|
||||
label: function () {
|
||||
return this.name || `NC Collab (${this.username || '?'})`;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-template-name="nc-collab-config">
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-name"><i class="fa fa-tag"></i> Namn</label>
|
||||
<input type="text" id="node-config-input-name" placeholder="t.ex. cloud.hannesahlin.se">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-nextcloudUrl"><i class="fa fa-cloud"></i> Nextcloud URL</label>
|
||||
<input type="text" id="node-config-input-nextcloudUrl">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-collaboraUrl"><i class="fa fa-server"></i> Collabora intern URL</label>
|
||||
<input type="text" id="node-config-input-collaboraUrl">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-collaboraPublicUrl"><i class="fa fa-globe"></i> Collabora publik URL (valfri)</label>
|
||||
<input type="text" id="node-config-input-collaboraPublicUrl">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-username"><i class="fa fa-user"></i> Användare</label>
|
||||
<input type="text" id="node-config-input-username">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-password"><i class="fa fa-key"></i> App-lösenord</label>
|
||||
<input type="password" id="node-config-input-password">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-verifyTls"><i class="fa fa-lock"></i> Verifiera TLS</label>
|
||||
<input type="checkbox" id="node-config-input-verifyTls" style="width:auto">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="nc-collab-config">
|
||||
<p>En instans per miljö. Använd <strong>app-lösenord</strong>, aldrig vanligt kontolösen.</p>
|
||||
<p>Fält: <code>nextcloudUrl</code>, <code>collaboraUrl</code>, <code>username</code>, app-lösen, <code>verifyTls</code>.</p>
|
||||
</script>
|
||||
@@ -0,0 +1,19 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function (RED) {
|
||||
function NcCollabConfigNode(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
this.nextcloudUrl = (n.nextcloudUrl || '').replace(/\/$/, '');
|
||||
this.collaboraUrl = (n.collaboraUrl || '').replace(/\/$/, '');
|
||||
this.collaboraPublicUrl = (n.collaboraPublicUrl || '').replace(/\/$/, '');
|
||||
this.username = n.username || '';
|
||||
this.verifyTls = n.verifyTls !== false && n.verifyTls !== 'false';
|
||||
this.password = this.credentials.password || '';
|
||||
}
|
||||
|
||||
RED.nodes.registerType('nc-collab-config', NcCollabConfigNode, {
|
||||
credentials: {
|
||||
password: { type: 'password' }
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('nc-office', {
|
||||
category: 'nextcloud collabora',
|
||||
color: '#46ba61',
|
||||
defaults: {
|
||||
name: { value: '' },
|
||||
config: { type: 'nc-collab-config', required: true },
|
||||
operation: { value: 'templates:list' }
|
||||
},
|
||||
inputs: 1,
|
||||
outputs: 1,
|
||||
icon: 'font-awesome/fa-file-text-o',
|
||||
label: function () {
|
||||
return this.name || `nc-office ${this.operation}`;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-template-name="nc-office">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Namn</label>
|
||||
<input type="text" id="node-input-name">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-config"><i class="fa fa-cog"></i> Config</label>
|
||||
<input type="text" id="node-input-config">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-operation"><i class="fa fa-wrench"></i> Operation</label>
|
||||
<select id="node-input-operation">
|
||||
<option value="templates:list">templates:list</option>
|
||||
<option value="templates:create">templates:create</option>
|
||||
<option value="template:extractFields">template:extractFields</option>
|
||||
<option value="template:fillFields">template:fillFields</option>
|
||||
<option value="document:direct">document:direct</option>
|
||||
<option value="document:public">document:public</option>
|
||||
<option value="files:convert">files:convert</option>
|
||||
<option value="assets:create">assets:create</option>
|
||||
</select>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/html" data-help-name="nc-office">
|
||||
<p>Richdocuments OCS — mallar, fält, direkt-URL, assets.</p>
|
||||
<p>Överstyr via <code>msg.operation</code>. Gemensamma fält: <code>ncPath</code>, <code>fileId</code>, <code>destNcPath</code>, <code>fields</code>.</p>
|
||||
</script>
|
||||
@@ -0,0 +1,146 @@
|
||||
'use strict';
|
||||
|
||||
const { getConfig, setError, setSuccess, clearStatus, pickMsgField } = require('../lib/utils');
|
||||
const { ocsRequest } = require('../lib/ocs');
|
||||
const bridge = require('../lib/bridge');
|
||||
const { resolveFileId } = bridge;
|
||||
const { webdavGet, webdavPut } = require('../lib/webdav');
|
||||
const { convertTo } = require('../lib/collabora');
|
||||
const {
|
||||
resolveDestNcPath,
|
||||
assertSafeDestination,
|
||||
contentTypeForFormat,
|
||||
normalizeNcPath
|
||||
} = require('../lib/utils');
|
||||
|
||||
module.exports = function (RED) {
|
||||
function NcOfficeNode(config) {
|
||||
RED.nodes.createNode(this, config);
|
||||
const node = this;
|
||||
node.configNode = RED.nodes.getNode(config.config);
|
||||
node.operation = config.operation || 'templates:list';
|
||||
|
||||
node.on('input', async (msg, send, done) => {
|
||||
send = send || function () { node.send.apply(node, arguments); };
|
||||
done = done || function (err) { if (err) { node.error(err, msg); } };
|
||||
clearStatus(node);
|
||||
|
||||
try {
|
||||
const cfg = getConfig(node);
|
||||
const op = msg.operation || node.operation;
|
||||
let result;
|
||||
|
||||
switch (op) {
|
||||
case 'templates:list': {
|
||||
const type = pickMsgField(msg, 'templateType') || 'document';
|
||||
result = await ocsRequest(cfg, {
|
||||
method: 'GET',
|
||||
path: `/ocs/v2.php/apps/richdocuments/api/v1/templates/${type}`
|
||||
});
|
||||
msg.payload = result.data;
|
||||
break;
|
||||
}
|
||||
case 'templates:create': {
|
||||
result = await ocsRequest(cfg, {
|
||||
method: 'POST',
|
||||
path: '/ocs/v2.php/apps/richdocuments/api/v1/templates/new',
|
||||
body: {
|
||||
templateId: msg.templateId,
|
||||
fileName: msg.fileName || msg.destName,
|
||||
templatePath: msg.templatePath
|
||||
}
|
||||
});
|
||||
msg.payload = result.data;
|
||||
break;
|
||||
}
|
||||
case 'template:extractFields': {
|
||||
const fileId = await resolveFileId(cfg, msg);
|
||||
result = await ocsRequest(cfg, {
|
||||
method: 'GET',
|
||||
path: `/ocs/v2.php/apps/richdocuments/api/v1/template/fields/extract/${fileId}`
|
||||
});
|
||||
msg.fileId = fileId;
|
||||
msg.payload = result.data;
|
||||
break;
|
||||
}
|
||||
case 'template:fillFields': {
|
||||
const fileId = await resolveFileId(cfg, msg);
|
||||
const fields = msg.fields || msg.transform;
|
||||
const body = { fields };
|
||||
if (msg.destNcPath) {
|
||||
body.destination = normalizeNcPath(msg.destNcPath).replace(/^\//, '');
|
||||
}
|
||||
result = await ocsRequest(cfg, {
|
||||
method: 'POST',
|
||||
path: `/ocs/v2.php/apps/richdocuments/api/v1/template/fields/fill/${fileId}`,
|
||||
body
|
||||
});
|
||||
msg.fileId = fileId;
|
||||
msg.payload = result.data;
|
||||
break;
|
||||
}
|
||||
case 'document:direct': {
|
||||
const fileId = await resolveFileId(cfg, msg);
|
||||
result = await ocsRequest(cfg, {
|
||||
method: 'POST',
|
||||
path: '/ocs/v2.php/apps/richdocuments/api/v1/document',
|
||||
body: { fileid: fileId }
|
||||
});
|
||||
msg.fileId = fileId;
|
||||
msg.editorUrl = result.data?.url || result.data;
|
||||
msg.payload = { url: msg.editorUrl, fileId };
|
||||
break;
|
||||
}
|
||||
case 'document:public': {
|
||||
if (!msg.shareToken) {
|
||||
throw new Error('document:public kräver msg.shareToken');
|
||||
}
|
||||
result = await ocsRequest(cfg, {
|
||||
method: 'POST',
|
||||
path: '/ocs/v2.php/apps/richdocuments/api/v1/document',
|
||||
body: { shareToken: msg.shareToken }
|
||||
});
|
||||
msg.editorUrl = result.data?.url || result.data;
|
||||
msg.payload = { url: msg.editorUrl };
|
||||
break;
|
||||
}
|
||||
case 'files:convert': {
|
||||
const format = (pickMsgField(msg, 'format') || 'pdf').toLowerCase();
|
||||
const source = msg.ncPath
|
||||
? await webdavGet(cfg, msg.ncPath)
|
||||
: { buffer: Buffer.from(msg.payload), filename: msg.filename || 'file.bin' };
|
||||
const converted = await convertTo(cfg, {
|
||||
buffer: source.buffer,
|
||||
filename: source.filename,
|
||||
format
|
||||
});
|
||||
const dest = assertSafeDestination(resolveDestNcPath(msg, msg.ncPath, format), msg);
|
||||
const put = await webdavPut(cfg, dest, converted.buffer, contentTypeForFormat(format));
|
||||
msg.destNcPath = put.destNcPath;
|
||||
msg.payload = converted.buffer;
|
||||
msg.format = format;
|
||||
break;
|
||||
}
|
||||
case 'assets:create': {
|
||||
const link = await bridge.createAssetLink(cfg, msg);
|
||||
msg.assetUrl = link.assetUrl;
|
||||
msg.payload = { assetUrl: link.assetUrl, ncPath: link.ncPath };
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Error(`Okänd nc-office operation: ${op}`);
|
||||
}
|
||||
|
||||
setSuccess(node, msg, op);
|
||||
send(msg);
|
||||
done();
|
||||
} catch (err) {
|
||||
setError(node, msg, err);
|
||||
send(msg);
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
RED.nodes.registerType('nc-office', NcOfficeNode);
|
||||
};
|
||||
Reference in New Issue
Block a user