Skip to content

Commit fc86793

Browse files
committed
feat(labels): added ability to add labels to text section
1 parent 72e23b4 commit fc86793

File tree

3 files changed

+304
-33
lines changed

3 files changed

+304
-33
lines changed

pages/diff.vue

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,6 @@ export default {
100100
copied: false,
101101
}
102102
},
103-
asyncData() {
104-
return { name: 'World' }
105-
},
106103
mounted() {
107104
const _diff = this.$route.hash
108105
const gunzip = pako.ungzip(Buffer.from(undoUrlSafeBase64(_diff), 'base64'))

pages/index.vue

Lines changed: 56 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,47 @@
1212
</h2>
1313
</header>
1414
</section>
15-
<form class="w-full" @submit="checkForm">
16-
<div class="flex flex-wrap w-full gap-4">
17-
<textarea
18-
rows="28"
19-
id="lhs"
20-
class="flex-1 bg-transparent rounded-md resize-none form-textarea"
21-
></textarea>
22-
<textarea
23-
id="rhs"
24-
rows="28"
25-
class="flex-1 bg-transparent rounded-md resize-none form-textarea"
26-
></textarea>
27-
<div class="self-end flex-grow-0 w-full text-center">
28-
<button
29-
class="inline-flex items-center justify-center w-48 px-4 py-2 transition-transform transform bg-blue-600 rounded-md shadow-lg outline-none text-gray-50 focus:ring-4 active:scale-y-75"
30-
>
31-
Compare
32-
</button>
15+
<form class="flex flex-col w-full gap-4" @submit="checkForm">
16+
<section class="flex w-full gap-4">
17+
<div class="flex flex-col flex-wrap w-1/2 gap-4">
18+
<label for="lhsLabel" class="relative">
19+
<input
20+
id="lhsLabel"
21+
name="lhsLabel"
22+
type="text"
23+
class="flex-1 flex-grow-0 w-full bg-transparent rounded-md material-input"
24+
placeholder="Add label to this text block"
25+
/>
26+
</label>
27+
<textarea
28+
id="lhs"
29+
rows="28"
30+
name="lhs"
31+
class="flex-1 w-full bg-transparent rounded-md resize-none form-textarea"
32+
></textarea>
33+
</div>
34+
<div class="flex flex-col flex-wrap w-1/2 gap-4">
35+
<input
36+
id="rhsLabel"
37+
name="rhsLabel"
38+
type="text"
39+
class="flex-1 flex-grow-0 w-full bg-transparent rounded-md material-input"
40+
placeholder="Add label to this text block"
41+
/>
42+
<textarea
43+
id="rhs"
44+
rows="28"
45+
name="rhs"
46+
class="flex-1 w-full bg-transparent rounded-md resize-none form-textarea"
47+
></textarea>
3348
</div>
49+
</section>
50+
<div class="self-end flex-grow-0 w-full text-center">
51+
<button
52+
class="inline-flex items-center justify-center w-48 px-4 py-2 transition-transform transform bg-blue-600 rounded-md shadow-lg outline-none text-gray-50 focus:ring-4 active:scale-y-75"
53+
>
54+
Compare
55+
</button>
3456
</div>
3557
</form>
3658
</main>
@@ -49,14 +71,12 @@ export default Vue.extend({
4971
methods: {
5072
checkForm(e: Event) {
5173
e.preventDefault()
52-
const lhsTextArea: HTMLTextAreaElement = document.getElementById(
53-
'lhs'
54-
) as HTMLTextAreaElement
55-
const lhs: string = lhsTextArea?.value || ''
56-
const rhsTextArea: HTMLTextAreaElement = document.getElementById(
57-
'rhs'
58-
) as HTMLTextAreaElement
59-
const rhs: string = rhsTextArea?.value || ''
74+
const formData = new FormData(e.currentTarget as HTMLFormElement)
75+
// const formDataJson = Object.fromEntries(formData.entries())
76+
const lhs = formData.get('lhs')
77+
const rhs = formData.get('rhs')
78+
const lhsLabel = formData.get('lhsLabel')
79+
const rhsLabel = formData.get('rhsLabel')
6080
if (!lhs || !rhs) {
6181
this.$store.commit('toast/show', {
6282
show: true,
@@ -84,11 +104,17 @@ export default Vue.extend({
84104
const originalLhs = lhs
85105
const originalRhs = rhs
86106
const diff = dmp.diff_main(originalLhs, originalRhs)
87-
const gzip = Buffer.from(pako.gzip(JSON.stringify(diff))).toString(
88-
'base64'
89-
)
107+
const gzip = Buffer.from(
108+
pako.gzip(
109+
JSON.stringify({
110+
diff,
111+
lhsLabel,
112+
rhsLabel,
113+
})
114+
)
115+
).toString('base64')
90116
this.$router.push({
91-
path: '/diff',
117+
path: '/v1/diff',
92118
hash: `#${doUrlSafeBase64(gzip)}`,
93119
})
94120
},

pages/v1/diff.vue

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
<template>
2+
<div class="page-contents">
3+
<Navbar :show-back-button="true">
4+
<template #right>
5+
<button
6+
type="button"
7+
class="inline-flex justify-center px-4 py-2 transition-transform transform rounded-md shadow outline-none copy-uri-button align-center focus:ring-4 active:scale-y-75"
8+
:class="{
9+
'bg-blue-500 text-white': !copied,
10+
'bg-green-500 text-gray-800': copied,
11+
}"
12+
@click="copyUrlToClipboard"
13+
>
14+
<span v-show="copied" class="inline-flex justify-center">
15+
<svg
16+
class="inline-block w-6 h-6 ml-[-4px]"
17+
fill="none"
18+
stroke="currentColor"
19+
viewBox="0 0 24 24"
20+
xmlns="http://www.w3.org/2000/svg"
21+
>
22+
<path
23+
stroke-linecap="round"
24+
stroke-linejoin="round"
25+
stroke-width="2"
26+
d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"
27+
></path>
28+
</svg>
29+
<span class="hidden ml-2 md:inline-block">Copied</span>
30+
</span>
31+
<span v-show="!copied" class="inline-flex justify-center">
32+
<svg
33+
class="w-6 h-6"
34+
fill="none"
35+
stroke="currentColor"
36+
viewBox="0 0 24 24"
37+
xmlns="http://www.w3.org/2000/svg"
38+
>
39+
<path
40+
stroke-linecap="round"
41+
stroke-linejoin="round"
42+
stroke-width="2"
43+
d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"
44+
></path>
45+
</svg>
46+
<span class="hidden ml-2 md:inline-block">Copy link</span>
47+
</span>
48+
</button>
49+
</template>
50+
</Navbar>
51+
<main>
52+
<section
53+
class="flex items-stretch w-full gap-4 font-mono text-gray-800 dark:text-gray-50"
54+
>
55+
<div class="flex flex-col w-1/2 gap-2">
56+
<p class="flex-grow-0 text-lg">{{ lhsLabel }}</p>
57+
<div
58+
class="relative flex-1 px-4 py-2 overflow-y-auto border-2 rounded-md dark:border-gray-500 max-h-screen--nav line-number-gutter min-h-80"
59+
>
60+
<RTStickyCopyButton :click-handler="copyTextToClipboard" />
61+
<div
62+
v-for="(lineDiff, index) in lhsDiff"
63+
:key="index"
64+
:class="{
65+
'bg-red-100 dark:bg-yellow-700':
66+
lineDiff.includes('isModified'),
67+
}"
68+
>
69+
<p class="break-all whitespace-pre-wrap" v-html="lineDiff"></p>
70+
</div>
71+
</div>
72+
</div>
73+
74+
<div class="flex flex-col w-1/2 gap-2">
75+
<p class="flex-grow-0 text-lg">{{ rhsLabel }}</p>
76+
<div
77+
class="relative flex-1 px-4 py-2 overflow-y-auto border-2 rounded-md dark:border-gray-500 min-h-80 line-number-gutter max-h-screen--nav"
78+
>
79+
<RTStickyCopyButton :click-handler="copyTextToClipboard" />
80+
<div
81+
v-for="(lineDiff, index) in rhsDiff"
82+
:key="index"
83+
:class="{
84+
'bg-green-100 dark:bg-green-700':
85+
lineDiff.includes('isModified'),
86+
}"
87+
>
88+
<p class="break-all whitespace-pre-wrap" v-html="lineDiff"></p>
89+
</div>
90+
</div>
91+
</div>
92+
</section>
93+
</main>
94+
<Footer />
95+
</div>
96+
</template>
97+
98+
<script>
99+
import pako from 'pako'
100+
import { undoUrlSafeBase64, escapeHtml } from '../../helpers/utils'
101+
import footer from '~/components/footer.vue'
102+
export default {
103+
components: { footer },
104+
layout: 'main',
105+
data() {
106+
return {
107+
lhsDiff: this.lhsDiff,
108+
rhsDiff: this.rhsDiff,
109+
rhsLabel: this.rhsLabel,
110+
lhsLabel: this.lhsLabel,
111+
copied: false,
112+
}
113+
},
114+
mounted() {
115+
const _diff = this.$route.hash
116+
const gunzip = pako.ungzip(Buffer.from(undoUrlSafeBase64(_diff), 'base64'))
117+
const diffData = JSON.parse(Buffer.from(gunzip).toString('utf8'))
118+
const { diff, lhsLabel, rhsLabel } = diffData
119+
this.lhsLabel = lhsLabel
120+
this.rhsLabel = rhsLabel
121+
this.lhsDiff = diff
122+
.map((item) => {
123+
const hunkState = item[0]
124+
if (hunkState === -1 || hunkState === 0) {
125+
const className =
126+
hunkState === -1 ? 'isModified bg-red-300 dark:bg-yellow-900' : ''
127+
return `<span class="break-all inline p-0 m-0 ${className}">${escapeHtml(
128+
item[1]
129+
)}</span>`
130+
}
131+
return false
132+
})
133+
.filter(Boolean)
134+
.join('')
135+
.split('\n')
136+
this.rhsDiff = diff
137+
.map((item) => {
138+
const hunkState = item[0]
139+
if (hunkState === 1 || hunkState === 0) {
140+
const className =
141+
hunkState === 1 ? 'isModified bg-green-300 dark:bg-green-900' : ''
142+
return `<span class="break-all inline p-0 m-0 ${className}">${escapeHtml(
143+
item[1]
144+
)}</span>`
145+
}
146+
return false
147+
})
148+
.filter(Boolean)
149+
.join('')
150+
.split('\n')
151+
const maxLineCount =
152+
this.lhsDiff.length > this.rhsDiff.length
153+
? this.lhsDiff.length
154+
: this.rhsDiff.length
155+
document.documentElement.style.setProperty(
156+
'--max-line-number-characher',
157+
`${`${maxLineCount}`.split('').length}ch`
158+
)
159+
},
160+
methods: {
161+
putToClipboard(textToPut, toastContent) {
162+
navigator.clipboard.writeText(textToPut)
163+
this.$store.commit('toast/show', {
164+
show: true,
165+
content: toastContent,
166+
iconHTML: `
167+
<svg
168+
class="w-6 h-6"
169+
fill="none"
170+
stroke="currentColor"
171+
viewBox="0 0 24 24"
172+
xmlns="http://www.w3.org/2000/svg"
173+
>
174+
<path
175+
stroke-linecap="round"
176+
stroke-linejoin="round"
177+
stroke-width="2"
178+
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
179+
></path>
180+
</svg>
181+
`,
182+
theme: 'success',
183+
})
184+
},
185+
copyUrlToClipboard() {
186+
this.putToClipboard(window.location.href, 'Link copied to your clipboard')
187+
this.copied = true
188+
setTimeout(() => {
189+
this.copied = false
190+
}, 5000)
191+
},
192+
copyTextToClipboard(e) {
193+
this.putToClipboard(
194+
e.currentTarget.parentNode.parentNode.innerText
195+
.split('\n\n')
196+
.join('\n'),
197+
'Text copied to your clipboard'
198+
)
199+
},
200+
},
201+
}
202+
</script>
203+
204+
<style lang="scss">
205+
.copy-uri-button:hover {
206+
@apply shadow-lg;
207+
svg {
208+
@apply scale-110 rotate-12;
209+
}
210+
}
211+
212+
/* line numbers in diff view */
213+
.line-number-gutter {
214+
counter-reset: line-numbers;
215+
216+
--line-number-gutter-width: calc(var(--max-line-number-characher) + 10px);
217+
&::before {
218+
content: '';
219+
width: var(--line-number-gutter-width);
220+
@apply bg-gray-200 dark:bg-gray-700 inline-block left-0 top-0 bottom-0 absolute text-sm;
221+
}
222+
@apply relative;
223+
p {
224+
padding-left: calc(var(--line-number-gutter-width) - 10px);
225+
line-height: 1.65;
226+
@apply relative;
227+
&:hover {
228+
@apply bg-gray-200 dark:bg-gray-700;
229+
}
230+
&::before {
231+
counter-increment: line-numbers;
232+
content: counter(line-numbers);
233+
width: var(--line-number-gutter-width);
234+
@apply absolute left-0 top-0 -mx-4 bottom-0 text-center bg-gray-200 dark:bg-gray-700 dark:text-gray-50 text-gray-500 flex justify-center text-sm;
235+
}
236+
&:first-of-type {
237+
&::before {
238+
@apply -mt-2 pt-2;
239+
}
240+
}
241+
&:last-of-type {
242+
&::before {
243+
@apply -mb-2 pb-2;
244+
}
245+
}
246+
}
247+
}
248+
</style>

0 commit comments

Comments
 (0)