mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-11-26 21:56:39 +00:00
CSV import/export, UI/UX improvements to import-export interface
This commit is contained in:
parent
3960327a43
commit
c80786014c
|
@ -19,6 +19,7 @@
|
||||||
"langs": "^2.0.0",
|
"langs": "^2.0.0",
|
||||||
"match-sorter": "^6.3.1",
|
"match-sorter": "^6.3.1",
|
||||||
"modern-normalize": "^1.1.0",
|
"modern-normalize": "^1.1.0",
|
||||||
|
"papaparse": "^5.3.2",
|
||||||
"photoswipe": "^5.3.3",
|
"photoswipe": "^5.3.3",
|
||||||
"photoswipe-dynamic-caption-plugin": "^1.2.7",
|
"photoswipe-dynamic-caption-plugin": "^1.2.7",
|
||||||
"photoswipe-video-plugin": "^1.0.2",
|
"photoswipe-video-plugin": "^1.0.2",
|
||||||
|
|
64
web/source/settings/admin/federation/export-format-table.jsx
Normal file
64
web/source/settings/admin/federation/export-format-table.jsx
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
GoToSocial
|
||||||
|
Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const React = require("react");
|
||||||
|
|
||||||
|
module.exports = function ExportFormatTable() {
|
||||||
|
return (
|
||||||
|
<table className="export-format-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th rowSpan={2} />
|
||||||
|
<th colSpan={2}>Includes</th>
|
||||||
|
<th colSpan={2}>Importable by</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Domain</th>
|
||||||
|
<th>Public comment</th>
|
||||||
|
<th>GoToSocial</th>
|
||||||
|
<th>Mastodon</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<Format name="Text" info={[true, false, true, false]} />
|
||||||
|
<Format name="JSON" info={[true, true, true, false]} />
|
||||||
|
<Format name="CSV" info={[true, true, true, true]} />
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
function Format({ name, info }) {
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
<td><b>{name}</b></td>
|
||||||
|
{info.map((b, key) => <td key={key} className="bool">{bool(b)}</td>)}
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function bool(val) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<i className={`fa fa-${val ? "check" : "times"}`} aria-hidden="true"></i>
|
||||||
|
<span className="sr-only">{val ? "Yes" : "No"}</span>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -45,6 +45,7 @@ const MutationButton = require("../../components/form/mutation-button");
|
||||||
const isValidDomain = require("is-valid-domain");
|
const isValidDomain = require("is-valid-domain");
|
||||||
const FormWithData = require("../../lib/form/form-with-data");
|
const FormWithData = require("../../lib/form/form-with-data");
|
||||||
const { Error } = require("../../components/error");
|
const { Error } = require("../../components/error");
|
||||||
|
const ExportFormatTable = require("./export-format-table");
|
||||||
|
|
||||||
const baseUrl = "/settings/admin/federation/import-export";
|
const baseUrl = "/settings/admin/federation/import-export";
|
||||||
|
|
||||||
|
@ -104,39 +105,55 @@ module.exports = function ImportExport() {
|
||||||
<Route>
|
<Route>
|
||||||
{parseResult.isSuccess && <Redirect to={`${baseUrl}/list`} />}
|
{parseResult.isSuccess && <Redirect to={`${baseUrl}/list`} />}
|
||||||
<h2>Import / Export suspended domains</h2>
|
<h2>Import / Export suspended domains</h2>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<form onSubmit={submitParse}>
|
<p>
|
||||||
<TextArea
|
This page can be used to import and export lists of domains to suspend.
|
||||||
field={form.domains}
|
Exports can be done in various formats, with varying functionality and support in other software.
|
||||||
label="Domains, one per line (plaintext) or JSON"
|
Imports will automatically detect what format is being processed.
|
||||||
placeholder={`google.com\nfacebook.com`}
|
</p>
|
||||||
rows={8}
|
<ExportFormatTable />
|
||||||
/>
|
</div>
|
||||||
|
<div className="import-export">
|
||||||
|
<TextArea
|
||||||
|
field={form.domains}
|
||||||
|
label="Domains"
|
||||||
|
placeholder={`google.com\nfacebook.com`}
|
||||||
|
rows={8}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<MutationButton label="Import" result={parseResult} showError={false} />
|
<MutationButton label="Import" type="button" onClick={() => submitParse()} result={parseResult} showError={false} />
|
||||||
<button type="button" className="with-padding">
|
<MutationButton label="Export" type="button" onClick={() => submitExport("export")} result={exportResult} showError={false} />
|
||||||
<label>
|
</div>
|
||||||
Import file
|
|
||||||
<input className="hidden" type="file" onChange={fileChanged} accept="application/json,text/plain" />
|
<div className="row">
|
||||||
</label>
|
<button type="button" className="with-padding">
|
||||||
</button>
|
<label>
|
||||||
</div>
|
Import file
|
||||||
</form>
|
<input
|
||||||
<form onSubmit={submitExport}>
|
type="file"
|
||||||
<div className="row">
|
className="hidden"
|
||||||
<MutationButton name="export" label="Export" result={exportResult} showError={false} />
|
onChange={fileChanged}
|
||||||
<MutationButton name="export-file" label="Export file" result={exportResult} showError={false} />
|
accept="application/json,text/plain,text/csv"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</button>
|
||||||
|
<div className="export-file">
|
||||||
|
<MutationButton label="Export to file" type="button" onClick={() => submitExport("export-file")} result={exportResult} showError={false} />
|
||||||
|
<span>
|
||||||
|
as
|
||||||
|
</span>
|
||||||
<Select
|
<Select
|
||||||
field={form.exportType}
|
field={form.exportType}
|
||||||
options={<>
|
options={<>
|
||||||
<option value="plain">Text</option>
|
<option value="plain">Text</option>
|
||||||
<option value="json">JSON</option>
|
<option value="json">JSON</option>
|
||||||
|
<option value="csv">CSV</option>
|
||||||
</>}
|
</>}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</div>
|
||||||
|
|
||||||
{parseResult.error && <Error error={parseResult.error} />}
|
{parseResult.error && <Error error={parseResult.error} />}
|
||||||
{exportResult.error && <Error error={exportResult.error} />}
|
{exportResult.error && <Error error={exportResult.error} />}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
const Promise = require("bluebird");
|
const Promise = require("bluebird");
|
||||||
const isValidDomain = require("is-valid-domain");
|
const isValidDomain = require("is-valid-domain");
|
||||||
const fileDownload = require("js-file-download");
|
const fileDownload = require("js-file-download");
|
||||||
|
const csv = require("papaparse");
|
||||||
|
|
||||||
const {
|
const {
|
||||||
replaceCacheOnMutation,
|
replaceCacheOnMutation,
|
||||||
|
@ -31,6 +32,23 @@ const {
|
||||||
function parseDomainList(list) {
|
function parseDomainList(list) {
|
||||||
if (list[0] == "[") {
|
if (list[0] == "[") {
|
||||||
return JSON.parse(list);
|
return JSON.parse(list);
|
||||||
|
} else if (list.startsWith("#domain")) { // Mastodon CSV
|
||||||
|
const { data, errors } = csv.parse(list, {
|
||||||
|
header: true,
|
||||||
|
transformHeader: (header) => header.slice(1), // removes starting '#'
|
||||||
|
skipEmptyLines: true,
|
||||||
|
dynamicTyping: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (errors.length > 0) {
|
||||||
|
let error = "";
|
||||||
|
errors.forEach((err) => {
|
||||||
|
error += `${err.message} (line ${err.row})`;
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
} else {
|
} else {
|
||||||
return list.split("\n").map((line) => {
|
return list.split("\n").map((line) => {
|
||||||
let domain = line.trim();
|
let domain = line.trim();
|
||||||
|
@ -109,6 +127,9 @@ module.exports = (build) => ({
|
||||||
}).then((exportList) => {
|
}).then((exportList) => {
|
||||||
if (formData.exportType == "json") {
|
if (formData.exportType == "json") {
|
||||||
return JSON.stringify(exportList);
|
return JSON.stringify(exportList);
|
||||||
|
} else if (formData.exportType == "csv") {
|
||||||
|
let header = `#domain,#severity,#reject_media,#reject_reports,#public_comment,#obfuscate`;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
return exportList.join("\n");
|
return exportList.join("\n");
|
||||||
}
|
}
|
||||||
|
|
|
@ -712,6 +712,40 @@ button.with-padding {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.import-export {
|
||||||
|
.export-file {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.7rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.export-format-table {
|
||||||
|
background: $list-entry-alternate-bg;
|
||||||
|
border-collapse: collapse;
|
||||||
|
|
||||||
|
th, td {
|
||||||
|
border: 0.1rem solid $gray1;
|
||||||
|
padding: 0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
background: $list-entry-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.fa-check {
|
||||||
|
color: $green1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa-times {
|
||||||
|
color: $error3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.form-field.radio {
|
.form-field.radio {
|
||||||
&, label {
|
&, label {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -4117,6 +4117,11 @@ pako@~1.0.5:
|
||||||
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
|
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
|
||||||
integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
|
integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
|
||||||
|
|
||||||
|
papaparse@^5.3.2:
|
||||||
|
version "5.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/papaparse/-/papaparse-5.3.2.tgz#d1abed498a0ee299f103130a6109720404fbd467"
|
||||||
|
integrity sha512-6dNZu0Ki+gyV0eBsFKJhYr+MdQYAzFUGlBMNj3GNrmHxmz1lfRa24CjFObPXtjcetlOv5Ad299MhIK0znp3afw==
|
||||||
|
|
||||||
parent-module@^1.0.0:
|
parent-module@^1.0.0:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
|
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
|
||||||
|
|
Loading…
Reference in a new issue