Document creation, edition, deletion, and signature
This commit is contained in:
177
htdocs/ViewDocEdit.vue
Normal file
177
htdocs/ViewDocEdit.vue
Normal file
@@ -0,0 +1,177 @@
|
||||
<template>
|
||||
<div v-if="record !== null" class="card mb-3 shadow">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<span>{{record['~.01'] && record['~.01'].description || 'Document'}}</span>
|
||||
<a class="widget" @click="() => update(true)">✔</a>
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item"><DateTimePicker v-model="datetime" /></li>
|
||||
<li class="list-group-item">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">Subject</span>
|
||||
<input type="text" class="form-control" v-model="subject" />
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-group-item"><textarea ref="textarea" class="form-control" v-model="text" @keydown.tab.exact.prevent="tab" @keydown.shift.tab.exact.prevent="untab" /></li>
|
||||
</ul>
|
||||
<div v-if="saved" class="card-footer" style="text-align: right;">Saved at {{saved.toLocaleString()}}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
a.widget {
|
||||
cursor: default;
|
||||
text-decoration: none;
|
||||
}
|
||||
textarea {
|
||||
font-family: monospace;
|
||||
tab-size: 8;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { debounce } from './util.mjs';
|
||||
import { strptime, strftime } from './fmdatetime.mjs';
|
||||
|
||||
import DateTimePicker from './DateTimePicker.vue';
|
||||
|
||||
function untab({ input, size=8, tab='\t', space=' ', join=true}={}) {
|
||||
input = input.split('\n');
|
||||
for(var i = input.length - 1; i >= 0; --i) input[i] = untab_line(input[i], size, tab, space);
|
||||
return join ? input.join('\n') : input;
|
||||
}
|
||||
function untab_line(line, size=8, tab='\t', space=' ') {
|
||||
var res = '', index = 0, offset = 0, next, count;
|
||||
while((next = line.indexOf(tab, index)) >= 0) {
|
||||
count = size - (next + offset)%size;
|
||||
res += line.substring(index, next) + Array(count + 1).join(space);
|
||||
offset += count - 1;
|
||||
index = next + 1;
|
||||
}
|
||||
return res + line.substring(index);
|
||||
}
|
||||
|
||||
function retab({ input, size=8, tab='\t', space=' ' }={}) {
|
||||
var re_space = new RegExp(space.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '+', 'g');
|
||||
return input.replace(re_space, function(m) { return Array(Math.ceil(m.length/size) + 1).join(tab); });
|
||||
}
|
||||
|
||||
function wrap({ input, width=80, cut=false, tabsize=8, tab='\t', space=' ', untab=false, join=true }={}) {
|
||||
var input = input.split('\n'), lines, res = [];
|
||||
if(untab) {
|
||||
for(var i = 0; i < input.length; ++i) {
|
||||
lines = wrap_line_split(untab_line(input[i], tabsize, tab, space), width, cut);
|
||||
for(var j = 0; j < lines.length; ++j) res.push(lines[j].replace(/(\s)\s+$/, '$1'));
|
||||
}
|
||||
return join ? res.join('\n') : res;
|
||||
} else {
|
||||
for(var i = 0; i < input.length; ++i) {
|
||||
lines = wrap_line_split(untab_line(input[i], tabsize, tab, tab), width, cut); // replace tabs with placeholder tabs
|
||||
for(var j = 0; j < lines.length; ++j) res.push(lines[j].replace(/(\s)\s+$/, '$1'));
|
||||
}
|
||||
res = retab({ input: res.join('\n'), size: tabsize, tab, space: tab }); // collapse placeholder tabs
|
||||
return join ? res : res.split('\n');
|
||||
}
|
||||
}
|
||||
function wrap_line(str, width=80, cut=false, brk='\n') {
|
||||
if(!str) return str;
|
||||
return str.match(new RegExp('.{1,' + width + '}(\\s+|$)' + (cut ? '|.{' + width + '}|.+$' : '|\\S+?(\\s+|$)'), 'g')).join(brk);
|
||||
}
|
||||
function wrap_line_split(str, width=80, cut=false) {
|
||||
if(!str) return [str];
|
||||
return str.match(new RegExp('.{1,' + width + '}(\\s+|$)' + (cut ? '|.{' + width + '}|.+$' : '|\\S+?(\\s+|$)'), 'g'));
|
||||
}
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DateTimePicker
|
||||
},
|
||||
props: {
|
||||
client: Object,
|
||||
dfn: String,
|
||||
ien: String
|
||||
},
|
||||
emits: {
|
||||
'accept': null,
|
||||
'update': Object
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
record: null,
|
||||
saved: null
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
datetime: {
|
||||
get() { return this.record && this.record['~1301'] && this.record['~1301'].value; },
|
||||
set(value) { this.record['~1301'].value = strftime(strptime(value)); this.autosave(); }
|
||||
},
|
||||
subject: {
|
||||
get() { return this.record && this.record['~1701'] && this.record['~1701'].value; },
|
||||
set(value) { this.record['~1701'].value = this.record['~.07'].value = value; this.autosave(); }
|
||||
},
|
||||
text: {
|
||||
get() { return this.record && this.record.text; },
|
||||
set(value) { this.record.text = value; this.autosave(); }
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
text() {
|
||||
this.resize();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async tab(evt) {
|
||||
var target = event.target, value = target.value, start = target.selectionStart, end = target.selectionEnd;
|
||||
if(start == end) document.execCommand('insertText', false, '\t');
|
||||
else {
|
||||
start = target.selectionStart = value.lastIndexOf('\n', start - 1) + 1;
|
||||
end = target.selectionEnd = value.indexOf('\n', end); if(end < 0) end = value.length;
|
||||
var selection = value.substring(start, end);
|
||||
document.execCommand('insertText', false, selection.replace(/^/gm, '\t'));
|
||||
await this.$nextTick();
|
||||
target.selectionStart = start;
|
||||
target.selectionEnd = end + selection.split('\n').length;
|
||||
}
|
||||
},
|
||||
async untab(evt) {
|
||||
var target = event.target, value = target.value;
|
||||
var start = target.selectionStart = value.lastIndexOf('\n', target.selectionStart - 1) + 1;
|
||||
var end = target.selectionEnd = value.indexOf('\n', target.selectionEnd); if(end < 0) end = value.length;
|
||||
var selection = value.substring(start, end);
|
||||
document.execCommand('insertText', false, selection.replace(/^\t/gm, ''));
|
||||
await this.$nextTick();
|
||||
target.selectionStart = start;
|
||||
target.selectionEnd = end - (selection.match(/^\t/gm) || []).length;
|
||||
},
|
||||
async update(accept) {
|
||||
var res = this.record.reduce((acc, val) => (acc['"' + val.field + '"'] = val.value, acc), {});
|
||||
var text = wrap({input: this.text || '\x01', width: 80, cut: true, untab: false, join: false});
|
||||
for(var i = 0; i < text.length; ++i) res['"TEXT","' + (i + 1) + '","0"'] = text[i];
|
||||
this.$emit('update', res);
|
||||
if(await this.client.TIU_UPDATE_RECORD(this.ien, res, 0)) {
|
||||
await this.client.TIU_GET_RECORD_TEXT_FLUSH(this.ien);
|
||||
this.saved = new Date();
|
||||
if(accept) this.$emit('accept');
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.resize = debounce(async function() {
|
||||
var textarea = this.$refs.textarea;
|
||||
textarea.style.height = 'auto';
|
||||
await this.$nextTick();
|
||||
textarea.style.height = textarea.scrollHeight + 4 + 'px';
|
||||
}, 50);
|
||||
this.autosave = debounce(this.update, 2000);
|
||||
this.$watch(
|
||||
() => (this.client, this.ien, {}),
|
||||
async function() {
|
||||
this.record = (this.client) && (this.ien) ? await this.client.TIU_LOAD_RECORD_FOR_EDIT(this.ien, '.01;.06;.07;1301;1204;1208;1701;1205;1405;2101;70201;70202') : [];
|
||||
this.resize();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
}
|
||||
};
|
||||
</script>
|
Reference in New Issue
Block a user