Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create initial campaign editor #42

Closed
wants to merge 12 commits into from
233 changes: 233 additions & 0 deletions campaigns.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>Commons Mobile App Campaign Editor</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" integrity="sha384-WskhaSGFgHYWDcbwN70/dfYBj47jz9qbsMId/iRN3ewGhXQFZCSftd1LZCfmhktB" crossorigin="anonymous">
<style>
.campaign {
border: 1px solid #ced4da;
border-radius: .25rem;
background: #fafafa;
margin-left: 0;
margin-right: 0;
}
</style>
</head>

<body>
<div class="container my-4">
<h1 class="my-4">Campaign editor</h1>

<section class="my-2">
<h2>Campaigns</h2>
<form oninput="generateJSON()" onchange="generateJSON()" id="form"></form>
<div class="btn-group mb-4 mt-3" role="group" style="flex-wrap: wrap;">
<button onclick="addSection()" type="button" class="btn btn-outline-primary">Add</button>
<button id="removeSectionButton" onclick="removeSection()" type="button" class="btn btn-outline-primary">Remove</button>
<div class="btn-group" role="group">
<button type="button" class="btn btn-outline-primary dropdown-toggle" data-toggle="dropdown">Import</button>
<div class="dropdown-menu">
<button onclick="overwriteConfirm(importFromGithub)" type="button" class="btn btn-outline-primary dropdown-item">Live version</button>
<button onclick="overwriteConfirm(importFromPaste)" type="button" class="btn btn-outline-primary dropdown-item">From paste</button>
</div>
</div>
</div>
</section>

<section class="my-2">
<h2>Config</h2>
<form class="pb-4" oninput="generateJSON()" onchange="generateJSON()">
<div class="form-check my-2">
<input type="checkbox" class="form-check-input" id="showOnlyLiveCampaigns">
<label class="form-check-label" for="showOnlyLiveCampaigns">Show only live campaigns</label>
</div>
<div class="form-group my-2">
<label for="sortBy">Sort by</label>
<input type="text" class="form-control" id="sortBy" placeholder="title/startDate/endDate">
</div>
</form>
</section>

<section class="my-4">
<h2>Result</h2>
<p id="resultValidity"></p>
<pre class="form-control" id="result"></pre>
<div class="btn-group" role="group">
<button onclick="copyToClipboard(result.innerText);this.innerText='Copied';setTimeout(() => {this.innerText='Copy to clipboard'}, 750);" type="button" class="btn btn-outline-primary">Copy to clipboard</button>
<a href="https://github.com/commons-app/campaigns/edit/master/campaigns.json" target="blank" role="button" class="btn btn-outline-primary">Edit on GitHub</a>
</div>
</section>
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js" integrity="sha384-tsQFqpEReu7ZLhBV2VZlAu7zcOV+rXbYlF2cqB8txI/8aZajjp4Bqd+V6D5IgvKT" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js" integrity="sha384-smHYKdLADwkXOn1EmN1qk/HfnUcbVRZyYmZ4qpPea6sjB/pTJ0euyQp0Mk8ck+5T" crossorigin="anonymous"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/ajv/6.10.0/ajv.min.js" integrity="sha256-KvJf+3L+RQpJ3LEBMcU2Yh29dlCt1CHbxKhwLtJUb94=" crossorigin="anonymous"></script>

<script>
const form = document.getElementById('form');
const result = document.getElementById('result');
const resultValidity = document.getElementById('resultValidity');
let campaignCounter = 0;
let validate = null;
fetch('https://raw.githubusercontent.com/commons-app/campaigns/master/schema.json')
.then(res => res.json())
.then(schema => validate = (new Ajv()).compile(schema))
.then(() => generateJSON());
addSection();

function addSection() {
const section = document.createElement("div");
const n = campaignCounter++;
section.innerHTML =
`<div class="form-group col-md-6">
<label for="title${n}">Title</label>
<input type="text" class="form-control" id="title${n}" placeholder="Wiki Loves Monuments">
</div>
<div class="form-group col-md-6">
<label for="description${n}">Description</label>
<div class="input-group">
<input type="text" class="form-control" id="description${n}" placeholder="Wiki Loves Monuments">
<div class="input-group-append">
<button onclick="description${n}.value = title${n}.value" type="button" class="btn btn-outline-primary input-group-text">Copy title</button>
</div>
</div>
</div>
<div class="form-group col-sm-6 col-md-3">
<label for="startDate${n}">Start date</label>
<input type="date" class="form-control" id="startDate${n}" placeholder="2018-09-01">
</div>
<div class="form-group col-sm-6 col-md-3">
<label for="endDate${n}">End date</label>
<input type="date" class="form-control" id="endDate${n}" placeholder="2018-09-01">
</div>
<div class="form-group col-md-6">
<label for="link${n}">Link</label>
<input type="url" class="form-control" id="link${n}" placeholder="https://www.wikilovesmonuments.org">
</div>
<div class="form-group col-md-12">
<button onclick="removeSection(${n})" type="button" class="btn btn-outline-danger btn-block">Remove</button>
</div>`;
section.classList.add("mt-3", "pt-3", "pb-2", "row", "campaign");
section.id = `campaign${n}`;
form.appendChild(section);
document.getElementById('removeSectionButton').disabled = false;
}

function removeSection(n) {
if (n == undefined) {
if (form.childElementCount == 0) throw new Error("No sections to remove");
form.removeChild(form.children[form.childElementCount - 1]);
} else {
form.removeChild(document.getElementById(`campaign${n}`));
}

generateJSON();
if (form.childElementCount == 0) document.getElementById('removeSectionButton').disabled = true;
}

function generateJSON() {
const campaigns = [...form.children]
.map(e => e.id.slice(-1))
.map(n => ({
"title": document.getElementById(`title${n}`).value,
"description": document.getElementById(`description${n}`).value,
"startDate": document.getElementById(`startDate${n}`).value,
"endDate": document.getElementById(`endDate${n}`).value,
"link": document.getElementById(`link${n}`).value
})).filter(campaign => campaign.title.trim().length != 0);

const config = {
"showOnlyLiveCampaigns": document.getElementById('showOnlyLiveCampaigns').checked,
"sortBy": document.getElementById('sortBy').value.length == 0 ? 'title/startDate/endDate' : document.getElementById('sortBy').value
}

resultObj = {
config: config,
campaigns: campaigns
}

// Validate JSON
if (validate != null) {
const isValid = validate(resultObj);
result.style.backgroundColor = isValid ? 'rgba(0,255,0,0.1)' : 'rgba(255,0,0,0.2)';
resultValidity.innerText = isValid ?
'JSON valid ✔️' :
`JSON invalid ❌${validate.errors.map(error => `\n${error.dataPath} ${error.message}`)}`;
} else {
result.style.removeProperty('backgroundColor');
resultValidity.innerText = '';
}

// Display JSON
result.innerHTML = JSON.stringify(resultObj, null, '\t');
}

function copyToClipboard(str) {
const textarea = document.createElement('textarea');
textarea.value = str;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
}

function overwriteConfirm(f) {
if(confirm("Warning: importing will overwrite the current data. Do you want to proceed?")) f();
}

function importFromPaste() {
const result = prompt("Paste in campaigns.json");
if (result != null && result.trim() != "") importFromText(result);
}

function importFromGithub() {
fetch('https://raw.githubusercontent.com/commons-app/campaigns/master/campaigns.json')
.then(res => res.text())
.then(importFromText);
}

function importFromText(data) {
let parsed;

try {
parsed = JSON.parse(data);
} catch (err) {
alert("Could not parse JSON for import, see console for details");
console.error(err);
return;
}

if (validate == null) {
alert("Validation function not instantiated. Please try again in a few seconds.");
}

if (!validate(parsed)) {
alert("Imported JSON does not comply with schema, see console for details");
console.error(validate.error);
return;
}

document.getElementById('showOnlyLiveCampaigns').checked = parsed.config.showOnlyLiveCampaigns;
document.getElementById('sortBy').value = parsed.config.sortBy;

form.innerHTML = "";
parsed.campaigns.forEach(campaign => {
const n = campaignCounter;
addSection();
document.getElementById(`title${n}`).value = campaign.title;
document.getElementById(`description${n}`).value = campaign.description;
document.getElementById(`startDate${n}`).value = campaign.startDate;
document.getElementById(`endDate${n}`).value = campaign.endDate;
document.getElementById(`link${n}`).value = campaign.link;
});

generateJSON();
}
</script>
</body>
</html>