v3 init push
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
<script>
|
||||
import { formatDate, formatTime } from '@scripts/time';
|
||||
|
||||
/**
|
||||
* One page of the book.
|
||||
*
|
||||
* A page is dumb on purpose: it is handed a list of already-broken visual lines
|
||||
* and draws them, the stamps in its margin, and whatever overlay (caret,
|
||||
* selection) the book has measured for it. All the deciding - where lines
|
||||
* break, which page they land on, where the caret is - happens in Book.vue,
|
||||
* because none of it can be decided a page at a time.
|
||||
*/
|
||||
export default {
|
||||
props: {
|
||||
//Visual lines for this page: {id, start, end, first}
|
||||
lines: {type: Array, required: true},
|
||||
//id -> entry, for the text and the margin stamps
|
||||
entries: {type: Map, required: true},
|
||||
//Position of this page in the whole book, for the page number
|
||||
pageIndex: {type: Number, required: true},
|
||||
side: {type: String, default: 'left'},
|
||||
//The entry still being written, drawn a shade darker than past pages
|
||||
openId: {type: Number, default: 0},
|
||||
lineHeight: {type: Number, default: 0},
|
||||
linesPerPage: {type: Number, default: 1},
|
||||
//Set on the page that currently holds the writing caret
|
||||
writable: {type: Boolean, default: false},
|
||||
//{line, x} in page coordinates, or null
|
||||
caret: {type: Object, default: null},
|
||||
//[{line, left, width}] - the book draws its own selection, since the
|
||||
//input's native one is invisible
|
||||
selection: {type: Array, default: () => []},
|
||||
//Line to hang the "write here" hint on, or -1
|
||||
placeholderLine: {type: Number, default: -1},
|
||||
placeholder: {type: String, default: ''}
|
||||
},
|
||||
emits: ['pick'],
|
||||
inject: ['lang'],
|
||||
computed: {
|
||||
//Where an entry begins, the margin says when it was written
|
||||
stamps() {
|
||||
const asStamps = [];
|
||||
|
||||
this.lines.forEach((oLine, iIndex) => {
|
||||
if(!oLine.first) return;
|
||||
|
||||
const oEntry = this.entries.get(oLine.id);
|
||||
if(!oEntry) return;
|
||||
|
||||
asStamps.push({
|
||||
id: oLine.id,
|
||||
line: iIndex,
|
||||
date: formatDate(oEntry.time, oEntry.timezone, this.lang.locale),
|
||||
time: formatTime(oEntry.time, oEntry.timezone, this.lang.locale)
|
||||
});
|
||||
});
|
||||
|
||||
return asStamps;
|
||||
},
|
||||
pageNumber() {
|
||||
return this.pageIndex + 1;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
lineText(oLine) {
|
||||
const oEntry = this.entries.get(oLine.id);
|
||||
return oEntry ? oEntry.content.slice(oLine.start, oLine.end) : '';
|
||||
},
|
||||
isOpenLine(oLine) {
|
||||
return (oLine.id === this.openId);
|
||||
},
|
||||
offsetFor(iLine) {
|
||||
return (iLine * this.lineHeight) + 'px';
|
||||
},
|
||||
|
||||
/* Read back by Book.vue, which needs real geometry to place the caret,
|
||||
* the selection, the input and the measurer. */
|
||||
|
||||
getLinesElement() {
|
||||
return this.$refs.lines || null;
|
||||
},
|
||||
getBodyElement() {
|
||||
return this.$refs.body || null;
|
||||
},
|
||||
/**
|
||||
* The nth rendered line.
|
||||
*
|
||||
* Queried from the DOM rather than read out of a `ref` on the v-for:
|
||||
* Vue makes no promise that a v-for ref array is in source order, and
|
||||
* when it is not, the caret gets measured against the wrong line - which
|
||||
* lands it back at the start of the line instead of after what was
|
||||
* just typed.
|
||||
*/
|
||||
getLineElement(iIndex) {
|
||||
const oLines = this.$refs.lines;
|
||||
if(!oLines) return null;
|
||||
|
||||
return oLines.querySelectorAll('.page__line')[iIndex] || null;
|
||||
},
|
||||
|
||||
onClick(oEvent) {
|
||||
if(!this.writable || (this.lineHeight <= 0)) return;
|
||||
|
||||
const oLines = this.$refs.lines;
|
||||
if(!oLines) return;
|
||||
|
||||
const oRect = oLines.getBoundingClientRect();
|
||||
const iLine = Math.floor((oEvent.clientY - oRect.top) / this.lineHeight);
|
||||
|
||||
this.$emit('pick', {
|
||||
pageIndex: this.pageIndex,
|
||||
lineIndex: Math.max(0, Math.min(this.linesPerPage - 1, iLine)),
|
||||
clientX: oEvent.clientX,
|
||||
extend: oEvent.shiftKey
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section
|
||||
class="page"
|
||||
:class="['page--' + side, writable ? 'page--writable' : null]"
|
||||
@mousedown.prevent="onClick"
|
||||
>
|
||||
<div ref="body" class="page__body">
|
||||
<div class="page__margin">
|
||||
<span
|
||||
v-for="oStamp in stamps"
|
||||
:key="oStamp.id"
|
||||
class="page__stamp"
|
||||
:style="{top: offsetFor(oStamp.line)}"
|
||||
>
|
||||
<span class="page__stamp-date">{{ oStamp.date }}</span>
|
||||
<span class="page__stamp-time">{{ oStamp.time }}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="page__column">
|
||||
<div ref="lines" class="page__lines">
|
||||
<span
|
||||
v-for="(oSelection, iIndex) in selection"
|
||||
:key="'sel' + iIndex"
|
||||
class="page__selection"
|
||||
:style="{top: offsetFor(oSelection.line), left: oSelection.left + 'px', width: oSelection.width + 'px'}"
|
||||
></span>
|
||||
|
||||
<span
|
||||
v-for="(oLine, iIndex) in lines"
|
||||
:key="iIndex"
|
||||
class="page__line"
|
||||
:class="isOpenLine(oLine) ? null : 'page__line--closed'"
|
||||
>{{ lineText(oLine) }}</span>
|
||||
|
||||
<span
|
||||
v-if="placeholderLine >= 0"
|
||||
class="page__placeholder"
|
||||
:style="{top: offsetFor(placeholderLine)}"
|
||||
>{{ placeholder }}</span>
|
||||
|
||||
<span
|
||||
v-if="caret"
|
||||
class="page__caret"
|
||||
:style="{top: offsetFor(caret.line), left: caret.x + 'px'}"
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page__foot">
|
||||
<span class="page__number">{{ pageNumber }}</span>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
Reference in New Issue
Block a user