tag1
This commit is contained in:
23
.gitignore
vendored
Normal file
23
.gitignore
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/dist
|
||||||
|
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
25
README.md
25
README.md
@@ -1 +1,24 @@
|
|||||||
# flagnote-web
|
# flagnote-web
|
||||||
|
|
||||||
|
## Project setup
|
||||||
|
```
|
||||||
|
yarn install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compiles and hot-reloads for development
|
||||||
|
```
|
||||||
|
yarn serve
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compiles and minifies for production
|
||||||
|
```
|
||||||
|
yarn build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lints and fixes files
|
||||||
|
```
|
||||||
|
yarn lint
|
||||||
|
```
|
||||||
|
|
||||||
|
### Customize configuration
|
||||||
|
See [Configuration Reference](https://cli.vuejs.org/config/).
|
||||||
|
|||||||
5
babel.config.js
Normal file
5
babel.config.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module.exports = {
|
||||||
|
presets: [
|
||||||
|
'@vue/cli-plugin-babel/preset'
|
||||||
|
]
|
||||||
|
}
|
||||||
19
jsconfig.json
Normal file
19
jsconfig.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es5",
|
||||||
|
"module": "esnext",
|
||||||
|
"baseUrl": "./",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"paths": {
|
||||||
|
"@/*": [
|
||||||
|
"src/*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"lib": [
|
||||||
|
"esnext",
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"scripthost"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
55
package.json
Normal file
55
package.json
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
"name": "flagnote-web",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"serve": "vue-cli-service serve",
|
||||||
|
"build": "vue-cli-service build",
|
||||||
|
"lint": "vue-cli-service lint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^0.26.0",
|
||||||
|
"core-js": "^3.8.3",
|
||||||
|
"crypto-js": "^4.1.1",
|
||||||
|
"escape-html": "^1.0.3",
|
||||||
|
"html2canvas": "^1.4.1",
|
||||||
|
"jquery": "^3.6.0",
|
||||||
|
"nprogress": "^0.2.0",
|
||||||
|
"pako": "^2.0.4",
|
||||||
|
"view-design": "^4.0.0",
|
||||||
|
"vue": "^2.6.14",
|
||||||
|
"vue-axios": "^3.4.1",
|
||||||
|
"vue-router": "^3.5.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.12.16",
|
||||||
|
"@babel/eslint-parser": "^7.12.16",
|
||||||
|
"@vue/cli-plugin-babel": "~5.0.0",
|
||||||
|
"@vue/cli-plugin-eslint": "~5.0.0",
|
||||||
|
"@vue/cli-plugin-router": "~5.0.0",
|
||||||
|
"@vue/cli-service": "~5.0.0",
|
||||||
|
"eslint": "^7.32.0",
|
||||||
|
"eslint-plugin-vue": "^8.0.3",
|
||||||
|
"vue-cli-plugin-iview": "^2.0.0",
|
||||||
|
"vue-template-compiler": "^2.6.14"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"root": true,
|
||||||
|
"env": {
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"plugin:vue/essential",
|
||||||
|
"eslint:recommended"
|
||||||
|
],
|
||||||
|
"parserOptions": {
|
||||||
|
"parser": "@babel/eslint-parser"
|
||||||
|
},
|
||||||
|
"rules": {}
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
"> 1%",
|
||||||
|
"last 2 versions",
|
||||||
|
"not dead"
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
BIN
public/favicon.png
Normal file
BIN
public/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
BIN
public/favicon.png.bak
Normal file
BIN
public/favicon.png.bak
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
public/favicon.png.bak2
Normal file
BIN
public/favicon.png.bak2
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
44
public/index.html
Normal file
44
public/index.html
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
|
<link rel="icon" href="<%= BASE_URL %>favicon.png">
|
||||||
|
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script src="https://www.recaptcha.net/recaptcha/api.js?render=6Le0CJwfAAAAAGk0J6aDngi7Dya-b0qyFRdIf6Wu"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
const CAPTCHA_CLIENT_SECRET = "6Le0CJwfAAAAAGk0J6aDngi7Dya-b0qyFRdIf6Wu";
|
||||||
|
window.onload = () => {
|
||||||
|
document.querySelector('button').addEventListener('click', () => {
|
||||||
|
grecaptcha.execute(CAPTCHA_CLIENT_SECRET, {action: 'homepage'}).then(function (token) {
|
||||||
|
console.log('客户端token:' + token);
|
||||||
|
fetch('/recaptcha/validate?token=' + token, {
|
||||||
|
method: 'GET'
|
||||||
|
}).then(response => {
|
||||||
|
if (response.ok) {
|
||||||
|
response.json().then(message => {
|
||||||
|
console.log('服务端验证');
|
||||||
|
console.log(message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body style="background-color: #dddddd;">
|
||||||
|
<noscript>
|
||||||
|
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
|
||||||
|
Please enable it to continue.</strong>
|
||||||
|
</noscript>
|
||||||
|
<div id="app"></div>
|
||||||
|
<!-- built files will be auto injected -->
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
39
src/App.vue
Normal file
39
src/App.vue
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<template>
|
||||||
|
<div id="app">
|
||||||
|
<router-view></router-view>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'app',
|
||||||
|
components: {}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#app {
|
||||||
|
font-family: 'Avenir', Helvetica, Arial, sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
text-align: center;
|
||||||
|
font-size: medium;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial,
|
||||||
|
"PingFang SC", "Hiragino Sans GB", "Heiti SC", "WenQuanYi Micro Hei",
|
||||||
|
sans-serif;
|
||||||
|
background-color: #dddddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monoFt {
|
||||||
|
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
#nprogress .bar {
|
||||||
|
background: red !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
40
src/api/lock.js
Normal file
40
src/api/lock.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
// import Jquery from "jquery";
|
||||||
|
import {aesEncrypt, md5} from "@/libs/secret";
|
||||||
|
|
||||||
|
// export function getSecretKey(key, password) {
|
||||||
|
// console.log("getSecretKey");
|
||||||
|
// let secretKey = '';
|
||||||
|
// Jquery.ajax({
|
||||||
|
// method: 'POST',
|
||||||
|
// url: '/note/' + key + "/secretKey",
|
||||||
|
// async: false,
|
||||||
|
// contentType: 'application/json',
|
||||||
|
// dataTeyp: 'json',
|
||||||
|
// data: JSON.stringify({'password': password}),
|
||||||
|
// success: function (data) {
|
||||||
|
// secretKey = data;
|
||||||
|
// },
|
||||||
|
// error: function () {
|
||||||
|
// alert('服务器链接异常 ');
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
// return secretKey;
|
||||||
|
// }
|
||||||
|
|
||||||
|
export function getStoreKey(key) {
|
||||||
|
return md5(aesEncrypt(key, key));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSecretKey(cipher, password) {
|
||||||
|
if(!password){
|
||||||
|
password = '';
|
||||||
|
}
|
||||||
|
return md5(cipher + password);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
79
src/api/note.js
Normal file
79
src/api/note.js
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
import Jquery from "jquery";
|
||||||
|
import {getStoreKey} from "@/api/lock";
|
||||||
|
import storage from "@/libs/storage";
|
||||||
|
|
||||||
|
export function saveNote(noteForm) {
|
||||||
|
|
||||||
|
let storeKey = getStoreKey(noteForm.key);
|
||||||
|
let storeText = storage.local.getText(storeKey + '.text');
|
||||||
|
let note = {
|
||||||
|
"lock": storeText.substring(0, 1),
|
||||||
|
"text": storeText.substring(35)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("n"+ storeText)
|
||||||
|
|
||||||
|
|
||||||
|
return axios({
|
||||||
|
url: '/note/' + noteForm.key,
|
||||||
|
method: 'post',
|
||||||
|
data: note
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// export function getSecretKey(key, password) {
|
||||||
|
// console.log("getSecretKey");
|
||||||
|
// let secretKey = '';
|
||||||
|
// Jquery.ajax({
|
||||||
|
// method: 'POST',
|
||||||
|
// url: '/note/' + key + "/secretKey",
|
||||||
|
// async: false,
|
||||||
|
// contentType: 'application/json',
|
||||||
|
// dataTeyp: 'json',
|
||||||
|
// data: JSON.stringify({'password': password}),
|
||||||
|
// success: function (data) {
|
||||||
|
// secretKey = data;
|
||||||
|
// },
|
||||||
|
// error: function () {
|
||||||
|
// alert('服务器链接异常 ');
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
// return secretKey;
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
export function getNoteMeta(key) {
|
||||||
|
console.log("getNoteMeta");
|
||||||
|
let noteMeta = {};
|
||||||
|
Jquery.ajax({
|
||||||
|
method: 'GET',
|
||||||
|
url: '/note/' + key + "/noteMeta",
|
||||||
|
async: false,
|
||||||
|
success: function (data) {
|
||||||
|
noteMeta = eval(data); //eval("(" + data + ")");
|
||||||
|
},
|
||||||
|
error: function () {
|
||||||
|
noteMeta = null;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return noteMeta;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getKeyMeta() {
|
||||||
|
console.log("getKeyMeta");
|
||||||
|
let keyMeta = {};
|
||||||
|
Jquery.ajax({
|
||||||
|
method: 'GET',
|
||||||
|
url: '/note/keyMeta',
|
||||||
|
async: false,
|
||||||
|
success: function (data) {
|
||||||
|
keyMeta = eval(data); //eval("(" + data + ")");
|
||||||
|
},
|
||||||
|
error: function () {
|
||||||
|
keyMeta = null;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return keyMeta;
|
||||||
|
}
|
||||||
BIN
src/assets/logo.png
Normal file
BIN
src/assets/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.7 KiB |
44
src/libs/noteStorage.js
Normal file
44
src/libs/noteStorage.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import {zip, aesEncrypt} from '../libs/secret'
|
||||||
|
import storage from "@/libs/storage";
|
||||||
|
import {getSecretKey} from "@/api/lock";
|
||||||
|
import escapeHtml from "escape-html";
|
||||||
|
|
||||||
|
export function setStoreText(text, secret, password) {
|
||||||
|
if (!text) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!password) {
|
||||||
|
password = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
text = "FLAGNOTE#" + text;
|
||||||
|
|
||||||
|
let secretKey = getSecretKey(secret.cipher, password);
|
||||||
|
let storeText = aesEncrypt(text, secretKey);
|
||||||
|
storeText = zip(storeText);
|
||||||
|
|
||||||
|
console.log("sssssssss" + secret.cipher)
|
||||||
|
console.log("sssssssss" + password)
|
||||||
|
console.log("sssssssss" + secretKey)
|
||||||
|
console.log("s" + storeText)
|
||||||
|
|
||||||
|
let lock = '0';
|
||||||
|
if (password) {
|
||||||
|
lock = '1';
|
||||||
|
}
|
||||||
|
|
||||||
|
storage.local.setText(secret.storeKey + '.text', lock + '|' + secret.cipher + '|' + storeText);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function getEscapeText(text) {
|
||||||
|
let textEscape = escapeHtml(text);
|
||||||
|
textEscape = textEscape.replaceAll(" ", " ");
|
||||||
|
textEscape = textEscape.replaceAll("\r\n", "<br/>");
|
||||||
|
textEscape = textEscape.replaceAll("\r", "<br/>");
|
||||||
|
textEscape = textEscape.replaceAll("\n", "<br/>");
|
||||||
|
textEscape = textEscape.replaceAll("\t", " ");
|
||||||
|
|
||||||
|
return textEscape;
|
||||||
|
}
|
||||||
57
src/libs/secret.js
Normal file
57
src/libs/secret.js
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import CryptoJS from 'crypto-js'
|
||||||
|
import pako from 'pako'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @word 要加密的内容
|
||||||
|
* @keyWord String 服务器随机返回的关键字
|
||||||
|
* */
|
||||||
|
|
||||||
|
//加密
|
||||||
|
export function md5(word, keyWord = 'F1agn0te') {
|
||||||
|
let srcWords = CryptoJS.enc.Utf8.parse(word + '_' + keyWord);
|
||||||
|
let encrypted = CryptoJS.MD5(srcWords);
|
||||||
|
return encrypted.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
//加密
|
||||||
|
export function aesEncrypt(word, keyWord = 'F1agn0te') {
|
||||||
|
let key = CryptoJS.enc.Utf8.parse(keyWord);
|
||||||
|
let srcWords = CryptoJS.enc.Utf8.parse(word);
|
||||||
|
let encrypted = CryptoJS.AES.encrypt(srcWords, key, {mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7});
|
||||||
|
return encrypted.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
//解密
|
||||||
|
export function aesDecrypt(word, keyWord = 'F1agn0te') {
|
||||||
|
let key = CryptoJS.enc.Utf8.parse(keyWord);
|
||||||
|
let decrypt = CryptoJS.AES.decrypt(word, key, {mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7});
|
||||||
|
return CryptoJS.enc.Utf8.stringify(decrypt).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function encode(text) {
|
||||||
|
return btoa(encodeURIComponent(text))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decode(text) {
|
||||||
|
return decodeURIComponent(atob(text))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unzip(text) {
|
||||||
|
let charData = text.split(',').map(function (x) {
|
||||||
|
return parseInt(x)
|
||||||
|
});
|
||||||
|
let binData = new Uint8Array(charData);
|
||||||
|
let data = pako.ungzip(binData);
|
||||||
|
//text = String.fromCharCode.apply(null, new Uint8Array(data));
|
||||||
|
text = new Uint8Array(data).reduce(function (data, byte) {
|
||||||
|
return data + String.fromCharCode(byte);
|
||||||
|
}, '');
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function zip(text) {
|
||||||
|
text = pako.gzip(text, {to: 'string'});
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
46
src/libs/storage.js
Normal file
46
src/libs/storage.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
class Storage {
|
||||||
|
constructor() {
|
||||||
|
this.session = new Session();
|
||||||
|
this.local = new Local();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Session {
|
||||||
|
|
||||||
|
setObject(key, value) {
|
||||||
|
sessionStorage.setItem(key, JSON.stringify(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
getObject(key) {
|
||||||
|
return JSON.parse(sessionStorage.getItem(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
setText(key, value) {
|
||||||
|
sessionStorage.setItem(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
getText(key) {
|
||||||
|
return sessionStorage.getItem(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Local {
|
||||||
|
setObject(key, value) {
|
||||||
|
localStorage.setItem(key, JSON.stringify(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
getObject(key) {
|
||||||
|
return JSON.parse(localStorage.getItem(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
setText(key, value) {
|
||||||
|
localStorage.setItem(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
getText(key) {
|
||||||
|
return localStorage.getItem(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const storage = new Storage();
|
||||||
|
export default storage;
|
||||||
41
src/main.js
Normal file
41
src/main.js
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
import App from './App.vue'
|
||||||
|
import router from './router'
|
||||||
|
import axios from 'axios'
|
||||||
|
import VueAxios from 'vue-axios'
|
||||||
|
import './plugins/iview.js'
|
||||||
|
|
||||||
|
//import NProgress from 'nprogress';
|
||||||
|
//import 'nprogress/nprogress.css';
|
||||||
|
|
||||||
|
//顶部页面加载条
|
||||||
|
|
||||||
|
// NProgress.configure({
|
||||||
|
// easing: 'ease',
|
||||||
|
// speed: 500,
|
||||||
|
// showSpinner: false,
|
||||||
|
// trickleSpeed: 200,
|
||||||
|
// minimum: 0.3
|
||||||
|
// })
|
||||||
|
|
||||||
|
// //路由监听
|
||||||
|
// router.beforeEach((to, from, next) => {
|
||||||
|
// NProgress.start();
|
||||||
|
// next();
|
||||||
|
// });
|
||||||
|
|
||||||
|
// //路由跳转结束
|
||||||
|
// router.afterEach(() => {
|
||||||
|
// NProgress.done()
|
||||||
|
// })
|
||||||
|
|
||||||
|
Vue.use(VueAxios, axios)
|
||||||
|
|
||||||
|
Vue.config.productionTip = false
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
router,
|
||||||
|
render: h => h(App)
|
||||||
|
}).$mount('#app')
|
||||||
|
|
||||||
|
|
||||||
6
src/plugins/iview.js
Normal file
6
src/plugins/iview.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
import ViewUI from 'view-design'
|
||||||
|
|
||||||
|
Vue.use(ViewUI)
|
||||||
|
|
||||||
|
import 'view-design/dist/styles/iview.css'
|
||||||
201
src/plugins/tln/LICENSE
Normal file
201
src/plugins/tln/LICENSE
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright 2018 Matheus Avellar
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
25
src/plugins/tln/README.md
Normal file
25
src/plugins/tln/README.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Textarea with Line Numbers
|
||||||
|
|
||||||
|
<p align="center"> <i>I'm definitely not very good at naming projects</i> </p>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
This is a simple script to add dynamic line numbers to a textarea. By adding
|
||||||
|
`tln.js` and `tln.css` (or their corresponding minified versions) to an HTML
|
||||||
|
file and then calling the `TLN.append_line_numbers( id )` function, the
|
||||||
|
`<textarea>` with the specified ID will be *numberified™*.
|
||||||
|
|
||||||
|
When and if you get sick of those pesky numbers on the side of your textarea,
|
||||||
|
you can call `TLN.remove_line_numbers( id )` and they'll poof out of existence,
|
||||||
|
as if they were never there.
|
||||||
|
|
||||||
|
You can check the [demo](https://matheusavellar.github.io/textarea-line-numbers/demo.html),
|
||||||
|
or this sample screenshot of what it looks like (with some additional CSS
|
||||||
|
styling):
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img alt="Screenshot of sample code in which each line is numbered accordingly" src="http://i.imgur.com/imEKehF.png"/>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
**Note:** Example code and file header ("`file.bc`") seen in screenshot are sold
|
||||||
|
separately and may or may not be available at your local convenience store.
|
||||||
36
src/plugins/tln/demo.html
Normal file
36
src/plugins/tln/demo.html
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<title> Textarea with line numbers </title>
|
||||||
|
<style type="text/css">
|
||||||
|
/* none of this truly matters, except maybe the wrapper size */
|
||||||
|
body{text-align:center}
|
||||||
|
h1,button{margin:25px;}
|
||||||
|
label,input{margin:0;vertical-align:middle;}
|
||||||
|
#wrapper{border:1px solid red;position:relative;
|
||||||
|
height:500px;width:500px;margin:15px auto}
|
||||||
|
#wrapper::before {
|
||||||
|
content:"wrapper";text-transform: uppercase;left:0px;
|
||||||
|
position:absolute;top:-18px;font-size:16px;color:red; }
|
||||||
|
</style>
|
||||||
|
<link rel="stylesheet" type="text/css" href="./tln.min.css"/>
|
||||||
|
<script type="text/javascript" src="./tln.min.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1> Example </h1>
|
||||||
|
<button onclick="TLN.append_line_numbers('test')"> NUMBERIFY! </button>
|
||||||
|
<button onclick="TLN.remove_line_numbers('test')"> UN-NUMBERIFY! </button>
|
||||||
|
<div id="wrapper">
|
||||||
|
<textarea id="test" class="banana-cake"></textarea>
|
||||||
|
</div>
|
||||||
|
<label>
|
||||||
|
Width:
|
||||||
|
<input oninput="wrapper.style.width=this.value+'px';" type="range" value="500" min="100" max="700" />
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Height:
|
||||||
|
<input oninput="wrapper.style.height=this.value+'px';" type="range" value="500" min="100" max="700" />
|
||||||
|
</label>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
48
src/plugins/tln/tln.css
Normal file
48
src/plugins/tln/tln.css
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
.tln-active, .tln-wrapper, .tln-line {
|
||||||
|
margin: 0;
|
||||||
|
border: 0;
|
||||||
|
padding: 0;
|
||||||
|
outline: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
vertical-align: middle;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
.tln-active {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.625em;
|
||||||
|
width: calc(100% - 3em);
|
||||||
|
height: 100%;
|
||||||
|
font-size: 1em;
|
||||||
|
line-height: 1.5;
|
||||||
|
font-family: "Roboto Mono", monospace;
|
||||||
|
word-break: break-all;
|
||||||
|
border: 1px solid #aeaeae;
|
||||||
|
background-color: #fff;
|
||||||
|
resize: none;
|
||||||
|
overflow-wrap: normal;
|
||||||
|
overflow-x: auto;
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
.tln-wrapper {
|
||||||
|
width: 3em;
|
||||||
|
padding: 0.6875em 0.3125em 2.1875em;
|
||||||
|
height: 100%;
|
||||||
|
word-break: break-all;
|
||||||
|
overflow: hidden;
|
||||||
|
display: inline-block;
|
||||||
|
counter-reset: line;
|
||||||
|
}
|
||||||
|
.tln-line {
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
text-align: right;
|
||||||
|
line-height: 1.5;
|
||||||
|
font-size: 1em;
|
||||||
|
color: #aeaeae;
|
||||||
|
}
|
||||||
|
.tln-line::before {
|
||||||
|
counter-increment: line;
|
||||||
|
content: counter(line);
|
||||||
|
font-size: 1em;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
138
src/plugins/tln/tln.js
Normal file
138
src/plugins/tln/tln.js
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
const TLN = {
|
||||||
|
eventList: {},
|
||||||
|
update_line_numbers: function(ta, el) {
|
||||||
|
// Let's check if there are more or less lines than before
|
||||||
|
const line_count = ta.value.split("\n").length;
|
||||||
|
const child_count = el.children.length;
|
||||||
|
let difference = line_count - child_count;
|
||||||
|
// If there is any positive difference, we need to add more line numbers
|
||||||
|
if(difference > 0) {
|
||||||
|
// Create a fragment to work with so we only have to update DOM once
|
||||||
|
const frag = document.createDocumentFragment();
|
||||||
|
// For each new line we need to add,
|
||||||
|
while(difference > 0) {
|
||||||
|
// Create a <span>, add TLN class name, append to fragment and
|
||||||
|
// update difference
|
||||||
|
const line_number = document.createElement("span");
|
||||||
|
line_number.className = "tln-line";
|
||||||
|
frag.appendChild(line_number);
|
||||||
|
difference--;
|
||||||
|
}
|
||||||
|
// Append fragment (with <span> children) to our wrapper element
|
||||||
|
el.appendChild(frag);
|
||||||
|
}
|
||||||
|
// If, however, there's negative difference, we need to remove line numbers
|
||||||
|
while(difference < 0) {
|
||||||
|
// Simple stuff, remove last child and update difference
|
||||||
|
el.removeChild(el.lastChild);
|
||||||
|
difference++;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
append_line_numbers: function(id) {
|
||||||
|
// Get reference to desired <textarea>
|
||||||
|
const ta = document.getElementById(id);
|
||||||
|
// If getting reference to element fails, warn and leave
|
||||||
|
if(ta == null) {
|
||||||
|
return console.warn("[tln.js] Couldn't find textarea of id '"+id+"'");
|
||||||
|
}
|
||||||
|
// If <textarea> already has TLN active, warn and leave
|
||||||
|
if(ta.className.indexOf("tln-active") != -1) {
|
||||||
|
return console.warn("[tln.js] textarea of id '"+id+"' is already numbered");
|
||||||
|
}
|
||||||
|
// Otherwise, we're safe to add the class name and clear inline styles
|
||||||
|
ta.classList.add("tln-active");
|
||||||
|
ta.style = {};
|
||||||
|
|
||||||
|
// Create line numbers wrapper, insert it before <textarea>
|
||||||
|
const el = document.createElement("div");
|
||||||
|
el.className = "tln-wrapper";
|
||||||
|
ta.parentNode.insertBefore(el, ta);
|
||||||
|
// Call update to actually insert line numbers to the wrapper
|
||||||
|
TLN.update_line_numbers(ta, el);
|
||||||
|
// Initialize event listeners list for this element ID, so we can remove
|
||||||
|
// them later if needed
|
||||||
|
TLN.eventList[id] = [];
|
||||||
|
|
||||||
|
// Constant list of input event names so we can iterate
|
||||||
|
const __change_evts = [
|
||||||
|
"propertychange", "input", "keydown", "keyup"
|
||||||
|
];
|
||||||
|
// Default handler for input events
|
||||||
|
const __change_hdlr = function(ta, el) {
|
||||||
|
return function(e) {
|
||||||
|
// If pressed key is Left Arrow (when cursor is on the first character),
|
||||||
|
// or if it's Enter/Home, then we set horizontal scroll to 0
|
||||||
|
// Check for .keyCode, .which, .code and .key, because the web is a mess
|
||||||
|
// [Ref] stackoverflow.com/a/4471635/4824627
|
||||||
|
if((+ta.scrollLeft==10 && (e.keyCode==37||e.which==37
|
||||||
|
||e.code=="ArrowLeft"||e.key=="ArrowLeft"))
|
||||||
|
|| e.keyCode==36||e.which==36||e.code=="Home"||e.key=="Home"
|
||||||
|
|| e.keyCode==13||e.which==13||e.code=="Enter"||e.key=="Enter"
|
||||||
|
|| e.code=="NumpadEnter")
|
||||||
|
ta.scrollLeft = 0;
|
||||||
|
// Whether we scrolled or not, let's check for any line count updates
|
||||||
|
TLN.update_line_numbers(ta, el);
|
||||||
|
}
|
||||||
|
}(ta, el);
|
||||||
|
|
||||||
|
// Finally, iterate through those event names, and add listeners to
|
||||||
|
// <textarea> and to events list
|
||||||
|
/// TODO: Performance gurus: is this suboptimal? Should we only add a few
|
||||||
|
/// listeners? I feel the update method is optimal enough for this to not
|
||||||
|
/// impact too much things.
|
||||||
|
for(let i = __change_evts.length - 1; i >= 0; i--) {
|
||||||
|
ta.addEventListener(__change_evts[i], __change_hdlr);
|
||||||
|
TLN.eventList[id].push({
|
||||||
|
evt: __change_evts[i],
|
||||||
|
hdlr: __change_hdlr
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constant list of scroll event names so we can iterate
|
||||||
|
const __scroll_evts = [ "change", "mousewheel", "scroll" ];
|
||||||
|
// Default handler for scroll events (pretty self explanatory)
|
||||||
|
const __scroll_hdlr = function(ta, el) {
|
||||||
|
return function() { el.scrollTop = ta.scrollTop; }
|
||||||
|
}(ta, el);
|
||||||
|
// Just like before, iterate and add listeners to <textarea> and to list
|
||||||
|
/// TODO: Also just like before: performance?
|
||||||
|
for(let i = __scroll_evts.length - 1; i >= 0; i--) {
|
||||||
|
ta.addEventListener(__scroll_evts[i], __scroll_hdlr);
|
||||||
|
TLN.eventList[id].push({
|
||||||
|
evt: __scroll_evts[i],
|
||||||
|
hdlr: __scroll_hdlr
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
remove_line_numbers: function(id) {
|
||||||
|
// Get reference to <textarea>
|
||||||
|
const ta = document.getElementById(id);
|
||||||
|
// If getting reference to element fails, warn and leave
|
||||||
|
if(ta == null) {
|
||||||
|
return console.warn("[tln.js] Couldn't find textarea of id '"+id+"'");
|
||||||
|
}
|
||||||
|
// If <textarea> already doesn't have TLN active, warn and leave
|
||||||
|
if(ta.className.indexOf("tln-active") == -1) {
|
||||||
|
return console.warn("[tln.js] textarea of id '"+id+"' isn't numbered");
|
||||||
|
}
|
||||||
|
// Otherwise, remove class name
|
||||||
|
ta.classList.remove("tln-active");
|
||||||
|
|
||||||
|
// Remove previous sibling if it's our wrapper (otherwise, I guess 'woops'?)
|
||||||
|
const __wrapper_chck = ta.previousSibling;
|
||||||
|
if(__wrapper_chck.className == "tln-wrapper")
|
||||||
|
__wrapper_chck.remove();
|
||||||
|
|
||||||
|
// If somehow there's no event listeners list, we can leave
|
||||||
|
if(!TLN.eventList[id]) return;
|
||||||
|
// Otherwise iterate through listeners list and remove each one
|
||||||
|
for(let i = TLN.eventList[id].length - 1; i >= 0; i--) {
|
||||||
|
const evt = TLN.eventList[id][i];
|
||||||
|
ta.removeEventListener(evt.evt, evt.hdlr);
|
||||||
|
}
|
||||||
|
// Finally, delete the listeners list for that ID
|
||||||
|
delete TLN.eventList[id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TLN;
|
||||||
1
src/plugins/tln/tln.min.css
vendored
Normal file
1
src/plugins/tln/tln.min.css
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.tln-active,.tln-wrapper,.tln-line{margin:0;border:0;padding:0;outline:0;box-sizing:border-box;vertical-align:middle;list-style:none}.tln-active{display:inline-block;padding:.625em;width:calc(100% - 3em);height:100%;word-break:break-all;border:1px solid #aeaeae;background-color:#fff;resize:none;overflow-wrap:normal;overflow-x:auto;white-space:pre;font:1em/1.5 "Roboto Mono",monospace}.tln-wrapper{width:3em;padding:.6875em .3125em 2.1875em;height:100%;word-break:break-all;overflow:hidden;display:inline-block;counter-reset:line}.tln-line{width:100%;display:block;text-align:right;line-height:1.5;font-size:1em;color:#aeaeae}.tln-line::before{counter-increment:line;content:counter(line);font-size:1em;user-select:none}
|
||||||
1
src/plugins/tln/tln.min.js
vendored
Normal file
1
src/plugins/tln/tln.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
const TLN={eventList:{},update_line_numbers:function(e,t){let n=e.value.split("\n").length-t.children.length;if(n>0){const e=document.createDocumentFragment();for(;n>0;){const t=document.createElement("span");t.className="tln-line",e.appendChild(t),n--}t.appendChild(e)}for(;n<0;)t.removeChild(t.lastChild),n++},append_line_numbers:function(e){const t=document.getElementById(e);if(null==t)return console.warn("[tln.js] Couldn't find textarea of id '"+e+"'");if(-1!=t.className.indexOf("tln-active"))return console.warn("[tln.js] textarea of id '"+e+"' is already numbered");t.classList.add("tln-active"),t.style={};const n=document.createElement("div");n.className="tln-wrapper",t.parentNode.insertBefore(n,t),TLN.update_line_numbers(t,n),TLN.eventList[e]=[];const l=["propertychange","input","keydown","keyup"],o=function(e,t){return function(n){(10!=+e.scrollLeft||37!=n.keyCode&&37!=n.which&&"ArrowLeft"!=n.code&&"ArrowLeft"!=n.key)&&36!=n.keyCode&&36!=n.which&&"Home"!=n.code&&"Home"!=n.key&&13!=n.keyCode&&13!=n.which&&"Enter"!=n.code&&"Enter"!=n.key&&"NumpadEnter"!=n.code||(e.scrollLeft=0),TLN.update_line_numbers(e,t)}}(t,n);for(let n=l.length-1;n>=0;n--)t.addEventListener(l[n],o),TLN.eventList[e].push({evt:l[n],hdlr:o});const r=["change","mousewheel","scroll"],s=function(e,t){return function(){t.scrollTop=e.scrollTop}}(t,n);for(let n=r.length-1;n>=0;n--)t.addEventListener(r[n],s),TLN.eventList[e].push({evt:r[n],hdlr:s})},remove_line_numbers:function(e){const t=document.getElementById(e);if(null==t)return console.warn("[tln.js] Couldn't find textarea of id '"+e+"'");if(-1==t.className.indexOf("tln-active"))return console.warn("[tln.js] textarea of id '"+e+"' isn't numbered");t.classList.remove("tln-active");const n=t.previousSibling;if("tln-wrapper"==n.className&&n.remove(),TLN.eventList[e]){for(let n=TLN.eventList[e].length-1;n>=0;n--){const l=TLN.eventList[e][n];t.removeEventListener(l.evt,l.hdlr)}delete TLN.eventList[e]}}};
|
||||||
80
src/router/index.js
Normal file
80
src/router/index.js
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
import VueRouter from 'vue-router'
|
||||||
|
import EditNote from '@/views/EditNote.vue'
|
||||||
|
import ViewNote from '@/views/ViewNote.vue'
|
||||||
|
import ErrorView from '@/views/ErrorView.vue'
|
||||||
|
import {getKeyMeta, getNoteMeta} from "@/api/note";
|
||||||
|
import {getStoreKey} from "@/api/lock";
|
||||||
|
import storage from "@/libs/storage";
|
||||||
|
|
||||||
|
Vue.use(VueRouter)
|
||||||
|
|
||||||
|
let keyMeta = null;
|
||||||
|
|
||||||
|
function getNoteView() {
|
||||||
|
let path = location.pathname;
|
||||||
|
if (null != keyMeta) {
|
||||||
|
path = "/" + keyMeta.key;
|
||||||
|
}
|
||||||
|
|
||||||
|
let reg = /^\/[abcdefhikmnopqstuvwxyz23456789]{16}$/
|
||||||
|
if (!reg.test(path)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let key = path.substr(1, path.length - 1);
|
||||||
|
|
||||||
|
let storeKey = getStoreKey(key);
|
||||||
|
if (keyMeta) {
|
||||||
|
storage.session.setObject(storeKey + '.keyMeta', keyMeta);
|
||||||
|
return EditNote;
|
||||||
|
}
|
||||||
|
|
||||||
|
let noteMeta = null;
|
||||||
|
noteMeta = getNoteMeta(key);
|
||||||
|
if (!noteMeta || !noteMeta.key) {
|
||||||
|
return EditNote;
|
||||||
|
}
|
||||||
|
|
||||||
|
storage.session.setObject(storeKey + '.noteMeta', noteMeta);
|
||||||
|
return ViewNote;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getHomeRedirect() {
|
||||||
|
let path = location.pathname;
|
||||||
|
if (path != '/') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let km = getKeyMeta();
|
||||||
|
km.isNew = true;
|
||||||
|
//sessionStorage.setItem(km.getKey() + '.keyMeta', km);
|
||||||
|
keyMeta = km;
|
||||||
|
return '/' + keyMeta.key;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'home',
|
||||||
|
redirect: getHomeRedirect(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/error',
|
||||||
|
name: 'error',
|
||||||
|
component: ErrorView,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/:name([abcdefhikmnopqstuvwxyz23456789]{16})',
|
||||||
|
name: 'note',
|
||||||
|
component: getNoteView(),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const router = new VueRouter({
|
||||||
|
routes,
|
||||||
|
mode: 'history'
|
||||||
|
})
|
||||||
|
|
||||||
|
export default router
|
||||||
478
src/views/EditNote.vue
Normal file
478
src/views/EditNote.vue
Normal file
@@ -0,0 +1,478 @@
|
|||||||
|
<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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.noteUrl {
|
||||||
|
color: red;
|
||||||
|
font-weight: bold;
|
||||||
|
font-family: Merriweather;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.noteKey {
|
||||||
|
color: red;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 18px;
|
||||||
|
font-family: "Bitstream Vera Sans Mono", Consolas, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei"
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#noteText {
|
||||||
|
color: black;
|
||||||
|
padding: 10px;
|
||||||
|
vertical-align: top;
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
background: white;
|
||||||
|
min-width: 200px;
|
||||||
|
border-radius: 0px;
|
||||||
|
overflow-y: auto;
|
||||||
|
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
|
||||||
|
min-height: 400px;
|
||||||
|
border: 0px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#noteText::selection {
|
||||||
|
background: firebrick;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#noteText::-moz-selection {
|
||||||
|
background: firebrick;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="layout">
|
||||||
|
<Layout>
|
||||||
|
<Affix :offset-top="0">
|
||||||
|
|
||||||
|
|
||||||
|
<Header class="header">
|
||||||
|
<div>
|
||||||
|
<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 }">
|
||||||
|
|
||||||
|
|
||||||
|
<div style="background: white;width:100%;height:40px;">
|
||||||
|
<img style="height:40px;float:left;" src="favicon.png">
|
||||||
|
|
||||||
|
<div style="height: 40px;float: left;padding-top: 15px;padding-left: 20px;">
|
||||||
|
<Icon type="md-create" style="font-size: 22px;color:red" /><a class="noteKey">{{ noteForm.key }}</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="float:right;width:auto;">
|
||||||
|
<Button-group size="large">
|
||||||
|
<Button type="error" :loading="loading" style="margin-left:5px; border-radius: 0px;font-size: 24px;"
|
||||||
|
@click="submitNote()" icon="md-cloud-upload"></Button>
|
||||||
|
<Button type="error" style="margin-left:5px; border-radius: 0px;font-size: 24px;"
|
||||||
|
@click="createNote()" icon="md-add"></Button>
|
||||||
|
|
||||||
|
</Button-group>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<div style="float:right;width:auto;">
|
||||||
|
<Button-group size="default">
|
||||||
|
<Button type="error" @click="unLockNote()" icon="md-eye"></Button>
|
||||||
|
<Button type="error" @click="lockNote()" icon="md-eye-off"></Button>
|
||||||
|
</Button-group>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<div style="width:auto;float:right;font-size: 24px;">
|
||||||
|
<Button-group vertical size="large" style="position:absolute;right;0px;top:0px;">
|
||||||
|
<Button type="error" style="border-radius: 0px;font-size: 24px;" icon="md-add"
|
||||||
|
@click="showExt = true"></Button>
|
||||||
|
<Button type="error" v-show="showExt" style="border-radius: 0px;font-size: 24px;"
|
||||||
|
icon="md-cloud-upload"></Button>
|
||||||
|
<Button type="error" v-show="showExt" style="border-radius: 0px;font-size: 24px;"
|
||||||
|
icon="logo-googleplus"></Button>
|
||||||
|
<Button type="error" v-show="showExt" style="border-radius: 0px;font-size: 24px;"
|
||||||
|
icon="logo-tumblr"></Button>
|
||||||
|
</Button-group>
|
||||||
|
</div>
|
||||||
|
-->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
</Header>
|
||||||
|
</Affix>
|
||||||
|
<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">
|
||||||
|
<Form :model="noteForm" :label-width="80">
|
||||||
|
<div id="wrapper" style="border-left: 3px solid #FF3366;">
|
||||||
|
|
||||||
|
<Input element-id="noteText" type="textarea" :border="false" v-model="noteForm.text"
|
||||||
|
:autosize="{ minRows: 30, maxRows: 1024 }" placeholder="Enter something..." v-on:input="log"
|
||||||
|
@on-keydown="down" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</Content>
|
||||||
|
|
||||||
|
<Footer class="layout-footer-center">2022 © openif.com</Footer>
|
||||||
|
</Layout>
|
||||||
|
<Modal v-model="modal1" title="Common Modal dialog box title" @on-ok="ok" @on-cancel="cancel">
|
||||||
|
|
||||||
|
<p>Content of dialog</p>
|
||||||
|
<i-switch true-color="#ff4949" @on-change="change">
|
||||||
|
<Icon type="md-lock" slot="open"></Icon>
|
||||||
|
<Icon type="md-unlock" slot="close"></Icon>
|
||||||
|
</i-switch>
|
||||||
|
<Input v-show="showPassword" maxlength="10" placeholder="Enter password..."
|
||||||
|
style="width: 150px;margin-left:20px;" />
|
||||||
|
<p>Content of dialog</p>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { unzip, aesDecrypt } from '../libs/secret'
|
||||||
|
// import html2canvas from "html2canvas";
|
||||||
|
import Jquery from "jquery";
|
||||||
|
import { saveNote } from "@/api/note";
|
||||||
|
import { getSecretKey, getStoreKey } from "@/api/lock";
|
||||||
|
import storage from "@/libs/storage";
|
||||||
|
import { setStoreText } from "@/libs/noteStorage";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'EditNote',
|
||||||
|
props: {},
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
modal1: false,
|
||||||
|
showExt: false,
|
||||||
|
showPassword: false,
|
||||||
|
noteForm: {
|
||||||
|
text: '',
|
||||||
|
key: '',
|
||||||
|
},
|
||||||
|
secret: {
|
||||||
|
storeKey: '',
|
||||||
|
secretKey: '',
|
||||||
|
cipher: '',
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
lock: '0',
|
||||||
|
|
||||||
|
locking: '0',
|
||||||
|
|
||||||
|
commited: '0'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.noteForm.key = this.$route.params.name;
|
||||||
|
let storeKey = getStoreKey(this.noteForm.key);
|
||||||
|
this.secret.storeKey = storeKey;
|
||||||
|
|
||||||
|
let keyMeta = storage.session.getObject(storeKey + '.keyMeta');
|
||||||
|
if (keyMeta) {
|
||||||
|
this.state.lock = '0';
|
||||||
|
this.secret.cipher = keyMeta.cipher;
|
||||||
|
storage.local.setText(storeKey + '.text', "0|" + this.secret.cipher);
|
||||||
|
storage.session.setObject(storeKey + '.keyMeta', null);
|
||||||
|
} else {
|
||||||
|
let storeText = storage.local.getText(storeKey + '.text');
|
||||||
|
this.state.lock = storeText.substring(0, 1);
|
||||||
|
this.secret.cipher = storeText.substring(2, 34)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state.lock == "1") {
|
||||||
|
this.noteForm.text = "*****lock*****";
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
this.loadText();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
this.bindEvent();
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
},
|
||||||
|
updated() {
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
},
|
||||||
|
destroyed() {
|
||||||
|
},
|
||||||
|
computed: {},
|
||||||
|
watch: {},
|
||||||
|
methods: {
|
||||||
|
change(status) {
|
||||||
|
this.showPassword = status;
|
||||||
|
},
|
||||||
|
ok() {
|
||||||
|
|
||||||
|
},
|
||||||
|
cancel() {
|
||||||
|
|
||||||
|
},
|
||||||
|
loadText() {
|
||||||
|
let secretKey = getSecretKey(this.secret.cipher);
|
||||||
|
let storeText = storage.local.getText(this.secret.storeKey + '.text');
|
||||||
|
|
||||||
|
if (null != storeText && '' != storeText) {
|
||||||
|
let lock = storeText.substring(0, 1);
|
||||||
|
if (lock == "1") {
|
||||||
|
alert("note is encrypt!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//let cipher = storeText.substring(2,34)
|
||||||
|
|
||||||
|
storeText = storeText.substring(35);
|
||||||
|
|
||||||
|
storeText = unzip(storeText);
|
||||||
|
let plainText = aesDecrypt(storeText, secretKey);
|
||||||
|
if (plainText.startsWith("FLAGNOTE#")) {
|
||||||
|
this.noteForm.text = plainText.substring(9);
|
||||||
|
this.state.lock = "0";
|
||||||
|
} else {
|
||||||
|
if (this.state.lock == "1") {
|
||||||
|
alert("password is wrong!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
log() {
|
||||||
|
setStoreText(this.noteForm.text, this.secret);
|
||||||
|
},
|
||||||
|
down(event) {
|
||||||
|
if (event.keyCode == 9) {
|
||||||
|
let start = event.currentTarget.selectionStart, end = event.currentTarget.selectionEnd;
|
||||||
|
let text = event.currentTarget.value;
|
||||||
|
let tab = ' ';//\t
|
||||||
|
text = text.substr(0, start) + tab + text.substr(start);
|
||||||
|
event.currentTarget.value = text;
|
||||||
|
event.currentTarget.selectionStart = start + tab.length;
|
||||||
|
event.currentTarget.selectionEnd = end + tab.length;
|
||||||
|
event.stopPropagation();
|
||||||
|
if (event.preventDefault) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
this.noteForm.text = event.currentTarget.value;
|
||||||
|
setStoreText(event.currentTarget.value, this.secret);
|
||||||
|
|
||||||
|
// html2canvas(document.body).then(function (canvas) {
|
||||||
|
// canvas;
|
||||||
|
// //document.body.appendChild(canvas);
|
||||||
|
// });
|
||||||
|
|
||||||
|
} else if (event.ctrlKey && event.which == 13) {
|
||||||
|
this.save().then(res => {
|
||||||
|
if (res) {
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
save() {
|
||||||
|
this.noteForm.cipher = this.secret.cipher;
|
||||||
|
this.noteForm.lock = this.state.lock;
|
||||||
|
return saveNote(this.noteForm);
|
||||||
|
},
|
||||||
|
submitNote() {
|
||||||
|
if (this.state.locking == "1") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.state.locking = "1";
|
||||||
|
|
||||||
|
this.loading = true;
|
||||||
|
|
||||||
|
this.save().then(res => {
|
||||||
|
if (res) {
|
||||||
|
location.reload();
|
||||||
|
this.state.locking = "0";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
lockNote() {
|
||||||
|
if (this.state.locking == "1") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.state.locking = "1";
|
||||||
|
|
||||||
|
if (this.state.lock == "1") {
|
||||||
|
this.state.locking = "0";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let password = '123456';
|
||||||
|
|
||||||
|
setStoreText(this.noteForm.text, this.secret, password);
|
||||||
|
|
||||||
|
this.state.lock = "1"
|
||||||
|
|
||||||
|
this.noteForm.text = '******lock******';
|
||||||
|
|
||||||
|
|
||||||
|
var noteText = document.getElementById("noteText");
|
||||||
|
|
||||||
|
noteText.select();
|
||||||
|
noteText.selectionStart = 0;
|
||||||
|
noteText.selectionEnd = 1;
|
||||||
|
|
||||||
|
|
||||||
|
this.state.locking = "0";
|
||||||
|
|
||||||
|
},
|
||||||
|
unLockNote() {
|
||||||
|
if (this.state.locking == "1") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state.locking = "1";
|
||||||
|
|
||||||
|
if (this.state.lock == "0") {
|
||||||
|
this.state.locking = "0";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let password = '123456';
|
||||||
|
|
||||||
|
let storeText = storage.local.getText(this.secret.storeKey + '.text');
|
||||||
|
if (storeText) {
|
||||||
|
let lock = storeText.substring(0, 1);
|
||||||
|
|
||||||
|
if (!lock) {
|
||||||
|
alert("note is not encrypt!");
|
||||||
|
}
|
||||||
|
|
||||||
|
storeText = storeText.substring(35);
|
||||||
|
|
||||||
|
storeText = unzip(storeText);
|
||||||
|
|
||||||
|
let secretKey = getSecretKey(this.secret.cipher, password);
|
||||||
|
let plainText = aesDecrypt(storeText, secretKey);
|
||||||
|
if (plainText.startsWith("FLAGNOTE#")) {
|
||||||
|
lock = "0";
|
||||||
|
} else {
|
||||||
|
let secretKey = getSecretKey(this.secret.cipher);
|
||||||
|
let plainText = aesDecrypt(storeText, secretKey);
|
||||||
|
if (plainText.startsWith("FLAGNOTE#")) {
|
||||||
|
lock = "0";
|
||||||
|
} else {
|
||||||
|
lock = "1";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lock == '0') {
|
||||||
|
this.noteForm.text = plainText.substring(9);
|
||||||
|
this.state.lock = "0";
|
||||||
|
setStoreText(this.noteForm.text, this.secret);
|
||||||
|
|
||||||
|
this.state.locking = "0";
|
||||||
|
} else {
|
||||||
|
alert("password is wrong!")
|
||||||
|
this.state.locking = "0";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alert("storeText is deleted!")
|
||||||
|
this.state.locking = "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
createNote() {
|
||||||
|
window.open("/");
|
||||||
|
},
|
||||||
|
bindEvent() {
|
||||||
|
if (document.body.createTextRange) {
|
||||||
|
Jquery(document).keydown(function (e) {
|
||||||
|
if ((e.ctrlKey || e.metaKey) && e.keyCode == 65) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var element = document.getElementById("noteText");
|
||||||
|
|
||||||
|
element.select();
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (window.getSelection) {
|
||||||
|
|
||||||
|
Jquery(document).keydown(function (e) {
|
||||||
|
if ((e.ctrlKey || e.metaKey) && e.keyCode == 65) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var element = document.getElementById("noteText");
|
||||||
|
|
||||||
|
element.select();
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
//alert('none');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
17
src/views/ErrorView.vue
Normal file
17
src/views/ErrorView.vue
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<div class="home">
|
||||||
|
error
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'ErrorView',
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
19
src/views/HomeView.vue
Normal file
19
src/views/HomeView.vue
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<template>
|
||||||
|
<div class="home">
|
||||||
|
home
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'HomeView',
|
||||||
|
components: {
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
327
src/views/ViewNote.vue
Normal file
327
src/views/ViewNote.vue
Normal file
@@ -0,0 +1,327 @@
|
|||||||
|
<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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.noteKey {
|
||||||
|
color: red;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 18px;
|
||||||
|
font-family: "Bitstream Vera Sans Mono", Consolas, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei"
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#noteText {
|
||||||
|
color: black;
|
||||||
|
padding: 10px;
|
||||||
|
vertical-align: top;
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
background: white;
|
||||||
|
min-width: 200px;
|
||||||
|
border-radius: 0px;
|
||||||
|
overflow-y: auto;
|
||||||
|
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
|
||||||
|
/*min-height: 400px;*/
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#noteText::selection {
|
||||||
|
background: firebrick;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#noteText::-moz-selection {
|
||||||
|
background: firebrick;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#noteText br::selection {
|
||||||
|
background: firebrick;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#noteText br::-moz-selection {
|
||||||
|
background: firebrick;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
|
<template>
|
||||||
|
<div class="layout" onkeydown="keydown">
|
||||||
|
<Layout>
|
||||||
|
<Affix :offset-top="0">
|
||||||
|
|
||||||
|
|
||||||
|
<Header class="header">
|
||||||
|
<div>
|
||||||
|
<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 }">
|
||||||
|
|
||||||
|
|
||||||
|
<div style="background: white;width:100%;height:40px;">
|
||||||
|
<img style="height:40px;float:left;" src="favicon.png">
|
||||||
|
|
||||||
|
<div style="height: 40px;float: left;padding-top: 15px;padding-left: 20px;">
|
||||||
|
<Icon type="md-cloud-done" style="font-size: 22px;color:red" /><a class="noteKey">{{ noteForm.key
|
||||||
|
}}</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div style="float:right;width:auto;">
|
||||||
|
<Button-group size="large">
|
||||||
|
<Button type="error" style="margin-left:5px; border-radius: 0px;font-size: 24px;"
|
||||||
|
@click="deleteNote()" icon="md-trash"></Button>
|
||||||
|
<Button type="error" style="margin-left:5px; border-radius: 0px;font-size: 24px;"
|
||||||
|
@click="createNote()" icon="md-add"></Button>
|
||||||
|
</Button-group>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<div style="float:right;width:auto;">
|
||||||
|
<Button-group size="default">
|
||||||
|
<Button type="error" @click="unLockNote()" icon="md-eye"></Button>
|
||||||
|
<Button type="error" @click="lockNote()" icon="md-eye-off"></Button>
|
||||||
|
</Button-group>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<div style="width:auto;float:right;font-size: 24px;">
|
||||||
|
<Button-group vertical size="large" style="position:absolute;right;0px;top:0px;">
|
||||||
|
<Button type="error" style="border-radius: 0px;font-size: 24px;" icon="md-add"
|
||||||
|
@click="showExt = true"></Button>
|
||||||
|
<Button type="error" v-show="showExt" style="border-radius: 0px;font-size: 24px;"
|
||||||
|
icon="md-cloud-upload"></Button>
|
||||||
|
<Button type="error" v-show="showExt" style="border-radius: 0px;font-size: 24px;"
|
||||||
|
icon="logo-googleplus"></Button>
|
||||||
|
<Button type="error" v-show="showExt" style="border-radius: 0px;font-size: 24px;"
|
||||||
|
icon="logo-tumblr"></Button>
|
||||||
|
</Button-group>
|
||||||
|
</div>
|
||||||
|
-->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
</Header>
|
||||||
|
</Affix>
|
||||||
|
<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 style="border-left: 3px solid #FF3366;">
|
||||||
|
<div id="noteText" style="text-align: left;min-height: 650px;" class="monoFt"
|
||||||
|
v-html="this.noteForm.escapeText">
|
||||||
|
view2
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
</Content>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</Layout>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import { aesDecrypt, md5, unzip } from "@/libs/secret";
|
||||||
|
import Jquery from "jquery";
|
||||||
|
import { getSecretKey, getStoreKey } from "@/api/lock";
|
||||||
|
import storage from "@/libs/storage";
|
||||||
|
import { getEscapeText } from "@/libs/noteStorage";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ViewNote',
|
||||||
|
components: {},
|
||||||
|
props: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
noteForm: {
|
||||||
|
url: '',
|
||||||
|
text: '',
|
||||||
|
escapeText: '',
|
||||||
|
key: '',
|
||||||
|
md5: '',
|
||||||
|
lock: '0',
|
||||||
|
},
|
||||||
|
secret: {
|
||||||
|
storeKey: '',
|
||||||
|
secretKey: '',
|
||||||
|
cipher: '',
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
lock: '0',
|
||||||
|
locking: '0',
|
||||||
|
commited: '0'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.noteForm.key = this.$route.params.name;
|
||||||
|
let storeKey = getStoreKey(this.noteForm.key);
|
||||||
|
this.secret.storeKey = storeKey;
|
||||||
|
let noteMeta = storage.session.getObject(storeKey + '.noteMeta');
|
||||||
|
if (noteMeta) {
|
||||||
|
this.state.lock = noteMeta.lock;
|
||||||
|
this.secret.cipher = noteMeta.cipher;
|
||||||
|
this.noteForm.md5 = noteMeta.md5;
|
||||||
|
this.loadText();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.bindEvent();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
decryptNote() {
|
||||||
|
// let password = "123456";
|
||||||
|
// let secretKey = getSecretKey(this.noteForm.key, password);
|
||||||
|
// this.loadText(secretKey);
|
||||||
|
|
||||||
|
},
|
||||||
|
createNote() {
|
||||||
|
window.open("/");
|
||||||
|
},
|
||||||
|
loadText() {
|
||||||
|
|
||||||
|
let password;
|
||||||
|
|
||||||
|
if (this.noteForm.lock == "1") {
|
||||||
|
password = "123456"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (!password) {
|
||||||
|
password = "";
|
||||||
|
}
|
||||||
|
let secretKey = getSecretKey(this.secret.cipher, password);
|
||||||
|
let storeText = storage.local.getText(this.secret.storeKey + '.text');
|
||||||
|
if (!storeText || md5(storeText.substring(35)) != this.noteForm.md5) {
|
||||||
|
let note = this.getNote(this.noteForm.key);
|
||||||
|
storeText = this.noteForm.lock + '|' + this.secret.cipher + '|' + note.text;
|
||||||
|
storage.local.setText(this.secret.storeKey + '.text', storeText);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (storeText) {
|
||||||
|
storeText = unzip(storeText.substring(35));
|
||||||
|
let plainText = aesDecrypt(storeText, secretKey);
|
||||||
|
if (plainText.startsWith("FLAGNOTE#")) {
|
||||||
|
this.noteForm.text = plainText.substring(9);
|
||||||
|
let escapeText = getEscapeText(this.noteForm.text);
|
||||||
|
this.noteForm.escapeText = escapeText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getNote(key) {
|
||||||
|
let noteObject;
|
||||||
|
Jquery.ajax({
|
||||||
|
url: '/note/' + key,
|
||||||
|
async: false,
|
||||||
|
success: function (data) {
|
||||||
|
noteObject = data;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return noteObject;
|
||||||
|
},
|
||||||
|
bindEvent() {
|
||||||
|
if (document.body.createTextRange) {
|
||||||
|
Jquery(document).keydown(function (e) {
|
||||||
|
if ((e.ctrlKey || e.metaKey) && e.keyCode == 65) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var element = document.getElementById("noteText");
|
||||||
|
let range = document.body.createTextRange();
|
||||||
|
range.moveToElementText(element);
|
||||||
|
range.select();
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else 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);
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
//alert('none');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
16
vue.config.js
Normal file
16
vue.config.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
const {defineConfig} = require('@vue/cli-service')
|
||||||
|
module.exports = defineConfig({
|
||||||
|
transpileDependencies: true,
|
||||||
|
devServer: {
|
||||||
|
proxy: {
|
||||||
|
'/note': {
|
||||||
|
target: 'http://localhost:3333/', // 后台接口域名
|
||||||
|
secure: false, // 如果是https接口,需要配置这个参数
|
||||||
|
changeOrigin: true, //是否跨域
|
||||||
|
pathRewrite:{
|
||||||
|
// '^/': '/'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user