178 lines
6.4 KiB
Vue
178 lines
6.4 KiB
Vue
|
<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>
|