Skip to content

Commit 7fa7983

Browse files
committed
✨ url title etc
1. auto fetch url title and display as record title; 2. fixed auto saving bug
1 parent b1e1b2d commit 7fa7983

9 files changed

Lines changed: 328 additions & 207 deletions

File tree

package-lock.json

Lines changed: 187 additions & 182 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@
2525
},
2626
"dependencies": {
2727
"axios": "^0.16.1",
28+
"debounce": "^1.0.2",
29+
"get-title-at-url": "^1.1.5",
30+
"getfavicon": "^1.1.2",
2831
"qr-image": "^3.2.0",
32+
"valid-url": "^1.0.9",
2933
"vue": "^2.3.3",
3034
"vue-electron": "^1.0.6",
3135
"vue-router": "^2.5.3",

src/renderer/components/QrCodeList.vue

Lines changed: 93 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<ul class="qr-code-list">
33
<li v-for="code in allCodes" :key="code.id" :id="code.id" class="qr-code-list__item" :class="code.selected ? 'qr-code-list__item_selected' : ''" @click="handleItemClick(code)">
44
<a class="qr-code-list__anchor" href="javascript: void(0);" tabindex="-1">
5-
<p class="qr-code-list__title" :title="code.title">{{ code.title }}</p>
5+
<p class="qr-code-list__title" :title="code.title" @dblclick="handleTitleDoubleClick" @blur="handleTitleBlur(arguments[0], code)" @keydown="handleTitleKeyDown">{{ code.title }}</p>
66
<p class="qr-code-list__content" :title="code.content">{{ code.content }}</p>
77
</a>
88
</li>
@@ -11,6 +11,7 @@
1111

1212
<script>
1313
import { mapGetters } from 'vuex';
14+
import * as cfg from '@/configs';
1415
1516
export default {
1617
computed: {
@@ -23,6 +24,58 @@ export default {
2324
handleItemClick(code) {
2425
this.$store.dispatch('selectCode', code.id);
2526
},
27+
handleTitleDoubleClick(ev) {
28+
const body = document.querySelector('body');
29+
const element = ev.currentTarget;
30+
31+
element.contentEditable = true;
32+
element.parentElement.parentElement.classList.add('qr-code-list__item_edit');
33+
if (body.createTextRange) {
34+
const range = body.createTextRange();
35+
36+
range.moveToElementText(element);
37+
range.select();
38+
} else if (window.getSelection) {
39+
const selection = window.getSelection();
40+
const range = document.createRange();
41+
42+
range.selectNodeContents(element);
43+
selection.removeAllRanges();
44+
selection.addRange(range);
45+
}
46+
},
47+
handleTitleKeyDown(ev) {
48+
if (ev.keyCode === cfg.KEY_ENTER) {
49+
ev.preventDefault();
50+
ev.currentTarget.blur();
51+
}
52+
},
53+
handleTitleBlur(ev, code) {
54+
const element = ev.currentTarget;
55+
const title = element.textContent.replace(/\n|\r/g, '');
56+
57+
element.contentEditable = false;
58+
element.innerHTML = title;
59+
element.parentElement.parentElement.classList.remove('qr-code-list__item_edit');
60+
if (!title) {
61+
element.innerHTML = code.title;
62+
return;
63+
}
64+
if (title === code.title) {
65+
return;
66+
}
67+
if (this.allCodes.some(c => c.title === title)) {
68+
this.$store.dispatch('showToast', 'Duplicate record title');
69+
element.innerHTML = code.title;
70+
return;
71+
}
72+
if (title !== code.title) {
73+
this.$store.dispatch('updateCode', {
74+
id: code.id,
75+
title,
76+
});
77+
}
78+
},
2679
},
2780
};
2881
</script>
@@ -40,22 +93,6 @@ export default {
4093
position: relative;
4194
}
4295
43-
&__item:active,
44-
&__item_selected {
45-
background-color: #efefef;
46-
border-bottom-color: #e0e5e6;
47-
48-
&:after {
49-
content: '';
50-
width: 100%;
51-
height: 1px;
52-
background-color: #e0e5e6;
53-
top: -1px;
54-
left: 0;
55-
position: absolute;
56-
}
57-
}
58-
5996
&__anchor {
6097
color: inherit;
6198
text-decoration: none;
@@ -68,18 +105,56 @@ export default {
68105
margin-bottom: 0;
69106
width: 100%;
70107
overflow: hidden;
71-
text-overflow: ellipsis;
72108
white-space: nowrap;
109+
position: relative;
110+
}
111+
112+
&__title::after,
113+
&__content::after {
114+
content: '';
115+
width: 20px;
116+
top: 0;
117+
right: 0;
118+
bottom: 0;
119+
position: absolute;
120+
background-image: linear-gradient(to left, #f6f8f8 20%, transparent);
73121
}
74122
75123
&__title {
124+
outline: none;
76125
margin-bottom: 5px;
77126
}
78127
79128
&__content {
80129
color: #cccccc;
81130
font-size: 14px;
82131
}
132+
133+
// state
134+
// ====
135+
&__item_selected {
136+
background-color: #efefef;
137+
border-bottom-color: #e0e5e6;
138+
}
139+
140+
&__item_selected::after {
141+
content: '';
142+
width: 100%;
143+
height: 1px;
144+
background-color: #e0e5e6;
145+
top: -1px;
146+
left: 0;
147+
position: absolute;
148+
}
149+
150+
&__item_selected &__title::after,
151+
&__item_selected &__content::after {
152+
background-image: linear-gradient(to left, #efefef 20%, transparent);
153+
}
154+
155+
&__item_edit &__title::after {
156+
background-image: none;
157+
}
83158
}
84159
</style>
85160

src/renderer/components/Toast.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export default {
3838
&__wrapper {
3939
text-align: center;
4040
border-radius: 500px;
41-
background-color: rgba(0, 0, 0, 0.5);
41+
background-color: rgba(0, 0, 0, 0.75);
4242
min-width: 100px;
4343
max-width: 250px;
4444
padding: 12px;

src/renderer/configs/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
export const MAX_AVALIABLE_CODE_COUNT = 20;
22
export const MAX_CONTENT_LENGTH = 150 * 3;
33
export const TOAST_DURATION = 3000;
4+
export const GET_TITLE_TIMEOUT = 8 * 1000;
5+
export const SAVE_CHECKPOINT_DURATION = 1000;
46

57
export * from './keycode';

src/renderer/helpers/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './is';
22
export * from './svg';
33
export * from './storage';
4+
export * from './title';

src/renderer/helpers/title.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import getTitleAtUrl from 'get-title-at-url';
2+
import * as cfg from '@/configs';
3+
4+
/* eslint-disable import/prefer-default-export */
5+
export function getTitle(url) {
6+
return Promise.race([
7+
new Promise((resolve, reject) => {
8+
setTimeout(reject, cfg.GET_TITLE_TIMEOUT);
9+
}),
10+
new Promise((resolve, reject) => {
11+
getTitleAtUrl(url, (title, err) => {
12+
if (err) reject(err);
13+
else resolve(title);
14+
});
15+
}),
16+
]);
17+
}

src/renderer/store/index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import Vue from 'vue';
22
import Vuex from 'vuex';
3+
import debounce from 'debounce';
34
import toast from '@/store/modules/toast';
45
import qrCodeList from '@/store/modules/qr-code-list';
56
import qrCodeImage from '@/store/modules/qr-code-image';
67
import * as helpers from '@/helpers';
8+
import * as cfg from '@/configs';
79

810
Vue.use(Vuex);
911

@@ -22,4 +24,7 @@ store.replaceState({
2224
},
2325
qrCodeImage: qrCodeImage.state,
2426
});
27+
store.subscribe(debounce((mutation, state) => {
28+
helpers.setItem('codes', state.qrCodeList.codes);
29+
}, cfg.SAVE_CHECKPOINT_DURATION));
2530
export default store;

src/renderer/webviews/Workspace.vue

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323

2424
<script>
2525
import { mapGetters } from 'vuex';
26-
import { parse as parseUrl } from 'url';
26+
import { isWebUri } from 'valid-url';
2727
import Toast from '@/components/Toast';
2828
import Scroller from '@/components/Scroller';
2929
import QrCodeImage from '@/components/QrCodeImage';
@@ -59,9 +59,6 @@ export default {
5959
this.$electron.ipcRenderer.send('encode-text', newSelectedCode.content);
6060
}
6161
},
62-
allCodes(newAllCodes) {
63-
helpers.setItem('codes', newAllCodes);
64-
},
6562
},
6663
methods: {
6764
handleInputKeyDown(ev) {
@@ -86,15 +83,30 @@ export default {
8683
return;
8784
}
8885
if (content) {
89-
const urlOpts = parseUrl(content);
86+
let previousCodeId;
9087
88+
if (isWebUri(content)) {
89+
helpers.getTitle(content)
90+
.then((urlTitle) => {
91+
if (urlTitle) {
92+
this.$store.dispatch('updateCode', {
93+
id: previousCodeId,
94+
title: urlTitle,
95+
});
96+
}
97+
})
98+
.catch(() => {
99+
// do nothing
100+
});
101+
}
91102
this.$store.dispatch('addCode', {
92-
title: urlOpts.hostname || content,
103+
title: content,
93104
content,
94105
selected: true,
95106
});
96107
this.$electron.ipcRenderer.send('encode-text', content);
97108
this.$store.dispatch('selectCode', this.allCodes[0].id);
109+
previousCodeId = this.selectedCode.id;
98110
}
99111
targetNode.value = '';
100112
},

0 commit comments

Comments
 (0)