Initial import

This commit is contained in:
2026-06-04 11:13:34 +01:00
commit 563d9cc96d
143 changed files with 10572 additions and 0 deletions
Executable
+24
View File
@@ -0,0 +1,24 @@
<template>
<div>
</div>
</template>
<script>
import { ref } from 'vue/dist/vue.esm-bundler';
export default {
props: [
],
setup() {
return {
}
},
methods: {
},
mounted() {
}
}
</script>
Executable
+90
View File
@@ -0,0 +1,90 @@
<template>
<div :draggable="draggable" :class="dropclass" @dragover="drag_over" @dragstart="drag_start" @drop="drop" @dragenter="drag_enter" @dragleave="drag_leave">
<slot></slot>
</div>
</template>
<script>
import { ref } from 'vue/dist/vue.esm-bundler';
export default {
props: [
"draggable",
"droppable",
"object_id",
"object_type",
"callback",
"extra_data",
],
setup(props, context) {
return {
dropclass: ref(""),
content: ref(""),
}
},
methods: {
init() {
},
drag_over(e) {
e.preventDefault();
},
drag_start(e) {
if (this.draggable == "true") {
e.dataTransfer.setData("object_id", this.object_id);
e.dataTransfer.setData("object_type", this.object_type);
e.dataTransfer.setData("extra_data", this.extra_data);
}
},
drop(e) {
if (this.droppable == "true") {
this.dropclass = "";
e.preventDefault();
console.log(e.dataTransfer.getData("object_id"));
console.log(e.dataTransfer.getData("object_type"));
console.log(e.dataTransfer.getData("extra_data"));
console.log(this);
$.ajax({
url: this.callback,
method: 'POST',
data: {
dst_id: this.object_id,
dst_type: this.object_type,
dst_extra: this.extra_data,
src_id: e.dataTransfer.getData("object_id"),
src_type: e.dataTransfer.getData("object_type"),
src_extra: e.dataTransfer.getData("extra_data"),
copy: e.ctrlKey,
}
}).done(this.drop_complete);
}
},
drop_complete(data) {
//console.log(data);
window.location.reload();
},
drag_enter(e) {
if (this.droppable == "true") {
this.dropclass = "bg-warning-subtle";
}
},
drag_leave(e) {
if (this.droppable == "true") {
this.dropclass = "";
}
},
},
mounted() {
this.init();
}
}
</script>
+195
View File
@@ -0,0 +1,195 @@
<template>
<div class="btn-group">
<button class="btn btn-primary" @click="edit" title="Edit Document"><i class='fa fa-pencil'></i></button>
<div id="docedit" class="modal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Edit Document</h5>
<button @click="close" type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div v-if="loading" class="text-center">
<div class="spinner-border" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
<div v-else>
<div class="row">
<div class="col-lg-6 col-12">
<label for="internal_id">Order Number</label>
<div class="input-group">
<input @keyup.enter="save" @keyup.esc="close" @keyup="id_caps" type="text" class="form-control" name="internal_id" v-model="internal_id">
<button @click="guess_docid" class="btn btn-secondary" title="Guess document ID"><i class="fa fa-wand-magic-sparkles"></i></button>
</div>
<label for="title">Title</label>
<div class="input-group">
<input ref="rtitle" @keyup.enter="save" @keyup.esc="close" type="text" class="form-control" name="title" v-model="title">
<button @click="split_title" class="btn btn-secondary" title="Split down on cursor or selection"><i class="fa fa-i-cursor"></i></button>
</div>
<label for="subtitle">Subtitle</label>
<div class="input-group">
<input ref="rsubtitle" @keyup.enter="save" @keyup.esc="close" type="text" class="form-control" name="subtitle" v-model="subtitle">
<button @click="split_subtitle" class="btn btn-secondary" title="Split down on cursor or selection"><i class="fa fa-i-cursor"></i></button>
</div>
<label for="subsubtitle">Sub-subtitle</label>
<input @keyup.enter="save" @keyup.esc="close" type="text" class="form-control" name="subsubtitle" v-model="subsubtitle">
</div>
<div class="col-lg-6 col-12">
<label for="overview">Overview <small>(Markdown supported)</small></label>
<textarea @keyup.esc="close" name="overview" v-model="overview" class="form-control w-100" rows="5"></textarea>
<label for="products">Products</label>
<productselector v-model="products"></productselector>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button @click="close" type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button @click="save" type="button" class="btn btn-primary">Save changes</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { ref } from 'vue/dist/vue.esm-bundler';
export default {
setup() {
return {
loading: ref(true),
internal_id: ref(""),
title: ref(""),
subtitle: ref(""),
subsubtitle: ref(""),
overview: ref(""),
products: ref(""),
}
},
mounted() {
},
methods: {
edit() {
this.loading = true;
$('#docedit').show();
$.ajax({
url: "/api/document/" + this.docid,
method: "GET"
}).done(this.get_products);
},
get_products(data) {
this.internal_id = data.internal_id;
this.title = data.title;
this.subtitle = data.subtitle;
this.subsubtitle = data.subsubtitle;
this.overview = data.overview;
$.ajax({
url: "/api/document/" + this.docid + "/products",
method: "GET"
}).done(this.fill_products);
},
fill_products(data) {
this.products = "";
for (var i = 0; i < data.length; i++) {
if (this.products != "") {
this.products += ",";
}
this.products += data[i].id;
}
this.loading = false;
},
close() {
$('#docedit').hide();
},
save() {
$.ajax({
url: "/api/document/" + this.docid,
method: "POST",
data: {
internal_id: this.internal_id,
title: this.title,
subtitle: this.subtitle,
subsubtitle: this.subsubtitle,
overview: this.overview,
products: this.products,
}
}).done(this.refresh);
},
refresh() {
window.location.reload();
},
id_caps() {
this.internal_id = this.internal_id.toUpperCase();
this.internal_id = this.internal_id.replace("", "-");
},
get_caret_position(oField) {
// Initialize
var iCaretPos = 0;
if (document.selection) {
// Set focus on the element
oField.focus();
// To get cursor position, get empty selection range
var oSel = document.selection.createRange();
// Move selection start to 0 position
oSel.moveStart('character', -oField.value.length);
// The caret position is selection length
iCaretPos = oSel.text.length;
} else if (oField.selectionStart || oField.selectionStart == '0') {
iCaretPos = oField.selectionDirection=='backward' ? oField.selectionStart : oField.selectionEnd;
}
return iCaretPos;
},
split_title() {
var pos = this.get_caret_position(this.$refs.rtitle);
var l = this.title.substring(0, pos);
var r = this.title.substring(pos);
this.title = l;
this.subtitle = r;
console.log(pos);
},
split_subtitle() {
var pos = this.get_caret_position(this.$refs.rsubtitle);
var l = this.subtitle.substring(0, pos);
var r = this.subtitle.substring(pos);
this.subtitle = l;
this.subsubtitle = r;
console.log(pos);
},
guess_docid() {
$.ajax({
url: "/api/docid/" + this.docid,
method: "GET"
}).done(this.guessed_docid);
},
guessed_docid(d) {
this.internal_id = d.docid;
},
},
props: [
"docid",
],
}
</script>
+127
View File
@@ -0,0 +1,127 @@
<template>
<div>
<div class="progress {{pstyle}}" role="progressbar" aria-label="Download progress" style="--bs-progress-bar-transition: 0.01s;">
<div ref="pg" :class="pbstyle" :style="{'width': percent + '%'}">{{status}}</div>
</div>
<div class="text-truncate">
<small>{{srcurl}}</small>
</div>
<div v-if="failed" class="alert alert-danger">
The download has failed with error code <strong>{{errorcode}}</strong>. Please try again.
</div>
</div>
</template>
<script>
import { ref } from 'vue/dist/vue.esm-bundler'
export default {
emits: [
'complete',
'failure'
],
props: [
'jobid',
'url'
],
setup() {
return {
percent: ref(100),
pbstyle: ref(""),
pstyle: ref(""),
outfile: ref(""),
srcurl: ref(""),
failed: ref(false),
errorcode: ref(""),
status: ref(""),
}
},
methods: {
get_status() {
$.ajax({
url: "/api/download/" + this.jobid,
method: "get",
}) .fail(this.get_status_fail)
.done(this.got_status);
},
get_status_fail(data, status, xhr) {
this.errorcode = xhr;
this.failed = true;
},
got_status(data, status, xhr) {
this.update_bar(data);
if (data.done != data.size) {
setTimeout(this.get_status, 500);
} else {
if (data.size == 0) {
setTimeout(this.get_status, 2000);
}
}
},
submit(url) {
$.ajax({
url: "/api/download",
method: "put",
data: {
url: this.url
}
}) .fail(this.submit_fail)
.done(this.submitted);
},
submit_fail(data, status, xhr) {
this.errorcode = xhr;
this.failed = true;
this.$emit("failed");
},
submitted(data, status, xhr) {
this.update_bar(data);
setTimeout(this.get_status, 500);
},
update_bar(data) {
this.outfile = data.file;
this.srcurl = data.url;
if (data.started == 0) {
this.status = "Queued";
this.pbstyle = "progress-bar bg-secondary progress-bar-striped progress-bar-animated";
this.percent = 100;
return;
}
if (data.finished != 0) {
this.status = "Completed";
this.pbstyle = "progress-bar bg-success";
this.percent = 100;
this.$emit("complete");
let event = new CustomEvent("complete", { bubbles: true, detail: data });
document.dispatchEvent(event);
return;
}
if (data.size == 0) {
this.status = "Starting...";
} else {
this.percent = data.done / data.size * 100;
this.pbstyle = "progress-bar bg-primary";
this.status = Math.round(this.percent) + "%";
}
}
},
mounted() {
this.status = "Please wait...";
this.pbstyle = "progress-bar bg-secondary progress-bar-striped progress-bar-animated";
this.percent = 100;
if (this.jobid != "") {
this.get_status();
} else if (this.url != "") {
this.submit(url);
}
}
};
</script>
+76
View File
@@ -0,0 +1,76 @@
<template>
<div>
<div class="input-group">
<span class="input-group-text">Download PDF URL:</span>
<input type="text" class="form-control" v-model="url">
<button class="btn btn-primary" @click="add_download">Download</button>
</div>
<ul v-for="download in downloads" class="list-group">
<li class="list-group-item">
<div class="row">
<div class="col-12 text-truncate">
{{ download.url }}
</div>
<div class="col-3">
<download :jobid="download.id"></download>
</div>
</div>
</li>
</ul>
</div>
</template>
<script>
import { ref } from 'vue/dist/vue.esm-bundler';
export default {
props: [
],
setup() {
return {
downloads: ref([]),
busy: ref(false),
url: ref(""),
}
},
methods: {
init() {
this.update();
},
update() {
this.busy = true;
$.ajax({
url: "/api/downloads",
method: "GET"
}).done(this.got_update);
},
add_download() {
this.busy = true;
$.ajax({
url: "/api/downloads",
method: "PUT",
data: {
url: this.url
}
}).done(this.got_update);
},
got_update(data) {
this.downloads = data;
this.busy = false;
setTimeout(this.update, 10000);
},
},
mounted() {
this.init();
}
}
</script>
+249
View File
@@ -0,0 +1,249 @@
<template>
<div>
<div class="input-group">
<span class="input-group-text">Test String:</span>
<input @keyup="refresh" class="form-control" type="text" v-model="teststring">
<span class="input-group-text"><i class="fa fa-arrow-left mx-1"></i> Type here to test matches</span>
<button class="btn btn-success" @click="addnew">Add New Match</button>
</div>
<table class="table table-hover w-100">
<thead>
<tr>
<th>Weight</th>
<th>Regular Expression</th>
<th>Example</th>
<th>Test Result</th>
</tr>
</thead>
<tbody>
<tr v-for="match in matches" :class="matchtest(match.regex)" @click="edit_reg(match)">
<td>{{ match.weight }}</td>
<td>/^{{ match.regex }}$/</td>
<td>{{ match.example }}</td>
<td>{{ teststring != "" ? matchresult(match.regex, teststring) : matchresult(match.regex, match.example)}}</td>
</tr>
</tbody>
</table>
<div id="newreg" class="modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Add New ID Match</h5>
<button @click="addclose" type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" :disabled="saving"></button>
</div>
<div class="modal-body">
<label for="example">Example ID</label>
<input type="text" name="example" class="form-control" v-model="add_example" @keyup="do_add_test">
<label for="regex">Regular Expression</label>
<div class="input-group">
<span class="input-group-text">/^</span>
<input type="text" name="regex" class="form-control" v-model="add_regex" @keyup="do_add_test">
<span class="input-group-text">$/</span>
</div>
<label for="weight">Weight (lower is higher priority)</label>
<input type="text" name="weight" class="form-control" v-model="add_weight">
Test Result: {{ add_test }}
</div>
<div class="modal-footer">
<button @click="addclose" type="button" class="btn btn-secondary" data-bs-dismiss="modal" :disabled="saving">Close</button>
<button @click="addsave" type="button" class="btn btn-primary" :disabled="saving">
<span v-if="!saving">Save changes</span>
<span class="spinner-border spinner-border-sm" role="status" v-if="saving"></span>
<span v-if="saving" class="mx-1">Saving...</span>
</button>
</div>
</div>
</div>
</div>
<div id="editreg" class="modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Edit ID Match</h5>
<button @click="edit_close" type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" :disabled="saving"></button>
</div>
<div class="modal-body">
<label for="example">Example ID</label>
<input type="text" name="example" class="form-control" v-model="edit_example" @keyup="do_edit_test">
<label for="regex">Regular Expression</label>
<div class="input-group">
<span class="input-group-text">/^</span>
<input type="text" name="regex" class="form-control" v-model="edit_regex" @keyup="do_edit_test">
<span class="input-group-text">$/</span>
</div>
<label for="weight">Weight (lower is higher priority)</label>
<input type="text" name="weight" class="form-control" v-model="edit_weight">
Test Result: {{ edit_test }}
</div>
<div class="modal-footer">
<button @click="edit_delete" type="button" class="btn btn-danger" :disabled="saving">Delete</button>
<button @click="edit_close" type="button" class="btn btn-secondary" data-bs-dismiss="modal" :disabled="saving">Close</button>
<button @click="edit_save" type="button" class="btn btn-primary" :disabled="saving">
<span v-if="!saving">Save changes</span>
<span class="spinner-border spinner-border-sm" role="status" v-if="saving"></span>
<span v-if="saving" class="mx-1">Saving...</span>
</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { ref } from 'vue/dist/vue.esm-bundler';
export default {
props: [
],
setup() {
return {
matches: ref([]),
teststring: ref(""),
saving: ref(false),
add_example: ref(""),
add_regex: ref(""),
add_weight: ref(0),
add_test: ref(""),
edit_example: ref(""),
edit_regex: ref(""),
edit_weight: ref(0),
edit_test: ref(""),
edit_id: ref(0),
}
},
methods: {
init() {
this.update();
},
refresh() {
var m = this.matches;
this.matches = [];
this.matches = m;
},
update() {
$.ajax({
url: "/api/sys/idmatch",
method: "GET"
}).done(this.got_update);
},
got_update(data) {
this.matches = data;
},
matchresult(regex, example) {
try {
const re = new RegExp("^" + regex + "$");
var res = example.match(re);
if (res == null) {
return "FAILED";
}
return res[1];
} catch (E) {
return "Bad Syntax";
}
},
matchtest(regex) {
var teststring = this.teststring.trim();
if (teststring == "") return "";
if (this.matchresult(regex, teststring) != "FAILED") {
return "table-success";
}
return "table-danger";
},
addnew() {
this.add_example = "";
this.add_regex = "";
this.add_weight = 0;
$('#newreg').show();
},
do_add_test() {
this.add_test = this.matchresult(this.add_regex, this.add_example);
},
addclose() {
$('#newreg').hide();
},
addsave() {
$.ajax({
url: "/api/sys/idmatch",
method: "PUT",
data: {
example: this.add_example,
regex: this.add_regex,
weight: this.add_weight
}
}).done(this.got_update);
$('#newreg').hide();
},
edit_reg(m) {
this.edit_id = m.id;
this.edit_example = m.example;
this.edit_regex = m.regex;
this.edit_weight = m.weight;
$('#editreg').show();
},
edit_close() {
$('#editreg').hide();
},
do_edit_test() {
this.edit_test = this.matchresult(this.edit_regex, this.edit_example);
},
edit_save() {
$.ajax({
url: "/api/sys/idmatch/" + this.edit_id,
method: "POST",
data: {
example: this.edit_example,
regex: this.edit_regex,
weight: this.edit_weight
}
}).done(this.got_update);
$('#editreg').hide();
},
edit_delete() {
$.ajax({
url: "/api/sys/idmatch/" + this.edit_id,
method: "DELETE",
}).done(this.got_update);
$('#editreg').hide();
},
},
mounted() {
this.init();
}
}
</script>
+550
View File
@@ -0,0 +1,550 @@
<template>
<div>
<ul class="list-group">
<li v-for="i in imports" :class="fancyrow(i)">
<div class="row">
<div class="col-1">
<a v-if="i.revision != null" :href='"/cover/" + i.revision.id + "/cover.jpg"'><img class="w-100" :src='"/cover/" + i.revision.id + "/100/cover.jpg"'></a>
</div>
<div class="col-11">
<div class="row">
<div class="col-lg-2 col-6">
<strong v-if="i.imported > 0">Imported</strong>
<strong v-else-if="i.completed > 0 && i.revision">Completed</strong>
<strong v-else-if="i.completed > 0 && (!i.revision)">Failed</strong>
<strong v-else-if="i.started > 0">Processing</strong>
<strong v-else>Queued</strong>
</div>
<div class="col-lg-2 col-6">
<a v-if="i.revision != null" :href='"/revision/" + i.revision.id'>{{ i.revision.id }}</a>
</div>
<div class="col-lg-2 col-3">
{{ i.guessed_id }}
</div>
<div class="col-lg-2 col-3">
{{ i.guessed_revno }}
</div>
<div class="col-lg-2 col-6">
{{ i.guessed_month }}/{{ i.guessed_year }}
</div>
</div>
<div class="row">
<div class="col-9 text-truncate">
{{ i.guessed_title }}
</div>
<div class="col-lg-3 col-12 btn-group">
<button :disabled="busy" class="btn btn-danger" @click="del(i)"><i class="fa-solid fa-trash"></i></button>
<button :disabled="busy || (i.revision == null)" class="btn btn-secondary" @click="download(i)"><i class="fa-solid fa-download"></i></button>
<button :disabled="(i.started == 0) || busy" class="btn btn-primary" @click="restart(i)"><i class="fa-solid fa-rotate"></i></button>
<button class="btn btn-success" :disabled="(i.completed == 0 || (!i.revision)) || busy" @click="import_rev(i)"><i class="fa-solid fa-file-import"></i></button>
</div>
</div>
<div class="row">
<div class="col-12">
{{ i.origfile }}
</div>
</div>
</div>
</div>
</li>
</ul>
<div id="docedit" class="modal" tabindex="-1">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Create Document</h5>
<button @click="close" type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" :disabled="saving"></button>
</div>
<div class="modal-body">
<div class="row">
<div class="col-12 alert alert-info">
{{ i_origfile }}
</div>
</div>
<pdf :rev="i_revision"></pdf>
<div class="row">
<div class="col-lg-6 col-12">
<label for="i_id">Order Number</label>
<div class="input-group">
<input @blur="upper_id" @keyup.enter="save" ref="orderno" @keyup.esc="close" type="text" @keyup="find_doc" :class="'form-control' + ((i_id.length <= 25) ? ' is-valid' : ' is-invalid')" name="i_id" v-model="i_id" :disabled="saving">
<button class="btn btn-secondary" @click="paste_id"><i class="fa-solid fa-paste"></i></button>
</div>
<div v-if="newdoc" class="alert alert-warning">
A new document will be created with this order number.
</div>
<div v-else class="alert alert-success">
This PDF will be added to {{ existing_doc.title }} {{ existing_doc.subtitle }} {{ existing_doc.subsubtitle }} as a new revision.
</div>
<div v-if="newdoc">
<label for="i_title">Title</label>
<div class="input-group">
<input ref="rtitle" @keyup.enter="save" @keyup.esc="close" @keyup="try_title" type="text" :class="'form-control' + (i_title == '' ? ' is-invalid' : ' is-valid')" name="i_title" v-model="i_title" :disabled="saving">
<button class="btn btn-secondary" @click="split_title" title="Split down on cursor position"><i class='fa fa-i-cursor'></i></button>
</div>
<label for="i_subtitle">Subtitle</label>
<div class="input-group">
<input ref="rsubtitle" @keyup.enter="save" @keyup.esc="close" @keyup="try_subtitle" type="text" class="form-control" name="i_subtitle" v-model="i_subtitle" :disabled="saving">
<button class="btn btn-secondary" @click="split_subtitle" title="Split down on cursor position"><i class='fa fa-i-cursor'></i></button>
</div>
<label for="i_subsubtitle">Sub-subtitle</label>
<input @keyup.enter="save" @keyup.esc="close" @keyup="try_subsubtitle" type="text" class="form-control" name="i_subsubtitle" v-model="i_subsubtitle" :disabled="saving">
</div>
</div>
<div class="col-lg-6 col-12">
<label for="i_revno">Revision / Version Number</label>
<input @keyup.enter="save" @keyup.esc="close" type="text" class="form-control" name="i_revno" v-model="i_revno" :disabled="saving">
<div class="row">
<div class="col-6">
<label for="i_month">Month</label>
<input @keyup.enter="save" @keyup.esc="close" type="text" class="form-control" name="i_month" v-model="i_month" :disabled="saving">
</div>
<div class="col-6">
<label for="i_year">Year</label>
<input @keyup.enter="save" @keyup.esc="close" type="text" class="form-control" name="i_year" v-model="i_year" :disabled="saving">
</div>
</div>
<div v-if="newdoc">
<label for="i_overview">Overview <small>(Markdown supported)</small></label>
<textarea @keyup.esc="close" name="i_overview" v-model="i_overview" class="form-control w-100" rows="5" :disabled="saving"></textarea>
<label for="i_products">Products</label>
<productselector id="ps" v-model="i_products"></productselector>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<textarea editable="false" v-model="i_covertext" class="form-control"></textarea>
</div>
</div>
</div>
<div class="modal-footer">
<button @click="close" type="button" class="btn btn-secondary" data-bs-dismiss="modal" :disabled="saving">Close</button>
<button @click="save" type="button" class="btn btn-primary" :disabled="saving || (newdoc && ((i_products == '') || (i_title == '')))">
<span v-if="!saving">Save changes</span>
<span class="spinner-border spinner-border-sm" role="status" v-if="saving"></span>
<span v-if="saving" class="mx-1">Saving...</span>
</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { ref } from 'vue/dist/vue.esm-bundler';
export default {
props: [
],
setup() {
return {
imports: ref([]),
busy: ref(false),
i_products: ref(""),
i_id: ref(""),
i_title: ref(""),
i_subtitle: ref(""),
i_subsubtitle: ref(""),
i_revno: ref(""),
i_month: ref(0),
i_year: ref(0),
i_overview: ref(""),
i_cover: ref(""),
i_fullcover: ref(""),
i_origfile: ref(""),
i_covertext: ref(""),
i_revision: ref(0),
triglock: ref(false),
saving: ref(false),
importing: ref({
id: 0
}),
newdoc: ref(true),
existing_doc: ref({
title: '',
subtitle: '',
subsubtitle: '',
}),
timer: ref(null),
zoom: ref(null),
fsel_start: ref(0),
fsel_end: ref(0),
fsel_el: ref(null),
keytimer : ref(false),
}
},
methods: {
init() {
this.update();
var that = this;
},
focus() {
this.$refs.rtitle.focus();
this.zoom = new ImageZoom(document.getElementById("cover"), {
fillContainer: true,
offset: { vertical: 0, horizontal: 10 },
zoomStyle: "z-index: 10;",
});
},
update() {
this.timer = false;
$.ajax({
url: "/api/imports",
method: "GET"
}).done(this.got_update);
},
got_update(data) {
this.imports = data;
this.busy = false;
var r = 0;
for (var i = 0; i < this.imports.length; i++) {
if (this.imports[i].completed == 0) {
r = 1;
}
}
if (r) {
if (this.timer) {
clearTimeout(this.timer);
this.timer=null;
}
this.timer = setTimeout(this.update, 1000);
}
},
del(rev) {
this.busy = true;
$.ajax({
url: "/api/import/" + rev.id,
method: "DELETE"
}).done(this.got_update);
},
restart(rev) {
this.busy = true;
$.ajax({
url: "/api/import/" + rev.id,
method: "POST",
data: {
started: 0,
completed: 0,
imported: 0,
}
}).done(this.got_update);
},
import_rev(rev) {
this.importing = rev;
this.i_id = rev.guessed_id;
this.i_title = rev.guessed_title;
this.i_revno = rev.guessed_revno;
this.i_subtitle = "";
this.i_subsubtitle = "";
this.i_month = rev.guessed_month;
this.i_year = rev.guessed_year;
this.i_overview = rev.overview;
this.i_products = "";
this.i_cover = "/cover/" + rev.revision.id + "/cover.jpg";
this.i_fullcover = "/cover/" + rev.revision.id + "/cover.jpg";
this.i_origfile = rev.origfile;
this.i_covertext = rev.covertext;
this.i_revision = rev.revision.id;
this.find_doc();
$(document).on('hide.bs.modal', '#docedit', function(e) {alert("doc"); });
$('#docedit').on('hide.bs.modal', function(e) {alert("modal"); });
$('#docedit').show();
setTimeout(this.focus, 10);
},
test(e) {
console.log(e);
},
close() {
$('#docedit').hide();
},
save() {
if (this.busy) return;
if (this.saving) return;
if (this.newdoc && (this.i_products == '')) return;
if (this.newdoc && (this.i_title == '')) return;
this.busy = true;
this.saving = true;
$.ajax({
url: "/api/import/" + this.importing.id ,
method: "PUT",
data: {
internal_id: this.i_id,
title: this.i_title,
subtitle: this.i_subtitle,
subsubtitle: this.i_subsubtitle,
month: this.i_month,
year: this.i_year,
overview: this.i_overview,
products: this.i_products,
revno: this.i_revno,
}
}).done(this.saved);
},
saved(data) {
this.got_update(data);
this.saving = false;
$('#docedit').hide();
delete this.zoom;
},
find_doc() {
var i_id = this.i_id.toUpperCase();
$.ajax({
url: "/api/documentbyid/" + i_id,
method: "GET"
}).done(this.got_doc);
},
got_doc(data) {
if (data == false) {
this.newdoc = true;
} else {
this.newdoc = false;
this.existing_doc = data;
}
},
fancyrow(i) {
if (i.completed > 0) {
if (!i.revision) {
return "list-group-item list-group-item-danger";
} else {
return "list-group-item list-group-item-success";
}
}
if (i.started > 0) return "list-group-item list-group-item-warning";
if (i.queued > 0) return "list-group-item list-group-item-primary";
return "list-group-item";
},
get_caret_position(oField) {
// Initialize
var iCaretPos = 0;
if (document.selection) {
// Set focus on the element
oField.focus();
// To get cursor position, get empty selection range
var oSel = document.selection.createRange();
// Move selection start to 0 position
oSel.moveStart('character', -oField.value.length);
// The caret position is selection length
iCaretPos = oSel.text.length;
} else if (oField.selectionStart || oField.selectionStart == '0') {
iCaretPos = oField.selectionDirection=='backward' ? oField.selectionStart : oField.selectionEnd;
}
return iCaretPos;
},
select_to_end(oField, from) {
if (document.selection) {
oField.focus();
var oSel = document.selection.createRange();
oSel.moveStart('character', from);
oSel.moveEnd('character', oField.value.length);
} else {
oField.setSelectionRange(from, oField.value.length);
}
},
split_title() {
var pos = this.get_caret_position(this.$refs.rtitle);
var l = this.i_title.substring(0, pos);
var r = this.i_title.substring(pos);
this.i_title = l;
this.i_subtitle = r;
},
split_subtitle() {
var pos = this.get_caret_position(this.$refs.rsubtitle);
var l = this.i_subtitle.substring(0, pos);
var r = this.i_subtitle.substring(pos);
this.i_subtitle = l;
this.i_subsubtitle = r;
},
upper_id() {
this.i_id = this.i_id.toUpperCase();
},
paste_id() {
if (navigator.clipboard) {
navigator.clipboard.readText().then(this.do_set_id);
}
},
do_set_id(data) {
this.i_id = data;
this.find_doc();
},
download(data) {
document.location="/pdf/" + data.revision.id + "/download/doc.pdf";
},
try_title(e) {
if (e.key == "Shift") return;
if (e.key == "ArrowLeft") return;
if (e.key == "ArrowRight") return;
if (e.key == "ArrowUp") return;
if (e.key == "ArrowDown") return;
if (e.key == "Backspace") return;
if (this.triglock) return;
if (this.keytimer) {
clearTimeout(this.keytimer);
}
this.keytimer = setTimeout(this.try_title2, 250);
},
try_title2() {
this.keytimer = false
$.ajax({
url: "/api/search/prefix",
method: "POST",
data: {
title: this.i_title
}
}).done(this.fill_title);
},
fill_title(data, status) {
if (status == "success") {
this.triglock = true;
this.fsel_start = this.get_caret_position(this.$refs.rtitle);
this.fsel_el = this.$refs.rtitle;
this.i_title = data;
this.triglock = false;
setTimeout(this.fsel, 10);
}
},
try_subtitle(e) {
if (e.key == "Shift") return;
if (e.key == "ArrowLeft") return;
if (e.key == "ArrowRight") return;
if (e.key == "ArrowUp") return;
if (e.key == "ArrowDown") return;
if (e.key == "Backspace") return;
if (this.triglock) return;
if (this.keytimer) {
clearTimeout(this.keytimer);
}
this.keytimer = setTimeout(this.try_subtitle2, 250);
},
try_subtitle2() {
this.keytimer = false
$.ajax({
url: "/api/search/prefix",
method: "POST",
data: {
title: this.i_subtitle
}
}).done(this.fill_subtitle);
},
fill_subtitle(data, status) {
if (status == "success") {
this.triglock = true;
this.fsel_start = this.get_caret_position(this.$refs.rsubtitle);
this.fsel_el = this.$refs.rsubtitle;
this.i_subtitle = data;
this.triglock = false;
setTimeout(this.fsel, 10);
}
},
try_subsubtitle(e) {
if (e.key == "Shift") return;
if (e.key == "ArrowLeft") return;
if (e.key == "ArrowRight") return;
if (e.key == "ArrowUp") return;
if (e.key == "ArrowDown") return;
if (e.key == "Backspace") return;
if (this.triglock) return;
if (this.keytimer) {
clearTimeout(this.keytimer);
}
this.keytimer = setTimeout(this.try_subsubtitle2, 250);
},
try_subsubtitle2() {
this.keytimer = false
$.ajax({
url: "/api/search/prefix",
method: "POST",
data: {
title: this.i_subsubtitle
}
}).done(this.fill_subsubtitle);
},
fill_subsubtitle(data, status) {
if (status == "success") {
this.triglock = true;
this.fsel_start = this.get_caret_position(this.$refs.rsubsubtitle);
this.fsel_el = this.$refs.rsubsubtitle;
this.i_subsubtitle = data;
this.triglock = false;
setTimeout(this.fsel, 10);
}
},
fsel() {
this.select_to_end(this.fsel_el, this.fsel_start);
},
},
mounted() {
this.init();
}
}
</script>
+85
View File
@@ -0,0 +1,85 @@
<template>
<div>
<div v-if="editing" class="input-group">
<input
ref="edval"
class="form-control"
type="text"
v-model="text"
@keyup.enter="submit_edit"
@keyup.esc="cancel_edit"
>
<button @click="cancel_edit" class="btn btn-danger px-1 py-0">
<i class="fa fa-close"></i>
</button>
<button @click="submit_edit" class="btn btn-success px-1 py-0">
<i class="fa fa-check"></i>
</button>
</div>
<div v-else class="d-flex justify-content-between align-middle">
<span class="align-middle my-auto">{{ text }}</span>
<button @click="enable_edit" class="btn btn-secondary px-1 py-0">
<i class="fa fa-pencil"></i>
</button>
</div>
</div>
</template>
<script>
import { ref } from 'vue/dist/vue.esm-bundler';
export default {
props: [
"value",
"url",
"field",
],
setup() {
return {
editing: ref(false),
text: ref(""),
saved: ref(""),
}
},
methods: {
enable_edit() {
this.editing = true;
this.saved = this.text;
var that = this;
setTimeout(function() { $(that.$refs.edval).focus() }, 100);
},
cancel_edit() {
this.text = this.saved;
this.editing = false;
},
submit_edit() {
var data = {};
data[this.field] = this.text;
console.log(data);
$.ajax({
url: this.url,
method: "POST",
data: data
});
this.editing = false;
},
},
mounted() {
this.text = this.value;
}
}
</script>
+114
View File
@@ -0,0 +1,114 @@
<template>
<div>
<div v-if="editing">
<textarea
ref="edval"
class="form-control w-100"
rows="5"
type="text"
v-model="text"
@keyup.esc="cancel_edit"
></textarea>
<button @click="cancel_edit" class="btn btn-danger">
<i class="fa fa-close"></i>
</button>
<button @click="submit_edit" class="btn btn-success">
<i class="fa fa-check"></i>
</button>
</div>
<div v-else>
<div class="align-middle my-auto" v-html="md()"></div>
<button @click="enable_edit" class="btn btn-secondary">
<i class="fa fa-pencil"></i>
</button>
</div>
<div ref="slotData" class="visually-hidden">
<slot></slot>
</div>
</div>
</template>
<script>
import { useSlots, ref } from 'vue/dist/vue.esm-bundler';
import { marked } from 'marked';
export default {
props: [
"url",
"field",
],
setup() {
return {
editing: ref(false),
text: ref(""),
saved: ref(""),
}
},
methods: {
init() {
this.load();
},
enable_edit() {
this.editing = true;
this.saved = this.text;
var that = this;
setTimeout(function() { $(that.$refs.edval).focus() }, 100);
},
cancel_edit() {
this.text = this.saved;
this.editing = false;
},
submit_edit() {
var data = {};
data[this.field] = this.text;
$.ajax({
url: this.url,
method: "POST",
data: data
});
this.editing = false;
},
load() {
$.ajax({
url: this.url,
method: "GET"
}).done(this.loaded);
},
loaded(data) {
this.text = data[0][this.field];
},
md() {
var text = this.text;
if (text == null) {
text = "Loading...";
}
var txt = marked.parse(text);
return txt;
},
},
mounted() {
// var div = document.createElement("div");
// div.innerHTML = this.$slots.default()[0].children;
// this.text = div.textContent;
this.init();
}
}
</script>
+87
View File
@@ -0,0 +1,87 @@
<template>
<div>
<div v-if="editing" class="input-group">
<select class="form-control" v-model="sel">
<option v-for='i in items' :value='i.key'>{{ i.value }}</option>
</select>
<button @click="cancel_edit" class="btn btn-danger">
<i class="fa fa-close"></i>
</button>
<button @click="submit_edit" class="btn btn-success">
<i class="fa fa-check"></i>
</button>
</div>
<div v-else>
<button @click="enable_edit" class="btn btn-secondary">
<i class="fa fa-plus"></i>
</button>
</div>
</div>
</template>
<script>
import { ref } from 'vue/dist/vue.esm-bundler';
export default {
props: [
"list",
"target",
"method",
],
setup() {
return {
editing: ref(false),
sel: ref(""),
items: ref([]),
}
},
methods: {
enable_edit() {
$.ajax({
url: this.list,
method: "GET"
}).done(this.start_edit);
},
start_edit(d) {
this.items = d;
this.editing = true;
var that = this;
},
cancel_edit() {
this.text = this.saved;
this.editing = false;
},
submit_edit() {
var data = {};
data["item_id"] = this.sel;
$.ajax({
url: this.target,
method: this.method,
data: data
}).done(this.fin);
this.editing = false;
},
fin() {
location.reload();
},
},
mounted() {
this.text = this.value;
}
}
</script>
+83
View File
@@ -0,0 +1,83 @@
<template>
<div>
<ul class="list-group">
<li v-for="job in jobs" :class="'d-flex justify-content-between list-group-item ' + get_group_color(job)">
<span>
{{ job.id }} - {{ job.class }} - {{ job.status }}
</span>
<small v-if='(job.finished + job.failed) > 0'><button @click='delete_job(job)' class='px-1 py-0 btn btn-danger'><i class='fa fa-close'></i></button></small>
</li>
</ul>
</div>
</template>
<script>
import { ref } from 'vue/dist/vue.esm-bundler';
export default {
props: [
"source"
],
setup() {
return {
jobs: ref([]),
speed: ref(1000),
timeout: ref(null),
}
},
methods: {
init() {
this.update();
},
update() {
$.ajax({
url: "/api/jobs/" + this.source,
method: "GET"
}).done(this.updated);
},
updated(d) {
this.refresh(d);
setTimeout(this.update, this.speed);
},
refresh(d) {
this.jobs = d;
this.speed = 10000;
for (var i = 0; i < this.jobs.length; i++) {
if ((this.jobs[i].finished == 0) && (this.jobs[i].failed == 0)) {
this.speed = 1000;
}
}
},
get_group_color(j) {
if (j.failed > 0) {
return "list-group-item-danger";
}
if (j.finished > 0) {
return "list-group-item-success";
}
if (j.started > 0) {
return "list-group-item-warning";
}
return "list-group-item-info";
},
delete_job(j) {
$.ajax({
url: "/api/jobs/" + j.id,
method: "DELETE"
}).done(this.refresh);
},
},
mounted() {
this.init();
}
}
</script>
+34
View File
@@ -0,0 +1,34 @@
<template>
<div style="display: none;">
</div>
</template>
<script>
import { ref } from 'vue/dist/vue.esm-bundler';
export default {
props: [
"keyname",
"target",
],
setup() {
return {
}
},
methods: {
handler(e) {
console.log(e.key);
if (e.key == this.keyname) {
document.location=this.target;
}
},
},
mounted() {
window.addEventListener('keypress', this.handler);
}
}
</script>
Executable
+89
View File
@@ -0,0 +1,89 @@
<template>
<div>
<div class="row m-3 bg-white">
<div class="col-6 m-0 p-0">
<img @click="nav_left" :class="leftclass()" :src="left">
</div>
<div class="col-6 m-0 p-0">
<img @click="nav_right" :class="rightclass()" :src="right">
</div>
</div>
</div>
</template>
<script>
import { ref } from 'vue/dist/vue.esm-bundler';
export default {
props: {
rev: ref(""),
pages: ref(0)
},
setup(props, emit) {
return {
left: ref(""),
right: ref(""),
leftpage : ref(0),
}
},
methods: {
init() {
this.leftpage = 0;
this.update();
this.$watch('rev', this.show);
},
update() {
this.left = "/page/" + this.rev + "/" + this.leftpage + "/page.jpg";
this.right = "/page/" + this.rev + "/" + (this.leftpage + 1) + "/page.jpg";
},
leftclass() {
if (this.leftpage > 0) {
return 'page-left w-100 m-0 p-0';
} else {
return 'page-end w-100 m-0 p-0';
}
},
rightclass() {
if ((this.leftpage + 1) >= this.pages) {
return "invisible w-100 m-0 p-0";
}
if (this.leftpage < this.pages - 2) {
return 'page-right w-100 m-0 p-0';
} else {
return 'page-end w-100 m-0 p-0';
}
},
show() {
this.left = '';
this.right = '';
this.leftpage = 0;
this.update();
},
nav_right() {
if (this.leftpage < this.pages - 2) {
this.leftpage += 2;
}
this.update();
},
nav_left() {
this.leftpage -= 2;
if (this.leftpage < 0) {
this.leftpage = 0;
}
this.update();
},
},
mounted() {
this.init();
}
}
</script>
+123
View File
@@ -0,0 +1,123 @@
<template>
<div>
<ul class="list-group">
<li v-for="pdf in pdfs" class="list-group-item">
<div class="row">
<div class="col-lg-11 col-12">
<div class="row">
<div class="col-12 text-center">
{{ pdf.title }}
</div>
<div class="col-12">
<a class="btn w-100 text-start" :href="pdf.url" target="_blank">{{ pdf.url }}</a>
</div>
</div>
</div>
<div class="btn-group col-lg-1 col-12 text-center">
<button :disabled="busy" @click="reject(pdf)" class="btn btn-danger"><i class="fa fa-close"></i></button>
<button :disabled="busy" @click="accept(pdf)" class="btn btn-success"><i class="fa fa-check"></i></button>
</div>
</div>
</li>
<li class="list-group-item">
<div class="row">
<div class="col-lg-11 col-12 text-center">
<a class="btn w-100" target="_blank">Entire List</a>
</div>
<div class="btn-group col-lg-1 col-12">
<button :disabled="busy" @click="reject_all" class="btn btn-danger"><i class="fa fa-close"></i></button>
<button :disabled="busy" @click="accept_all" class="btn btn-success"><i class="fa fa-check"></i></button>
</div>
</div>
</li>
</ul>
</div>
</template>
<script>
import { ref } from 'vue/dist/vue.esm-bundler';
export default {
props: [
],
setup() {
return {
pdfs: ref([]),
busy: ref(false),
}
},
methods: {
init() {
this.update();
},
update() {
this.busy = true;
$.ajax({
url: "/api/spider/pdfs",
method: "GET"
}).done(this.got_update);
},
got_update(data) {
this.pdfs = data;
this.busy = false;
},
accept(pdf) {
if (this.busy) return;
this.busy = true;
$.ajax({
url: '/api/spider/pdf/' + pdf.id,
method: "PUT"
}).done(this.got_update);
},
reject(pdf) {
if (this.busy) return;
this.busy = true;
$.ajax({
url: '/api/spider/pdf/' + pdf.id,
method: "DELETE"
}).done(this.got_update);
},
accept_all() {
if (this.busy) return;
this.busy = true;
var l = [];
for (var i = 0; i < this.pdfs.length; i++) {
l.push(this.pdfs[i].id);
}
$.ajax({
url: '/api/spider/pdf/' + l.join(","),
method: "PUT"
}).done(this.got_update);
},
reject_all() {
if (this.busy) return;
this.busy = true;
var l = [];
for (var i = 0; i < this.pdfs.length; i++) {
l.push(this.pdfs[i].id);
}
$.ajax({
url: '/api/spider/pdf/' + l.join(","),
method: "DELETE"
}).done(this.got_update);
},
},
mounted() {
this.init();
}
}
</script>
+188
View File
@@ -0,0 +1,188 @@
<template>
<nav class="d-flex justify-content-end">
<itemadder :list="'/api/product/' + pid + '/available_metadata'" :target="'/api/product/' + pid + '/metadata'" method="PUT"></itemadder>
<div ref="ddcreate_child" class="dropdown ms-1">
<button class="btn btn-success dropdown-toggle" title="Add" data-bs-toggle="dropdown"><i class="fa fa-plus-circle"></i></button>
<ul class="dropdown-menu" style="min-width: 300px;">
<li class="p-2">
<strong>Create new child product</strong>
<div class="input-group">
<span class="input-group-text">Name</span>
<input @keyup.enter="create_child" ref="increate_child" class="form-control" name="title" v-model="newtitle">
<button class="btn btn-primary" @click="create_child">Create</button>
</div>
</li>
</ul>
</div>
<div ref="ddrename" class="dropdown ms-1">
<button class="btn btn-success dropdown-toggle" title="Rename" data-bs-toggle="dropdown" @click="set_edittitle"><i class="fa fa-pencil"></i></button>
<ul class="dropdown-menu" style="min-width: 300px;">
<li class="p-2">
<strong>Rename product</strong>
<div class="input-group">
<span class="input-group-text">Name</span>
<input ref="inrename" class="form-control" name="title" v-model="edittitle">
<button class="btn btn-primary" @click="rename">Rename</button>
</div>
</li>
</ul>
</div>
<button class="btn btn-success ms-1" @click="gemini_all"><i class="fa-solid fa-wand-magic"></i></button>
<button class="btn btn-success ms-1" @click="gemini_all_move"><i class="fa-solid fa-wand-magic-sparkles"></i></button>
<button :class="'btn ms-1 ' + dp_class" @click="delete_product"><i class="fa fa-trash"></i></button>
<button @click="empty_trash" class="ms-1 btn btn-danger" v-if="prod.title=='Trash'"><i class="fa fa-recycle"></i></button>
</nav>
</template>
<script>
import { ref } from 'vue/dist/vue.esm-bundler';
export default {
props: [
"pid",
],
setup() {
return {
newtitle: ref(""),
edittitle: ref(""),
dp_class: ref("btn-danger"),
dp_clicks: ref(0),
prod: ref({}),
}
},
methods: {
init() {
$(this.$refs.ddcreate_child).on("show.bs.dropdown", this.add_child_show);
$(this.$refs.ddcreate_child).on("shown.bs.dropdown", this.add_child_shown);
$(this.$refs.ddrename).on("show.bs.dropdown", this.rename_show);
$(this.$refs.ddrename).on("shown.bs.dropdown", this.rename_shown);
this.get_product();
},
get_product() {
$.ajax({
url: "/api/product/" + this.pid,
method: "GET"
}).done(this.got_product);
},
add_child_show(e) {
this.newtitle = "";
},
add_child_shown(e) {
$(this.$refs.increate_child).focus();
},
rename_show(e) {
this.edittitle = this.prod.title;
},
set_edittitle() {
this.edittitle = this.prod.title;
},
got_product(data) {
this.prod = data[0];
},
rename_shown(e) {
},
create_child() {
console.log("Adding child");
$.ajax({
url: "/api/product/" + this.pid,
method: "PUT",
data: {
title: this.newtitle
}
}).done(this.created_child);
},
rename() {
$.ajax({
url: "/api/product/" + this.pid,
method: "POST",
data: {
title: this.edittitle
}
}).done(this.renamed);
},
renamed() {
window.location.reload();
},
created_child(data) {
window.location.reload();
},
delete_product() {
this.dp_clicks++;
console.log("clicked " + this.dp_clicks);
if (this.dp_clicks >= 2) {
$.ajax({
url: "/api/product/" + this.pid,
method: "DELETE",
}).done(this.done_delete);
return;
}
this.dp_class = "btn-warning";
setTimeout(this.undo_delete, 1000);
},
undo_delete() {
console.log("Undo delete");
this.dp_class = "btn-danger";
this.dp_clicks = 0;
},
done_delete(data) {
document.location="/documents/" + data.id;
},
empty_trash() {
$.ajax({
url: "/api/product/trash/" + this.prod.id,
method: "DELETE"
}).done(this.trash_emptied);
},
trash_emptied() {
window.location.reload();
},
gemini_all() {
$.ajax({
url: "/api/product/gemini_all/" + this.prod.id + "/keep",
method: "GET"
}).done(this.gemini_all_queued);
},
gemini_all_move() {
$.ajax({
url: "/api/product/gemini_all/" + this.prod.id + "/move",
method: "GET"
}).done(this.gemini_all_queued);
},
gemini_all_queued(d) {
},
},
mounted() {
this.init();
},
};
</script>
+172
View File
@@ -0,0 +1,172 @@
<template>
<div>
<div v-for="p in products" class="btn-group m-1">
<button class="btn btn-secondary p-1" :title="p.full_path"><small>{{ p.title }}</small></button>
<button :pid="p.id" @click.stop="remove(p.id)" class="btn btn-secondary p-1" title="Remove"><small><i class="fa fa-close"></i></small></button>
</div>
<div ref="dropdown" class="dropdown" @show.bs.dropdown="add_show">
<button class="btn btn-success p-1 dropdown-toggle" title="Add" data-bs-toggle="dropdown" @click="add_show"><small><i class="fa fa-plus"></i></small></button>
<ul class="dropdown-menu" @show.bs.dropdown="add_show">
<li v-for="p in mru"><a href='#' @click="add_pid(p)" class="dropdown-item">{{ p.full_path }}</a></li>
<hr>
<li><input ref="searchinput" @input="do_search" class='form-control' name='search' v-model="search" autocomplete="off"></li>
<li v-for="p in searchres"><a href='#' @click="add_pid(p)" class="dropdown-item">{{ p.full_path }}</a></li>
</ul>
</div>
<button @click="set_default_pids" class="btn btn-primary">Set Default</button>
<button @click="clear_default_pids" class="btn btn-primary">Clear Default</button>
</div>
</template>
<script>
import { ref, computed } from 'vue/dist/vue.esm-bundler';
export default {
props: {
modelValue: ref(""),
},
setup(props, {emit}) {
return {
products: ref([]),
mru: ref([]),
search: ref(""),
searchres: ref([]),
pids: computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value)
}),
was_mod: ref(false),
req: ref(null),
}
},
methods: {
init() {
$(document).on("show.bs.dropdown", '.dropdown', this.add_show);
$(document).on("shown.bs.dropdown", $(this.$refs.dropdown), this.add_shown);
$(this.$refs.dropdown).on("show.bs.dropdown", this.add_show);
$(this.$refs.dropdown).on("shown.bs.dropdown", this.add_shown);
this.get_products();
this.$watch('modelValue', this.get_products);
},
get_products() {
if (this.pids!= "") {
$.ajax({
url: "/api/product/" + this.pids,
method: "GET"
}).done(this.got_products);
} else {
this.products = [];
}
var m = this.was_mod;
this.was_mod = false;
if (!m) {
if (this.pids == "") {
// Get default if available
var p = localStorage.getItem("default_pids");
if ((p != null) && (p != "")) {
this.was_mod = true;
this.pids = p;
}
}
}
},
got_products(data) {
this.products = data;
},
remove(e) {
this.was_mod = true;
var tmp = [];
var pidarray = [];
for (var i = 0; i < this.products.length; i++) {
if (this.products[i].id != e) {
tmp.push(this.products[i]);
pidarray.push(this.products[i].id);
}
}
this.products = tmp;
this.pids = pidarray.join(",");
},
add_show(e) {
this.search = "";
this.searchres = []
$.ajax({
url: "/api/productmru",
method: "GET"
}).done(this.fill_mru);
},
add_shown(e) {
this.$refs.searchinput.focus();
},
fill_mru(data) {
this.mru = data;
},
do_search(e) {
if (this.search.length >= 3) {
if (this.req != null) {
this.req.abort();
this.req = null;
}
this.req=$.ajax({
url: "/api/search/product",
method: "POST",
data: {
search: this.search
}
}).done(this.search_done);
}
},
search_done(data) {
this.searchres = data;
},
add_pid(prod) {
this.was_mod = true;
var pidarray;
if (this.pids == "") {
pidarray = [];
} else {
pidarray = this.pids.split(",");
}
if (pidarray.includes(prod.id)) {
return;
}
pidarray.push(prod.id);
this.pids = pidarray.join(",");
this.products.push(prod);
$.ajax({
url: "/api/productmru/" + prod.id,
method: "POST",
data: {
}
});
},
set_default_pids() {
localStorage.setItem("default_pids", this.pids);
},
clear_default_pids() {
localStorage.setItem("default_pids", "");
},
},
mounted() {
this.init();
},
};
</script>
Executable
+85
View File
@@ -0,0 +1,85 @@
<template>
<div tabindex=0 @blur.capture="click_close">
<form action="/search" method="POST">
<div class="input-group">
<input name="search" ref="sb" @focus="key_search" @keyup="key_search" class="form-control" v-model="search">
<input type="submit" class="btn btn-success" value="Search">
</div>
<div v-if="results.length > 0" class="z-3 position-absolute bg-light">
<ul class="list-group">
<li class="list-group-item" v-for="r in results"><a :href="'/document/' + r.id"><strong>{{ r.title }}</strong> {{ r.subtitle }} <small>{{ r.subsubtitle }}</small></a></li>
</ul>
</div>
</form>
</div>
</template>
<script>
import { ref } from 'vue/dist/vue.esm-bundler';
export default {
props: [
],
setup() {
return {
search: ref(""),
searchTimer: ref(null),
results: ref([]),
req: ref(null),
}
},
methods: {
click_close(event) {
if (!event.currentTarget.contains(event.relatedTarget)) {
this.results = [];
}
},
key_search() {
if (this.searchTimer != null) {
window.clearTimeout(this.searchTimer);
this.searchTimer = null;
}
this.searchTimer = setTimeout(this.do_key_search, 100);
},
do_key_search() {
if (this.search.length < 3) {
this.results = [];
} else {
this.searchTimer = null;
if (this.req != null) {
this.req.abort();
this.req = null;
}
this.req = $.ajax({
url: "/api/search/title",
method: "POST",
data: {
search: this.search
}
}).done(this.got_results);
}
},
got_results(data) {
this.results = data;
},
shortcut(e) {
if (e.target.tagName == "INPUT") return;
if (e.target.tagName == "TEXTAREA") return;
if (e.key == "/") {
e.preventDefault();
$(this.$refs.sb).focus();
}
},
},
mounted() {
$(document).on("keydown", this.shortcut);
}
}
</script>
+79
View File
@@ -0,0 +1,79 @@
<template>
<div>
<ul class="list-group">
<li v-for="page in pages" class="list-group-item">
<div class="row">
<div class="col-lg-11 col-12 text-center">
<a class="btn text-left w-100" :href="page.url" target="_blank">
<h6>{{ page.title }}</h6>
{{ page.url }}
</a>
</div>
<div class="btn-group col-lg-1 col-12">
<button :disabled="busy" @click="reject(page)" class="btn btn-danger"><i class="fa fa-close"></i></button>
<button :disabled="busy" @click="accept(page)" class="btn btn-success"><i class="fa fa-check"></i></button>
</div>
</div>
</li>
</ul>
</div>
</template>
<script>
import { ref } from 'vue/dist/vue.esm-bundler';
export default {
props: [
],
setup() {
return {
pages: ref([]),
busy: ref(false),
}
},
methods: {
init() {
this.update();
},
update() {
this.busy = true;
$.ajax({
url: "/api/spider/pages",
method: "GET"
}).done(this.got_update);
},
got_update(data) {
this.pages = data;
this.busy = false;
},
accept(page) {
if (this.busy) return;
this.busy = true;
$.ajax({
url: "/api/spider/page/" + page.id,
method: "PUT"
}).done(this.got_update)
},
reject(page) {
if (this.busy) return;
this.busy = true;
$.ajax({
url: "/api/spider/page/" + page.id,
method: "DELETE"
}).done(this.got_update)
},
},
mounted() {
this.init();
}
}
</script>
+73
View File
@@ -0,0 +1,73 @@
<template>
<div @click="confirm_rating()" @mouseleave.prevent="reset">
<span v-if="current_rating<3" @mouseenter.prevent="set_rating(10)"><i class="fa-regular fa-star"></i></span>
<span v-else-if="current_rating<8" @mouseenter.prevent="set_rating(10)"><i class="fa-solid fa-star-half-stroke"></i></span>
<span v-else @mouseenter.prevent="set_rating(10)"><i class="fa-solid fa-star"></i></span>
<span v-if="current_rating<13" @mouseenter.prevent="set_rating(20)"><i class="fa-regular fa-star"></i></span>
<span v-else-if="current_rating<18" @mouseenter.prevent="set_rating(20)"><i class="fa-solid fa-star-half-stroke"></i></span>
<span v-else @mouseenter.prevent="set_rating(20)"><i class="fa-solid fa-star"></i></span>
<span v-if="current_rating<23" @mouseenter.prevent="set_rating(30)"><i class="fa-regular fa-star"></i></span>
<span v-else-if="current_rating<28" @mouseenter.prevent="set_rating(30)"><i class="fa-solid fa-star-half-stroke"></i></span>
<span v-else @mouseenter.prevent="set_rating(30)"><i class="fa-solid fa-star"></i></span>
<span v-if="current_rating<33" @mouseenter.prevent="set_rating(40)"><i class="fa-regular fa-star"></i></span>
<span v-else-if="current_rating<38" @mouseenter.prevent="set_rating(40)"><i class="fa-solid fa-star-half-stroke"></i></span>
<span v-else @mouseenter.prevent="set_rating(40)"><i class="fa-solid fa-star"></i></span>
<span v-if="current_rating<43" @mouseenter.prevent="set_rating(50)"><i class="fa-regular fa-star"></i></span>
<span v-else-if="current_rating<48" @mouseenter.prevent="set_rating(50)"><i class="fa-solid fa-star-half-stroke"></i></span>
<span v-else @mouseenter.prevent="set_rating(50)"><i class="fa-solid fa-star"></i></span>
</div>
</template>
<script>
import { ref } from 'vue/dist/vue.esm-bundler';
export default {
props: {
rev: ref(""),
rating: ref("")
},
setup() {
return {
current_rating: ref(""),
orig_rating: ref("")
}
},
methods: {
init() {
this.current_rating = this.rating;
this.orig_rating = this.rating;
},
reset() {
this.current_rating = this.orig_rating;
},
set_rating(v) {
this.current_rating = v;
},
confirm_rating() {
this.orig_rating = this.current_rating;
$.ajax({
url: "/api/revision/rate/" + this.rev + "/" + this.current_rating,
method: "PUT"
}).done(this.updated);
},
updated(d) {
this.orig_rating = d.rating;
this.current_rating = d.rating;
}
},
mounted() {
this.init();
}
}
</script>
+24
View File
@@ -0,0 +1,24 @@
<template>
<div>
</div>
</template>
<script>
import { ref } from 'vue/dist/vue.esm-bundler';
export default {
props: [
],
setup() {
return {
}
},
methods: {
},
mounted() {
}
}
</script>
Executable
+27
View File
@@ -0,0 +1,27 @@
.table td.fit,
.table th.fit {
white-space: nowrap;
width: 1%
}
a[title]:hover::after {
padding: 5px;
}
.ocrtext {
overflow: auto;
max-height: 400px;
}
.page-right {
cursor: e-resize;
}
.page-left {
cursor: w-resize;
}
.page-end {
cursor: not-allowed;
}
Executable
+54
View File
@@ -0,0 +1,54 @@
import 'jquery';
import 'jquery-simple-websocket';
import 'bootstrap';
import '@fortawesome/fontawesome-free/js/fontawesome';
import '@fortawesome/fontawesome-free/js/solid';
import '@fortawesome/fontawesome-free/js/regular';
import '@fortawesome/fontawesome-free/js/brands';
import 'bootstrap/dist/css/bootstrap.min.css';
import { createApp } from 'vue/dist/vue.esm-bundler';
import Download from './Download.vue';
import DocumentEdit from './DocumentEdit.vue';
import ProductSelector from './ProductSelector.vue';
import ProductControls from './ProductControls.vue';
import InlineEdit from './InlineEdit.vue';
import InlineEditText from './InlineEditText.vue';
import Search from './Search.vue';
import PDFList from './PDFList.vue';
import SpiderPages from './SpiderPages.vue';
import DownloadManager from './DownloadManager.vue';
import Imports from './Imports.vue';
import IDMatch from './IDMatch.vue';
import DDDiv from './DDDiv.vue';
import PDF from './PDF.vue';
import ItemAdder from './ItemAdder.vue';
import KeyboardInteraction from './KeyboardInteraction.vue';
import JobDisplay from './JobDisplay.vue';
import StarRating from './StarRating.vue';
const app = createApp({ });
app.component("download", Download);
app.component("documentedit", DocumentEdit);
app.component("productselector", ProductSelector);
app.component("productcontrols", ProductControls);
app.component("inlineedit", InlineEdit);
app.component("inlineedittext", InlineEditText);
app.component("search", Search);
app.component("pdflist", PDFList);
app.component("spiderpages", SpiderPages);
app.component("downloadmanager", DownloadManager);
app.component("imports", Imports);
app.component("idmatch", IDMatch);
app.component("dddiv", DDDiv);
app.component("pdf", PDF);
app.component("itemadder", ItemAdder);
app.component("keyboardinteraction", KeyboardInteraction);
app.component("jobdisplay", JobDisplay);
app.component("starrating", StarRating);
$(window).on("load", function() { app.mount('#app') });
Executable
View File