메모용 코드(임시)
계기
일을 하다가 보안이 빡센 사이트에 들어가게 되었다...
무려 메모장을(포함한 모든 문서 작성 가능 application)을 못 쓰게 하는 사이트다...
내부망과 외부망을 같이 볼 수 있으니 외부망(인터넷망)을 통해외부로 유출하지 말라고 문서 작성해서 저장 못하게 막은거 같은데... 의미가 있나 싶다... 뭐 못쓰니 어쩔 수 없지... 라고 하기에는
우리도 점검 하면서 나오는 정보를 보고 띁고 분석하고 뭔가 긴 이상한 값 같은건 기록했다가 돌려쓰고(세션 점검 등)해야 하는데 그걸 막으면... 할 수는 있지 근데 힘들지... 라는 상황이었다.
아이디어
그러던 도중 hosts파일에 ip, dns를 추가해야 하는 상황에서 이게 관리자 권한 문제 있는 곳은 cmd(power shell)를 관리자 권한 받아서 echo, add-content 등 사용해서 밀어넣어야 하는데 여기서 든 생각이 html코드만 사용하면 패킷 안가니까 관제 관점에서 문제 없잔아? 그럼 내가 간단하고 짧은 html코드를 cmd에서 redirection을 잘 섞어서 만들면...? 이걸로 임시 메모장 만들 수 있는거 아닌가라는 생각에 이르렀다.
초기버전
그래서 만들어진 것이
<textarea id=t></textarea>
<input id=f type=file hidden>
<script>
t.value=localStorage.memo||''
t.oninput=_=>localStorage.memo=t.value
document.body.style.margin=0
t.style.cssText='width:100vw;height:100vh;box-sizing:border-box;font:16px monospace;padding:12px'
onkeydown=e=>{
if(e.ctrlKey&&e.key=='s'){
e.preventDefault()
a=document.createElement('a')
a.href=URL.createObjectURL(new Blob([t.value],{type:'text/plain'}))
a.download='memo.txt'
a.click()
}
if(e.ctrlKey&&e.key=='o'){
e.preventDefault()
f.click()
}
}
f.onchange=_=>{
r=new FileReader()
r.onload=_=>t.value=localStorage.memo=r.result
r.readAsText(f.files[0])
}
</script>이때는 탭도 사치여서 그냥 가독성 떨어지더라도 일단 만들었다.
글쓰고 저장하고, 불러오기까지 되도록 이제 나는 임시 에디터를 얻었다.
업데이트
로컬 스토리지를 쓰다 보니 메모장 하나만 쓸 수 있는데 보통 메모장 하나에 모든 것을 다 하지는 않고 여러개 켜서 이것 저것 분류하면서 쓰지 않는가? 그러기에 불편함이 있기에 업데이트를 해봤다.
<div id=bar><button onclick=add()>+</button><button onclick=del()>-</button></div>
<div id=box></div>
<input id=f type=file hidden>
<script>
let cur,loading=1
document.body.style.cssText='margin:0;height:100vh;display:flex;flex-direction:column'
bar.style.cssText='padding:4px;background:#ddd'
box.style.cssText='flex:1;display:flex'
function save(){
if(!loading)localStorage.memos=JSON.stringify([...box.children].map(x=>x.value))
}
function st(x){
x.style.cssText='flex:1;min-width:0;height:100%;box-sizing:border-box;font:16px monospace;padding:12px;tab-size:4'
x.onfocus=_=>cur=x
x.oninput=save
}
function add(v=''){
let n=document.createElement('textarea')
st(n); n.value=v
box.appendChild(n); cur=n; n.focus(); save()
}
function del(){
if(box.children.length<2)return
let n=cur.previousElementSibling||cur.nextElementSibling
cur.remove(); cur=n; cur.focus(); save()
}
JSON.parse(localStorage.memos||'[""]').forEach(add)
loading=0
onkeydown=e=>{
if(e.key=='Tab'&&e.target.tagName=='TEXTAREA'){
e.preventDefault()
let x=e.target,s=x.selectionStart,v=x.value
x.value=v.slice(0,s)+' '+v.slice(x.selectionEnd)
x.selectionStart=x.selectionEnd=s+4
save()
}
if(e.ctrlKey&&e.key=='s'){
e.preventDefault()
a=document.createElement('a')
a.href=URL.createObjectURL(new Blob([cur.value]))
a.download='memo.txt'
a.click()
}
if(e.ctrlKey&&e.key=='o'){
e.preventDefault()
f.click()
}
}
f.onchange=_=>{
r=new FileReader()
r.onload=_=>{cur.value=r.result;save()}
r.readAsText(f.files[0])
f.value=''
}
</script>이제 만족스럽게.... 사람이 욕심이 끝이 없다 했던가 만들다 보니 이것저것 추가하고 싶은게 많아져버렸다...
헷갈리니 라인번호 보고 싶고, 파일도 드래그드롭으려 열리면 얼마나 좋은가, 탭 누르면 공백이 변경되기도 하고... 이것저것 욕심이 생겨 더 업데이트 해봤다.
<!doctype html>
<meta charset="utf-8">
<div id=bar><button onclick=add()>+</button><button onclick=del()>-</button></div>
<div id=box></div>
<input id=f type=file hidden>
<style>
html,body{margin:0;height:100%}
body{display:flex;flex-direction:column}
#bar{padding:4px;background:#ddd}
#box{flex:1;display:flex;min-height:0}
.pane{flex:1;min-width:0;display:flex;height:100%}
.gbox{width:42px;background:#eee;overflow:hidden;user-select:none;text-align:right}
.gut{padding:12px 6px;font:16px/20px monospace}
.gut div{box-sizing:border-box}
textarea{flex:1;min-width:0;height:100%;box-sizing:border-box;font:16px/20px monospace;padding:12px;tab-size:4;border:0;outline:0;resize:none;overflow:auto;white-space:pre-wrap;word-break:break-word}
.mirror{position:absolute;left:-9999px;top:-9999px;visibility:hidden;white-space:pre-wrap;word-break:break-word;overflow-wrap:break-word;font:16px/20px monospace;tab-size:4}
</style>
<script>
let cur,loading=1
function areas(){return [...box.querySelectorAll('textarea')]}
function save(){
if(!loading)localStorage.memos=JSON.stringify(areas().map(x=>x.value))
}
function line(t){
let g=t._gut,m=t._mir,cs=getComputedStyle(t),lh=parseFloat(cs.lineHeight)||20
m.style.width=(t.clientWidth-parseFloat(cs.paddingLeft)-parseFloat(cs.paddingRight))+'px'
g.innerHTML=''
;(t.value||'').split('\n').forEach((s,i)=>{
m.textContent=s||' '
let d=document.createElement('div')
d.textContent=i+1
d.style.height=Math.max(lh,m.scrollHeight)+'px'
g.appendChild(d)
})
g.style.transform='translateY(-'+t.scrollTop+'px)'
}
function read(file,target=cur){
if(!file||!target)return
let r=new FileReader()
r.onload=_=>{target.value=r.result;line(target);save()}
r.readAsText(file)
}
function add(v=''){
let p=document.createElement('div'),gb=document.createElement('div')
let g=document.createElement('div'),t=document.createElement('textarea'),m=document.createElement('div')
p.className='pane';gb.className='gbox';g.className='gut';m.className='mirror'
t.value=v;t._gut=g;t._mir=m
t.onfocus=_=>cur=t
t.oninput=_=>{line(t);save()}
t.onscroll=_=>line(t)
t.ondragover=e=>e.preventDefault()
t.ondrop=e=>{e.preventDefault();cur=t;read(e.dataTransfer.files[0],t)}
gb.appendChild(g);p.append(gb,t,m);box.appendChild(p)
cur=t;t.focus();line(t);save()
}
function del(){
let a=areas()
if(a.length<2)return
let i=a.indexOf(cur),n=a[i-1]||a[i+1]
cur.parentElement.remove();cur=n;cur.focus();save()
}
onkeydown=e=>{
if(e.key=='Tab'&&e.target.tagName=='TEXTAREA'){
e.preventDefault()
let x=e.target,s=x.selectionStart,v=x.value
x.value=v.slice(0,s)+' '+v.slice(x.selectionEnd)
x.selectionStart=x.selectionEnd=s+4
line(x);save()
}
if(e.ctrlKey&&e.key=='s'){
e.preventDefault()
let a=document.createElement('a')
a.href=URL.createObjectURL(new Blob([cur.value],{type:'text/plain'}))
a.download='memo.md'
a.click()
}
if(e.ctrlKey&&e.key=='o'){
e.preventDefault()
f.click()
}
}
f.onchange=_=>{read(f.files[0],cur);f.value=''}
window.onresize=_=>areas().forEach(line)
JSON.parse(localStorage.memos||'[""]').forEach(add)
loading=0
</script>욕심의 끝은 vim으로
내가 좋아하는 에디터중 하나인 vim처럼 만들고 싶다는 욕심까지 다아버렸다.... GPT 잘만들어준다... 그래서 생긴 최종본이
<!doctype html>
<meta charset="utf-8">
<style>
html,body{margin:0;height:100%}
body{display:flex;flex-direction:column;background:#111;color:#ddd;font:14px monospace}
#box{flex:1;display:flex;min-height:0}
#cmd{height:28px;background:#000;color:#aaa;padding:5px 8px;box-sizing:border-box}
#ci{background:#000;color:#ddd;border:0;outline:0;font:14px monospace;width:80%;display:none}
.wrap{flex:1;display:flex;min-width:0;min-height:0}
.pane{flex:1;min-width:0;min-height:0;display:flex;border-right:1px solid #333;border-bottom:1px solid #333}
.pane.on textarea{background:#171717}
.gut{width:42px;background:#1b1b1b;color:#777;text-align:right;padding:8px 6px;overflow:hidden;user-select:none;box-sizing:border-box;line-height:20px}
.gut div{height:20px}
textarea{flex:1;min-width:0;height:100%;box-sizing:border-box;background:#111;color:#ddd;border:0;outline:0;resize:none;padding:8px;font:14px/20px monospace;tab-size:4;overflow:auto;white-space:pre-wrap;word-break:break-word}
</style>
<div id=box></div>
<div id=cmd><span id=mode></span><input id=ci></div>
<script>
let cur,mod='INSERT',loading=1
function areas(){return [...box.querySelectorAll('textarea')]}
function save(){if(!loading)localStorage.memos=JSON.stringify(areas().map(x=>x.value))}
function msg(s){mode.textContent=s}
function setmod(m){
mod=m
ci.style.display=m=='COMMAND'?'inline':'none'
msg(m=='COMMAND'?':':'-- '+m+' --')
if(m=='COMMAND'){ci.value='';ci.focus()}
else cur.focus()
}
function snap(t=cur){
if(!t)return
let s=t._s
if(s.h[s.i]!=t.value){
s.h=s.h.slice(0,s.i+1)
s.h.push(t.value)
s.i++
}
}
function undo(t=cur){
if(!t)return
let s=t._s
if(s.i>0){
s.i--
t.value=s.h[s.i]
line(t);save()
}
}
function rollback(t){
t.value=t._s.h[t._s.i]
line(t)
}
function line(t){
let g=t._g,n=t.value.split('\n').length
g.innerHTML=''
for(let i=1;i<=n;i++){let d=document.createElement('div');d.textContent=i;g.appendChild(d)}
g.scrollTop=t.scrollTop
}
function active(t){
cur=t
document.querySelectorAll('.pane').forEach(p=>p.classList.remove('on'))
t.parentElement.classList.add('on')
}
function pane(v=''){
let p=document.createElement('div'),g=document.createElement('div'),t=document.createElement('textarea')
p.className='pane';g.className='gut';t.value=v;t._g=g;t._s={h:[v],i:0}
t.onfocus=_=>active(t)
t.onbeforeinput=e=>{if(mod!='INSERT')e.preventDefault()}
t.oncompositionstart=e=>{if(mod!='INSERT')t._lock=1}
t.oncompositionupdate=e=>{if(mod!='INSERT')t._lock=1}
t.oncompositionend=e=>{if(mod!='INSERT'){t._lock=0;rollback(t)}}
t.oninput=_=>{
if(mod!='INSERT'||t._lock){rollback(t);return}
snap(t);line(t);save()
}
t.onscroll=_=>g.scrollTop=t.scrollTop
t.ondragover=e=>e.preventDefault()
t.ondrop=e=>{e.preventDefault();active(t);read(e.dataTransfer.files[0],t)}
p.append(g,t);line(t)
return p
}
function add(v=''){
let p=pane(v)
box.appendChild(p)
active(p.querySelector('textarea'))
cur.focus()
save()
}
function split(d,v){
let old=cur.parentElement,parent=old.parentElement
let w=document.createElement('div')
w.className='wrap'
w.style.cssText='flex:1;display:flex;min-width:0;min-height:0;flex-direction:'+d
parent.replaceChild(w,old)
w.appendChild(old)
let np=pane(v)
w.appendChild(np)
active(np.querySelector('textarea'))
cur.focus()
save()
}
function del(){
let a=areas()
if(a.length<2)return msg('마지막 창은 닫지 않음')
let p=cur.parentElement,parent=p.parentElement
let next=a[a.indexOf(cur)-1]||a[a.indexOf(cur)+1]
p.remove()
if(parent.className=='wrap'&&parent.children.length==1){
let only=parent.children[0]
parent.parentElement.replaceChild(only,parent)
}
active(next);next.focus();save()
}
function only(){
let v=cur.value
box.innerHTML=''
add(v)
save()
}
function read(file,t=cur){
if(!file||!t)return
let r=new FileReader()
r.onload=_=>{t.value=r.result;t._s={h:[t.value],i:0};line(t);save()}
r.readAsText(file)
}
function down(){
let a=document.createElement('a')
a.href=URL.createObjectURL(new Blob([cur.value],{type:'text/plain'}))
a.download='memo.md'
a.click()
}
function pos(){
let s=cur.selectionStart,v=cur.value.slice(0,s).split('\n')
return {l:v.length-1,c:v[v.length-1].length,arr:cur.value.split('\n')}
}
function at(o){
let p=0
for(let i=0;i<o.l;i++)p+=o.arr[i].length+1
cur.selectionStart=cur.selectionEnd=p+Math.min(o.c,o.arr[o.l].length)
}
function mv(dl,dc){let o=pos();o.l=Math.max(0,Math.min(o.arr.length-1,o.l+dl));o.c=Math.max(0,Math.min(o.arr[o.l].length,o.c+dc));at(o)}
function word(d){
let v=cur.value,p=cur.selectionStart
if(d>0){while(p<v.length&&/\w/.test(v[p]))p++;while(p<v.length&&!/\w/.test(v[p]))p++}
else{p--;while(p>0&&!/\w/.test(v[p]))p--;while(p>0&&/\w/.test(v[p-1]))p--}
cur.selectionStart=cur.selectionEnd=Math.max(0,p)
}
function endw(){
let v=cur.value,p=cur.selectionStart+1
while(p<v.length&&!/\w/.test(v[p]))p++
while(p<v.length-1&&/\w/.test(v[p+1]))p++
cur.selectionStart=cur.selectionEnd=p
}
function run(c){
if(c=='w')down()
else if(c=='q')del()
else if(c=='vs'||c=='vsplit')split('row',cur.value)
else if(c=='vnew')split('row','')
else if(c=='sp'||c=='split')split('column',cur.value)
else if(c=='new')split('column','')
else if(c=='only'||c=='on')only()
else msg('알 수 없는 명령: '+c)
setmod('NORMAL')
}
onkeydown=e=>{
if(!cur)return
if(e.key=='Escape'){e.preventDefault();setmod('NORMAL');return}
if(mod=='COMMAND'){
if(e.key=='Enter'){e.preventDefault();run(ci.value.trim());return}
return
}
if(mod=='INSERT'){
if(e.key=='Tab'&&e.target.tagName=='TEXTAREA'){
e.preventDefault()
snap(cur)
let s=cur.selectionStart,v=cur.value
cur.value=v.slice(0,s)+' '+v.slice(cur.selectionEnd)
cur.selectionStart=cur.selectionEnd=s+4
snap(cur);line(cur);save()
}
return
}
if(mod=='NORMAL'){
if(!e.ctrlKey&&!e.metaKey)e.preventDefault()
if(e.key==':'){setmod('COMMAND');return}
if(e.key=='i'){setmod('INSERT');return}
if(e.key=='a'){cur.selectionStart=cur.selectionEnd=cur.selectionStart+1;setmod('INSERT');return}
if(e.key=='h')mv(0,-1)
if(e.key=='l')mv(0,1)
if(e.key=='j')mv(1,0)
if(e.key=='k')mv(-1,0)
if(e.key=='w')word(1)
if(e.key=='b')word(-1)
if(e.key=='e')endw()
if(e.key=='0'){let o=pos();o.c=0;at(o)}
if(e.key=='$'){let o=pos();o.c=o.arr[o.l].length;at(o)}
if(e.key=='G'){cur.selectionStart=cur.selectionEnd=cur.value.length}
if(e.key=='u')undo()
if(e.key=='x'){
snap(cur)
let s=cur.selectionStart,v=cur.value
cur.value=v.slice(0,s)+v.slice(s+1)
cur.selectionStart=cur.selectionEnd=s
snap(cur);line(cur);save()
}
}
}
JSON.parse(localStorage.memos||'[""]').forEach(add)
loading=0
setmod('NORMAL')
</script>오늘도 vim쓰로 간다 나는
:wq