638 lines
16 KiB
Vue
638 lines
16 KiB
Vue
<style scoped>
|
|
.layout {
|
|
height: 100%;
|
|
background: #dddddd;
|
|
}
|
|
|
|
.header {
|
|
background: #dddddd;
|
|
}
|
|
|
|
.ivu-layout-header {
|
|
line-height: normal;
|
|
height: auto;
|
|
padding: 0px;
|
|
}
|
|
|
|
|
|
|
|
.content {
|
|
background: #dddddd;
|
|
}
|
|
|
|
.ivu-layout-content {
|
|
padding: 0px;
|
|
}
|
|
|
|
|
|
|
|
.layout-footer-center {
|
|
background: #dddddd;
|
|
text-align: center;
|
|
}
|
|
|
|
.ivu-card-bordered {
|
|
border: 0px solid #dcdee2;
|
|
border-color: #e8eaec;
|
|
}
|
|
|
|
|
|
|
|
.fnmodal {
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
@media print {
|
|
|
|
@page {
|
|
size: portrait;
|
|
margin: 20px;
|
|
}
|
|
|
|
body {
|
|
margin: 0.8cm;
|
|
background-color: #ffffff;
|
|
}
|
|
|
|
.header {
|
|
display: none;
|
|
}
|
|
|
|
.layout-footer-center {
|
|
display: none;
|
|
}
|
|
|
|
|
|
}
|
|
</style>
|
|
|
|
<style>
|
|
.ivu-btn-text:focus {
|
|
margin-top: -3px;
|
|
box-shadow: none !important;
|
|
}
|
|
|
|
.tab_pre {
|
|
display: inline;
|
|
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.tab_pre::selection {
|
|
background: #ed4014;
|
|
color: #FFFFFF;
|
|
}
|
|
|
|
.tab_pre::-moz-selection {
|
|
background: #ed4014;
|
|
color: #FFFFFF;
|
|
}
|
|
|
|
|
|
.vue-contextmenu-listWrapper {
|
|
background: #ed4014 !important;
|
|
border-radius: 0px !important;
|
|
}
|
|
|
|
.vue-contextmenu-listWrapper .context-menu-list {
|
|
background: #ed4014 !important;
|
|
margin: 0px !important;
|
|
}
|
|
|
|
.context-menu-list:hover {
|
|
background: #f16643 !important;
|
|
}
|
|
|
|
.btn-wrapper-simple {
|
|
height: 24px !important;
|
|
margin-top: 1px !important;
|
|
text-align: left !important;
|
|
}
|
|
|
|
.no-child-btn {
|
|
padding: 0px 10px !important;
|
|
}
|
|
|
|
.nav-name-right {
|
|
margin: 0px 20px 0px 5px !important;
|
|
color: #ffffff !important;
|
|
font-size: 14px !important;
|
|
line-height: 24px !important;
|
|
font-family: "Bitstream Vera Sans Mono", Consolas, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei";
|
|
}
|
|
|
|
|
|
|
|
.ivu-modal-content {
|
|
border-radius: 0px !important;
|
|
}
|
|
|
|
button span {
|
|
font-size: 18px;
|
|
margin-left: -1px !important;
|
|
margin-bottom: 4px;
|
|
}
|
|
</style>
|
|
|
|
<template>
|
|
<div class="layout">
|
|
<Layout>
|
|
|
|
<Header class="header">
|
|
<Row>
|
|
<Col :xs="{ span: 24, offset: 0 }" :sm="{ span: 22, offset: 1 }" :md="{ span: 20, offset: 2 }"
|
|
:lg="{ span: 18, offset: 3 }" :xl="{ span: 16, offset: 4 }" :xxl="{ span: 16, offset: 4 }">
|
|
<Affix :offset-top="0">
|
|
<div style="background: white;width:100%;height:40px;">
|
|
<img style="height:40px;float:left;" alt="refresh flagnote" src="/static/favicon.png">
|
|
<div style="float:left;width:auto;">
|
|
|
|
<Button-group size="large">
|
|
|
|
<Button aria-label="share" type="error"
|
|
style="margin-left:5px; border-radius: 0px;font-size: 24px; font-family: Arial, sans-serif"
|
|
@click="showShareModel()" icon="md-cloud-done">{{ noteForm.ttlDesc }}</Button>
|
|
|
|
|
|
|
|
<Button aria-label="download text" v-show="model.showDownloadText" type="error"
|
|
style="margin-left:5px; border-radius: 0px; font-size: 22px;" @click="downLoadText()"
|
|
icon="md-download"></Button>
|
|
|
|
|
|
|
|
</Button-group>
|
|
|
|
</div>
|
|
|
|
<div style="float:right;width:auto;">
|
|
<Button-group size="large">
|
|
<Button aria-label="to top" v-show="toTopState" type="text"
|
|
style="margin-left:0px; border-radius: 0px; font-size: 28px;color:red;line-height: 20px;"
|
|
@click="toTop()" icon="ios-arrow-up" ghost></Button>
|
|
|
|
<Button aria-label="create note" type="error"
|
|
style="margin-left:10px; border-radius: 0px;font-size: 24px;" @click="createNote()"
|
|
icon="md-add"></Button>
|
|
<Button aria-label="delete note" type="error"
|
|
style="margin-left:5px; border-radius: 0px;font-size: 24px;" @click="showDeleteModel()"
|
|
icon="md-trash"></Button>
|
|
</Button-group>
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
</Affix>
|
|
|
|
|
|
|
|
</Col>
|
|
</Row>
|
|
</Header>
|
|
|
|
<Content class="content">
|
|
<div style="min-height: 650px;">
|
|
<Row>
|
|
<Col :xs="{ span: 24, offset: 0 }" :sm="{ span: 22, offset: 1 }" :md="{ span: 20, offset: 2 }"
|
|
:lg="{ span: 18, offset: 3 }" :xl="{ span: 16, offset: 4 }" :xxl="{ span: 16, offset: 4 }">
|
|
<Card :padding="0">
|
|
<div id="wrapper" style="border-left: 0px solid #FF3366;" @contextmenu="showMenu">
|
|
<vue-context-menu :contextMenuData="contextMenuData" @selectAllText="selectAllText"
|
|
@copySelectedText="copySelectedText" @copyAllText="copyAllText">
|
|
</vue-context-menu>
|
|
<div id="noteText" style="text-align: left;min-height: 650px;" class="monoFt"
|
|
v-html="noteForm.escapeText">
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
</Col>
|
|
</Row>
|
|
</div>
|
|
</Content>
|
|
|
|
|
|
|
|
|
|
<Footer class="layout-footer-center">
|
|
2022 © flagnote.com
|
|
</Footer>
|
|
|
|
</Layout>
|
|
|
|
<Modal v-model="model.showShare" width="330" footer-hide class-name="qrmodal" :styles="{ borderRadius: 0 }">
|
|
<p style="text-align: center;">
|
|
<canvas id="qrimg" class=""></canvas>
|
|
</p>
|
|
<p style="text-align: center;">
|
|
<span id="tag-copy" class="noteUrl" :data-clipboard-text="noteForm.noteUrl" data-clipboard-action="copy">{{
|
|
noteForm.noteUrl
|
|
}}</span>
|
|
</p>
|
|
</Modal>
|
|
|
|
<Modal v-model="model.showDelete" width="330" footer-hide class-name="fnmodal" :styles="{ borderRadius: 0 }">
|
|
<p style="text-align: center;font-size:medium;margin-bottom: 20px;">
|
|
{{ $t("message.askTodelete") }}
|
|
</p>
|
|
<p style="text-align: center;">
|
|
<Button type="error" :loading="model.delete" style="border-radius: 0px;" @click="dropNote()">{{ $t("button.yes")
|
|
}}</Button>
|
|
</p>
|
|
</Modal>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</template>
|
|
|
|
<script>
|
|
|
|
import { aesDecrypt, md5, unzip } from "@/libs/secret";
|
|
import Jquery from "jquery";
|
|
import { getSecretKey, getStoreKey } from "@/api/lock";
|
|
import { deleteNote, getNote } from "@/api/note";
|
|
import storage from "@/libs/storage";
|
|
import { getEscapeText } from "@/libs/noteStorage";
|
|
import QRCode from "qrcode";
|
|
import Clipboard from "clipboard";
|
|
import { saveAs } from 'file-saver';
|
|
import { isWeixin, getNoteUrl } from "@/libs/utils";
|
|
|
|
export default {
|
|
name: 'ViewNote',
|
|
components: {},
|
|
props: {},
|
|
data() {
|
|
return {
|
|
noteForm: {
|
|
url: '',
|
|
noteUrl: '',
|
|
text: '',
|
|
escapeText: '',
|
|
key: '',
|
|
md5: '',
|
|
lock: 0,
|
|
ttl: 3600,
|
|
ttlDesc: '-- : --',
|
|
},
|
|
secret: {
|
|
storeKey: '',
|
|
secretKey: '',
|
|
cipher: '',
|
|
currentTime: '',
|
|
},
|
|
state: {
|
|
lock: 0,
|
|
locking: 0,
|
|
initTime: null,
|
|
initTtl: null,
|
|
commited: 0,
|
|
},
|
|
model: {
|
|
showDelete: false,
|
|
showShare: false,
|
|
deleting: false,
|
|
showDownloadText: false,
|
|
},
|
|
toTopState: false,
|
|
contextMenuData: {
|
|
menuName: 'textMenu',
|
|
//菜单显示的位置
|
|
axis: {
|
|
x: null,
|
|
y: null
|
|
},
|
|
//菜单选项
|
|
menulists: [{
|
|
fnHandler: 'selectAllText', //绑定事件
|
|
btnName: this.$t("button.selectAll") //菜单名称
|
|
}, {
|
|
fnHandler: 'copySelectedText',
|
|
btnName: this.$t("button.copy")
|
|
}, {
|
|
fnHandler: 'copyAllText',
|
|
btnName: this.$t("button.copyAll")
|
|
}]
|
|
},
|
|
tempFragment: null,
|
|
|
|
}
|
|
},
|
|
created() {
|
|
// read $route
|
|
this.noteForm.key = this.$route.params.name;
|
|
let noteMeta = this.$route.meta.noteMeta;
|
|
|
|
//ipad chrome url not redirect
|
|
let path = location.pathname;
|
|
if ("/" == path) {
|
|
history.pushState('', '', '/' + this.noteForm.key);
|
|
}
|
|
|
|
//wx does not show downloadText
|
|
this.model.showDownloadText = !isWeixin();
|
|
|
|
this.noteForm.noteUrl = getNoteUrl(this.noteForm.key);
|
|
this.secret.storeKey = getStoreKey(this.noteForm.key);
|
|
|
|
if (noteMeta) {
|
|
this.state.lock = noteMeta.lock;
|
|
this.state.initTime = new Date().getTime();
|
|
this.state.initTtl = noteMeta.ttl;
|
|
this.secret.currentTime = noteMeta.currentTime;
|
|
this.secret.cipher = "00000000000000000000000000000000";//noteMeta.cipher; //读者有没有值可配置
|
|
this.noteForm.md5 = noteMeta.md5;
|
|
this.noteForm.ttl = noteMeta.ttl;
|
|
|
|
this.startClock();
|
|
|
|
storage.local.dynamicClear();
|
|
|
|
this.loadText();
|
|
|
|
this.bindCtrlAllEvent();
|
|
this.bindCopyUrlEvent();
|
|
this.bindToTopEvent();
|
|
this.bindMouseEvent();
|
|
|
|
} else {
|
|
alert("Unconnected.");
|
|
}
|
|
|
|
},
|
|
mounted() {
|
|
this.bindCopyTextEvent();
|
|
|
|
const myObserver = new ResizeObserver(entries => {
|
|
// iterate over the entries, do something.
|
|
entries.forEach(entry => {
|
|
let affix = document.querySelector('.ivu-affix');
|
|
if (affix) {
|
|
affix.setAttribute("style", "top: 0px; width: " + entry.contentRect.width + "px;");
|
|
}
|
|
|
|
});
|
|
});
|
|
|
|
const someOtherEl = document.querySelector('#wrapper');
|
|
myObserver.observe(someOtherEl);
|
|
|
|
},
|
|
updated() {
|
|
},
|
|
beforeDestroy() {
|
|
},
|
|
destroyed() {
|
|
},
|
|
computed: {},
|
|
watch: {},
|
|
methods: {
|
|
selectAllText() {
|
|
var element = document.getElementById("noteText");
|
|
if (window.getSelection) {
|
|
let selection = window.getSelection();
|
|
let range = document.createRange();
|
|
range.selectNodeContents(element);
|
|
selection.removeAllRanges();
|
|
selection.addRange(range);
|
|
}
|
|
},
|
|
copySelectedText() {
|
|
|
|
},
|
|
copyAllText() {
|
|
|
|
},
|
|
showMenu(event) {
|
|
event.preventDefault()
|
|
var x = event.clientX
|
|
var y = event.clientY
|
|
// Get the current location
|
|
this.contextMenuData.axis = {
|
|
x, y
|
|
}
|
|
},
|
|
downLoadText() {
|
|
var blob = new Blob([this.noteForm.text], { type: "application/octet-stream;charset=utf-8" });
|
|
saveAs(blob, this.noteForm.key + ".txt");
|
|
},
|
|
toTop() {
|
|
window.scrollTo(0, 0);
|
|
},
|
|
startClock() {
|
|
let that = this;
|
|
window.setInterval(function () {
|
|
let ittl = parseInt(that.noteForm.ttl / 1000);
|
|
let mins = parseInt(ittl / 60);
|
|
if (mins < 0) {
|
|
mins = "00";
|
|
} else if (mins < 10) {
|
|
mins = "0" + mins;
|
|
}
|
|
|
|
let seds = parseInt(ittl % 60);
|
|
|
|
if (seds < 0) {
|
|
seds = "00";
|
|
} else if (seds < 10) {
|
|
seds = "0" + seds;
|
|
}
|
|
that.noteForm.ttlDesc = mins + ":" + seds;
|
|
that.noteForm.ttl = that.state.initTtl - (new Date().getTime() - that.state.initTime);
|
|
|
|
if (that.noteForm.ttl <= 0) {
|
|
storage.local.delete(that.secret.storeKey + '.text');
|
|
location.reload();
|
|
}
|
|
}, 1000)
|
|
},
|
|
decryptNote() {
|
|
// let password = "123456";
|
|
// let secretKey = getSecretKey(this.noteForm.key, password);
|
|
// this.loadText(secretKey);
|
|
|
|
},
|
|
createNote() {
|
|
window.open("/");
|
|
},
|
|
showShareModel() {
|
|
this.model.showShare = true;
|
|
let qrimg = document.getElementById("qrimg");
|
|
let qrurl = "https://flagnote.com/" + this.noteForm.key;
|
|
var opts = {
|
|
errorCorrectionLevel: 'Q',
|
|
type: 'image/jpeg',
|
|
quality: 0.9,
|
|
height: 192,
|
|
width: 192,
|
|
margin: 1,
|
|
color: {
|
|
dark: "#ed4014",
|
|
light: "#FFFFFF"
|
|
}
|
|
}
|
|
QRCode.toCanvas(qrimg, qrurl, opts)
|
|
},
|
|
showDeleteModel() {
|
|
this.model.showDelete = true;
|
|
},
|
|
dropNote() {
|
|
this.model.deleting = true;
|
|
let that = this;
|
|
deleteNote(this.noteForm.key).then(res => {
|
|
if (res) {
|
|
storage.local.delete(that.secret.storeKey + '.text');
|
|
location.reload();
|
|
} else {
|
|
that.model.deleting = false;
|
|
}
|
|
});
|
|
},
|
|
loadText() {
|
|
let password = '';
|
|
|
|
if (this.noteForm.lock == 1) {
|
|
password = "FLAGNOTE"; //默认密码
|
|
}
|
|
|
|
let secretKey = getSecretKey(this.noteForm.key, password);
|
|
let storeText = storage.local.getText(this.secret.storeKey + '.text');
|
|
if (!storeText || (md5(storeText.substring(51)) != this.noteForm.md5)) {
|
|
// local is useless
|
|
let note = getNote(this.noteForm.key);
|
|
// if lack of local , not set local
|
|
if (storage.local.getAvailableSize() > 1 * 1024 * 1024) {
|
|
storeText = this.noteForm.lock + '|' + this.secret.cipher + '|1|' + this.secret.currentTime + '|' + note.text;
|
|
storage.local.setText(this.secret.storeKey + '.text', storeText);
|
|
}
|
|
} else {
|
|
// local is usable, and set commited flag
|
|
var starray = storeText.split('|');
|
|
if ("0" == starray[2]) {
|
|
storage.local.setText(this.secret.storeKey + '.text', starray[0] + "|" + starray[1] + "|1|" + starray[3] + "|" + starray[4]);
|
|
}
|
|
}
|
|
|
|
if (storeText) {
|
|
storeText = unzip(storeText.split('|')[4]);
|
|
let plainText = aesDecrypt(storeText, secretKey);
|
|
if (plainText.startsWith("FLAGNOTE#")) {
|
|
this.noteForm.text = plainText.substring(9);
|
|
this.noteForm.escapeText = getEscapeText(this.noteForm.text);
|
|
}
|
|
}
|
|
},
|
|
bindToTopEvent() {
|
|
let that = this;
|
|
window.onscroll = function () {
|
|
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
|
|
if (scrollTop >= 20) {
|
|
that.toTopState = true;
|
|
} else {
|
|
that.toTopState = false;
|
|
}
|
|
}
|
|
},
|
|
bindCopyUrlEvent() {
|
|
let that = this;
|
|
var clipboard = new Clipboard("#tag-copy")
|
|
clipboard.on('success', function () {
|
|
that.$Message.success({ content: 'url copied' });
|
|
});
|
|
|
|
clipboard.on('error', function () {
|
|
that.$Message.error('not allow to copy');
|
|
});
|
|
},
|
|
bindCopyTextEvent() {
|
|
|
|
let ele1 = document.getElementsByClassName("vue-contextmenuName-textMenu")[0].children[1].children[0].children[0];
|
|
let ele2 = document.getElementsByClassName("vue-contextmenuName-textMenu")[0].children[2].children[0].children[0];
|
|
|
|
let that = this;
|
|
|
|
const clipboard1 = new Clipboard(ele1, { // 绑定需要的触发的dom
|
|
text: function () {
|
|
let fragment = that.tempFragment;
|
|
|
|
if (fragment) {
|
|
let copyText = "";
|
|
|
|
fragment.childNodes.forEach(element => {
|
|
if (3 == element.nodeType) {
|
|
copyText += element.textContent.replace(new RegExp('\u00a0', 'gm'), " ");
|
|
} else if (1 == element.nodeType) {
|
|
if ("BR" == element.nodeName) {
|
|
copyText += "\r\n";
|
|
} else if ("PRE" == element.nodeName) {
|
|
copyText += "\t";
|
|
}
|
|
}
|
|
});
|
|
|
|
return copyText;
|
|
}
|
|
return null;
|
|
}
|
|
});
|
|
clipboard1.on('success', function () {
|
|
that.$Message.success({ content: 'selected text copied' });
|
|
});
|
|
clipboard1.on('error', function () {
|
|
that.$Message.error('not allow to copy');
|
|
});
|
|
|
|
const clipboard2 = new Clipboard(ele2, { // 绑定需要的触发的dom
|
|
text: function () {
|
|
return that.noteForm.text;
|
|
}
|
|
});
|
|
clipboard2.on('success', function () {
|
|
that.$Message.success({ content: 'text copied' });
|
|
});
|
|
clipboard2.on('error', function () {
|
|
that.$Message.error('not allow to copy');
|
|
});
|
|
},
|
|
bindCtrlAllEvent() {
|
|
if (window.getSelection) {
|
|
|
|
Jquery(document).keydown(function (e) {
|
|
if ((e.ctrlKey || e.metaKey) && e.keyCode == 65) {
|
|
e.preventDefault();
|
|
|
|
var element = document.getElementById("noteText");
|
|
|
|
let selection = window.getSelection();
|
|
let range = document.createRange();
|
|
range.selectNodeContents(element);
|
|
selection.removeAllRanges();
|
|
selection.addRange(range);
|
|
|
|
}
|
|
});
|
|
|
|
}
|
|
},
|
|
bindMouseEvent() {
|
|
let that = this;
|
|
document.addEventListener('mouseup', function (e) {
|
|
if (e.button === 2) {
|
|
if (window.getSelection) {
|
|
let sel = window.getSelection();
|
|
if (sel.rangeCount > 0) {
|
|
that.tempFragment = sel.getRangeAt(0).cloneContents();
|
|
}
|
|
}
|
|
}
|
|
}, false)
|
|
}
|
|
|
|
}
|
|
}
|
|
</script>
|
|
|