Skip to content
created by Aha00aAha00a at 2026-05-31
last modified by Aha00aAha00a at 2026-09-16
revision: 13

Dev Editor

현재 위키 편집 화면의 기능과 구현 위치를 정리한다.

1. Entry Points

  • app/views/Wiki/edit.scala.html - 편집 화면 마크업, 툴바, AhaWikiEditConfig (CodeMirror 스타일은 app/assets/edit.less)
  • public/js/Wiki/edit.page.js - 편집 화면의 대부분의 클라이언트 동작
  • public/js/AhaWiki.Editor.js - 입력 보조 규칙과 CodeMirror/textarea 공통 keydown 처리
  • public/js/AhaWiki.CodeMirror.AhaMark.js - AhaMark용 CodeMirror mode
  • public/js/Wiki/edit.hotkeyAlt.js - 편집 화면 Alt 단축키 dispatcher
  • public/js/Wiki/edit.divider.js - editor/preview 컬럼 divider drag resize
  • public/js/AhaWiki.WikipediaToSchema.js - Wikipedia InfoBox를 Schema 블록으로 변환

2. 화면 구성

  • 왼쪽은 editor, 오른쪽은 preview로 구성한다.
  • divider를 drag하면 왼쪽 editor column width를 조정한다.
  • editor와 preview 높이는 viewport, toolbar, help, attachment list 높이를 반영해 자동 계산한다.
  • preview pane은 editor column과 높이를 맞추고 자체 scroll 영역을 가진다.
  • Word Wrap 체크박스는 CodeMirror의 lineWrapping 옵션을 토글한다.
  • CodeMirror가 없으면 기본 textarea로 fallback한다.

3. CodeMirror / Syntax Highlight

CodeMirror 5를 사용하며 mode 이름은 ahamark이다.

현재 하이라이트 대상:

  • heading: =, ==, ..., ======
  • horizontal rule: ----
  • list marker: bullet, ordered list marker
  • interpreter block: [[[#!...]]]와 블록 내부
  • shebang directive line: #!...
  • link: [Page], [Page Name], [Page|Alias], URL
  • macro: [[Macro(...)]]
  • tag/variable: {{name}}
  • date link: [YYYY-MM-DD], [YYYY-MM-DD|YYYY-MM-DDTHH:mm:ss]
  • inline style: bold, italic, bold italic, underline, strikeout
  • inline code: inline Copied!!

Style class:

  • ahamark-interpreter - [[[#!...]]] interpreter block
  • ahamark-interpreter-shebang - #!... directive line

4. 입력 보조

AhaWiki.Editor는 CodeMirror와 textarea 모두에 같은 입력 규칙을 적용한다.

  • 괄호/따옴표 자동 닫기: (), {}, [], "", '', `
  • 선택 영역이 있으면 opening character 입력 시 선택 영역을 감싼다.
  • Backspace는 커서 양쪽의 matching pair 를 지운다(사이에 선택 영역이 있으면 감싼 괄호만 벗긴다). [[[ 바로 뒤에서 누르면 \n]]] wrapper 를 통째로 지운다.
  • Enter는 현재 줄이 list이면 list prefix를 다음 줄로 이어준다.
  • Tab은 list line에서는 indent/outdent, 일반 위치에서는 tab 문자를 삽입한다.
  • --[ 입력 흐름에서는 현재 날짜/시간 link를 만든다 — [YYYY-MM-DD|YYYY-MM-DDTHH:mm:ss], 날짜 페이지로 가고 시각이 별칭이다. 2026-09-14 전에는 둘 사이가 공백이었는데, 지금 문법에서 공백은 이름의 일부다.
  • [[ / [[[ 주변 입력은 interpreter/block wrapper 작성을 돕는다.
  • IME 조합 중 key 값이 Process로 들어오는 경우를 보정한다.

5. 자동완성

자동완성 popup은 CodeMirror cursor 위치 또는 textarea 위치 기준으로 표시한다.

지원 context:

  • [[Ma - macro name
  • [Pa 또는 ["Pa - page name
  • [[[#!Ta - interpreter name
  • [[[#!table ts - table option
  • [[[#!Schema Pe - schema.org class
  • [[[#!Schema Person 블록 내부 property line - schema.org property

Source API:

  • GET /api/macroNames
  • GET /api/interpreterNames
  • GET /api/pageNames
  • GET /api/schemaClassNames
  • GET /api/schemaPropertyNames

동작:

  • prefix match만 보여주며 최대 30개까지 표시한다.
  • 정확히 일치하는 항목이 있으면 popup을 숨긴다.
  • ArrowUp / ArrowDown으로 선택을 이동한다.
  • Enter / Tab으로 선택 항목을 적용한다.
  • Escape, blur, editor 외부 click/scroll에서 popup을 닫는다.
  • macro 자동완성은 선택 시 MacroName()을 삽입하고 cursor를 괄호 안에 둔다.

6. Preview

  • editor 변경 시 debounce 후 POST /preview로 미리보기를 갱신한다.
  • debounce는 본문 길이에 따라 200ms에서 1000ms 사이로 clamp한다.
  • preview 요청에는 name, text, lineStart, lineEnd를 함께 보낸다.
  • 응답 HTML을 .previewPane에 넣은 뒤 Mermaid 초기화를 수행한다.
  • Paper preview에서는 content가 footer를 침범하면 .pageOverflow class를 붙인다.
  • cursor가 이동하면 preview의 data-line-start / data-line-end를 이용해 대응 위치로 scroll한다.
  • 대응 preview node에는 previewSyncBlink animation을 적용한다.

7. Partial Edit

편집 URL에 lineStart와 lineEnd가 있으면 해당 line range만 editor에 넣는다.

  • 저장 시 서버에서 최신 본문에 partial body를 merge한다.
  • preview와 scroll sync는 partial line offset을 반영한다.
  • generated See Also 영역은 partial 저장 merge 전에 제거한다.
  • partial edit 로 열면 Table Editor 가 자동으로 열린다. Switch to … 링크로 raw editor 와 오간다.

8. Table Editor

편집 화면을 열 때 본문이 #!gantt 블록이면 Gantt 편집기를, partial edit 이거나 본문 전체가 #!table 블록이면 Table Editor 를 자동으로 연다. partial edit 의 Switch to … 링크로 raw editor 와 오간다.

  • option input 에는 자동완성이 없다. tsv, csv, tsv 1, tsv 1 1, tsv 1 1 tablesorter 를 제안하는 것은 raw editor 의 [[[#!table 뒤다(아래 자동완성 참고)
    • 마지막 항목의 tablesorter 는 아무 일도 하지 않는다. InterpreterTable.Shebang.getClasses 가 모든 표에 tablesorter 클래스를 붙이고 .distinct 로 중복을 지우므로, 적어도 안 적어도 결과가 같다. 정렬을 켜는 스위치처럼 보이지만 아니다 — 목록에서 뺄지는 정하지 않았다. --2026-08-31
  • option input으로 table shebang option 변경
  • row/column add/delete
  • cell focus highlight
  • arrow key로 cell 이동
  • 입력 후 짧은 debounce로 raw text에 자동 반영

주의:

  • CSV 파싱은 현재 단순 delimiter split이다. quoted CSV field escaping은 처리하지 않는다.

9. Attachment

편집 화면에서 현재 page의 attachment를 조회하고 관리한다.

  • GET /api/pageAttachments로 기존 첨부 목록을 읽는다.
  • file input 또는 drag/drop으로 POST /api/uploadAttachment에 multipart upload한다.
  • clipboard image paste는 POST /api/uploadClipboardImage에 multipart upload한다.
  • 업로드 중 progress bar를 표시한다.
  • 업로드 성공 시 [[Attachment(...)]] macro를 cursor 위치에 삽입한다.
  • image attachment는 thumbnail로 표시하고, 그 외 파일은 icon으로 표시한다.
  • attachment list의 insert button은 macro를 다시 삽입한다.
  • delete button은 POST /api/deleteAttachment를 호출한다.
  • integrityStatus가 S3_ONLY인 항목은 insert/delete action을 숨긴다.

10. Wikipedia InfoBox

InfoBox 버튼은 Wikipedia page name을 입력받아 en, ko Wikipedia에서 infobox를 조회한다.

  • 조회 결과는 schema.org property 형태로 변환한다.
  • 변환 결과를 [[[#!Schema ... ]]] block으로 삽입한다.
  • # API URL과 sameAs 값을 함께 넣는다.
  • 원본 행은 바로 위에 # 라벨\t값 주석으로 남는다. 매핑이 값을 줄이더라도 원문은 문서에 남는다.
  • 블록 인자(클래스)는 Wikidata 에서 온다. 없으면 비워 둔다 — 아래.

10.1. 클래스는 Wikidata 가 정한다 (2026-09-05)

[[[#!Schema 뒤의 인자가 클래스인데, 가져오기는 이 자리를 늘 비워 두었고 Corporation·WebSite·SoftwareApplication 은 사람이 나중에 쳐 넣었다. 그 일을 Wikidata 가 대신한다.

문서의 Wikidata 항목이 무엇의 인스턴스인지(P31) 말하고, 그 클래스 상당수가 schema.org 대응 클래스(P1709 equivalent class, P2888 exact match)를 달고 있다. 극장(Q24354)은 PerformingArtsTheater 를 직접 가리킨다. 그 직접 대응만 쓴다.

  • 세종문화회관 → PerformingArtsTheater, GitHub → WebApplication WebSite, 트루먼 쇼 → Movie.
  • 롯데월드타워는 항목(Q494895)은 있지만 P31 이 「마천루」라 대응이 없다 → 인자를 비운다.
  • 상위 클래스(P279)로 한 단계 올라가는 것은 하지 않는다. 문서 열 개로 해 보니 Git 이 Service, Python 이 잡음 셋이었다. 틀린 클래스는 맞아 보이면서 틀리므로 빈 것보다 나쁘다.

출처는 # Wikidata\thttps://www.wikidata.org/wiki/Q… 주석으로 블록 안에 남는다. 언어판이 달라도 항목은 하나라, 인포박스가 잡힌 언어판을 차례로 물어 항목이 나오는 첫 언어판에서 멈춘다.

실제 API 로 확인한 결과(2026-09-10): Seoul → City, 봉준호 → Person, Breaking Bad → TVSeries, 기생충 → Movie. PHP 와 Amazon Web Services 는 항목은 찾지만 P31 의 클래스에 직접 대응이 없어 인자가 빈다. 브라우저 밖(Node)에서 재현할 때는 User-Agent 를 줘야 한다 — Wikimedia API 는 UA 가 없으면 403, node 면 429 를 답하고, 스크립트는 그것을 조용히 빈 결과로 삼킨다.

클래스가 둘 이상이면 인자도 둘 이상이다. InterpreterSchema 는 이제 인자를 전부 읽는다(2026-09-04 이전에는 첫 것만 읽었다) — JSON-LD @type 은 배열, RDFa typeof 는 공백 구분, 색인에는 양쪽에 오른다.

10.2. 매핑에서 판단이 필요했던 것들

소유는 매핑하지 않는다. schema.org에는 「이 장소를 X가 소유한다」에 해당하는 property가 없다. owns는 그 반대로, domain이 Person·Organization이고 range가 Product다. 여기 연결하면 맞아 보이면서 뜻이 뒤집히므로 Owner·소유·소유주·소유기관은 라벨 그대로 통과시킨다.

yearBuilt는 연도만 남는다. range가 Number라 1978-04-14를 담을 수 없다. 어휘 전체에서 range가 Date·DateTime이면서 domain에 Place 계열이 있는 property는 하나도 없다 — 건물의 완공일을 온전히 담을 자리가 schema.org에 없다. 월·일은 위의 # 주석에 남는다.

반면 조직은 다르다. 설립일은 foundingDate(domain Organization, range Date)로 가므로 1963-01-01이 그대로 남는다.

좌표는 latitude·longitude 두 줄로 쪼갠다. geo의 range는 GeoCoordinates 객체라 문자열 한 줄로는 맞지 않는다. 읽지 못한 값은 geo인 채로 둔다 — 지어내지 않는다.

  • 도분초는 영어(37°33′36″N)와 한국어(북위 37° 34′ 21″)를 모두 읽는다. 방위가 숫자 뒤·앞 어디에 오든 된다.
  • 도분초를 못 읽으면 문장 끝에 붙는 십진수 짝(… / 37.5725; 126.9756)으로 넘어간다.
  • 라벨은 좌표와 지리 좌표계를 모두 본다. ko.wikipedia 건물 인포박스는 후자를 쓴다.

한 칸이 여러 값으로 쪼개져 온다. Wikipedia가 April 11, 1931; 95 years ago (…)를 쉼표에서 나누므로 값 하나씩 보는 변환은 yearBuilt를 April 11\t1931로 만든다. 그래서 변환은 행 전체를 본다.

10.3. 수작업 스키마 전수 검토 (2026-09-04 ~ 05)

CalculatedSchemaOrg 의 (클래스, 속성) 조합 전부를 배포된 어휘 파일과 대조했다. 550여 문서.

고친 것

  • writer 137건 → author. schema.org 에 writer 는 없다. 가져오기가 만든 게 아니라 Writer 라벨이 미매핑으로 통과한 것을 사람이 소문자로 바꾼 것이었다. 라벨은 이제 author 로 간다.
  • politicalParty 4건 → memberOf(Person → Organization). 정당·Political party 라벨도 같이.
  • Cognag → Cognac(Rémy Martin). 커스텀 클래스지만 철자.
  • SoftwareSourceCode ↔ SoftwareApplication — 한쪽 속성이 다른 쪽 블록에 있던 것. 클래스를 바꾸거나 속성을 옮기지 않고 짝 클래스를 인자에 더했다 (20건). 라이브러리는 코드이면서 쓰는 물건이고, 여섯 문서(Git·bash·ncdu…)는 이미 그렇게 적혀 있었다. scripts/add-paired-class.mjs. 소프트웨어 문서 47개가 전부 정합해졌다.
  • Graphviz·Homebrew 2건은 반대로 처리했다. 두 문서는 프로그램용 SoftwareApplication 블록과 코드용 SoftwareSourceCode 블록을 따로 두고 있었고, 두 번째의 applicationCategory 는 첫 번째와 값까지 같은 사본이었다. 클래스를 넓히면 한 문서가 애플리케이션을 두 번 선언한다. 그래서 그 줄을 지웠다.

두기로 한 것 — 대안이 없거나, 고치면 더 틀려진다.

  • TVSeries.duration 29건. duration 은 에피소드의 속성이라 시리즈에는 안 맞지만, 시리즈 길이를 담을 자리가 schema.org 에 없다. numberOfEpisodes 는 다른 사실이다.
  • ComputerLanguage.datePublished 16건, designer 15건. ComputerLanguage 는 Intangible 이라 CreativeWork 속성이 붙지 않고, designer 는 속성 자체가 없다. 언어의 설계자·발표일을 담을 자리가 없다.
  • Cinematography 4건, volume 3건 — 촬영감독·권 수에 해당하는 속성이 없다. 라벨 그대로 통과한 정직한 상태다.
  • Corporation.industry 2건 — industry 의 domain 은 JobPosting 이다. 기업의 업종을 담을 속성이 없다.
  • Standard·Poem·Whiskey·Cognac — 커스텀 클래스. 색인의 「Custom」 절이 이를 위한 자리다.
  • logo 가 소프트웨어 블록에 있는 것 — domain 은 Organization·Brand·Product 이지만 InterpreterSchema.imageKeys 가 클래스와 무관하게 그림으로 그린다. 표시 관례다.

검산은 파생 테이블이 아니라 페이지를 다시 읽어서 한다. 테이블은 재계산이 밀려, writer 를 전부 바꾼 직후에도 121건이 남았다고 답했다.

저장이 재계산을 부른다. 그래서 고친 문서는 색인도 따라온다. 반대로 고칠 게 없던 문서는 안 따라온다 — Git·bash·ncdu·ClamAv·Mermaid·uBlock Origin 은 원래 두 클래스를 적고 있어 편집하지 않았고, 색인은 아직 한 클래스로만 보여 준다. 렌더링(typeof·JSON-LD @type)은 이미 둘 다 맞다. 다음 편집 때 맞춰진다. POST /api/Admin/Site/:seq/Calculate?pageName=... 로 한 문서만 재계산할 수 있지만 API 키로는 403 이고 세션 관리자여야 한다.

11. 저장

  • form은 ajax로 submit한다.
  • comment는 필수 입력이다.
  • submit 직전에 CodeMirror 내용을 textarea로 sync한다.
  • minorEdit 체크박스를 지원한다.
  • 관리자에게는 page permission mode control을 표시한다.
  • reCAPTCHA 가 켜져 있으면 저장할 때마다 Google 에 새 token 을 받아 함께 보낸다.
    • * 켜짐은 site key 와 secret key 가 둘 다 설정된 것이다. 그때만 페이지에 ahawiki-recaptcha-site-key meta 가 실리고, 그때만 서버가 token 없는 저장을 거절한다. 키 하나만 있으면 끈 것과 같다 — 하나만으로 켜면 token 을 받을 길 없이 저장이 전부 거절된다.
    • * token 은 한 번, 2분 동안만 통한다. 그래서 미리 받아 두지 않는다. 2026-09-13 전에는 열 때와 60초마다 받아 두어서, 1분 안의 두 번째 저장은 이미 쓴 token 을, 첫 token 이 오기 전의 저장은 빈 token 을 보냈다.
    • * token 을 받지 못하면 저장하지 않고 알린다. 광고 차단기가 reCAPTCHA 를 막으면 이렇게 된다.
    • * token 을 받는 코드는 AhaWiki.ReCaptcha.js 하나이고, InterpreterKanban 도 이것을 쓴다. Google 의 스크립트는 처음 필요할 때 읽는다(편집 화면은 열 때 미리).
  • saveSenderId를 hidden field에 넣어 WebSocket page update broadcast에서 자기 tab의 저장 알림을 제외한다.

오류 처리:

  • 403 - permission denied
  • 403 reCAPTCHA token is required - reCAPTCHA 가 켜져 있는데 token 이 없다. 2026-09-13 전에는 빈 token 이면 검사를 건너뛰었다 — 어느 클라이언트든 token 을 빼면 검사 없이 저장할 수 있었고, Kanban 이 그렇게 저장하고 있었다.
  • 403 reCAPTCHA failed - Google 이 token 을 받아들이지 않았다. 만료되었거나 이미 쓴 token 도 여기로 온다.
  • 409 - revision conflict
  • 400 body == latestText - 변경 없는 저장

12. Hotkeys

편집 화면에서는 Alt와 다음 key 조합으로 toolbar action을 실행한다. edit.hotkeyAlt.js 가 .edit 안에서 kbd[data-hotkey-alt=키] 를 찾아 클릭한다.

2026-09-10 까지는 문서 전체에서 찾았다. 상단 내비게이션(_base.scala.html)의 S·R·W·H·B·E 힌트도 같은 속성을 쓰므로, Alt S 는 저장과 함께 검색 창을 열고 Alt R 은 Rename 과 Random(/r) 두 링크를 다 따라갔으며, 아래 목록에 없는 Alt H·Alt B·Alt E 는 History·Blame·Edit 링크로 이동해 편집 중인 내용을 잃었다. test/manual/editor-hotkeys/ 가 브라우저에서 키를 눌러 이것을 재현하고, 고쳐진 것을 확인한다.

  • Alt W - Word Wrap
  • Alt I - InfoBox
  • Alt A - Attach
  • Alt C - revision comment input focus
  • Alt M - Minor edit
  • Alt S - Save
  • Alt R - Rename
  • Alt T - Delete

14. 부분 편집 링크

문단마다 붙는 편집 링크는 logics.wikis.PartialEdit 한 곳에서 만듭니다.

  • revision — 지금 보고 있는 리비전. 쿼리스트링이 우선입니다. 옛 리비전을 보는 중이라면 편집 링크도 그 리비전을 가리켜야 하기 때문입니다. 링크에 리비전을 실어 두는 덕에, 문단을 열어 둔 사이 페이지가 바뀌면 저장 시점에 알아챌 수 있습니다.
  • editUrl — /w/{페이지}?action=edit&revision=N&lineStart=N&lineEnd=N

이 둘이 인터프리터 블록용(ExtractConvertInjectInterpreter)과 제목용(InterpreterWiki)에 각각 한 벌씩, 글자 하나 다르지 않게 있었습니다. 두 렌더러는 페이지를 나누는 방식이 달라서 따로 있는 게 맞지만, **링크 모양은 달라질 수 없는 것** 입니다.

15. 서식 툴바 (2026-09-16)

편집 툴바(toolbarLeft)에 서식 버튼을 둔다 — 굵게·기울임·밑줄·취소선·인라인 코드·제목·불릿 목록·링크. 선택을 감싸거나(굵게 → '''…''') 줄 앞에 붙인다(제목 == , 목록 * ). 빈 선택이면 커서를 프리픽스와 서픽스 사이에 둔다.

  • 텍스트 연산은 순수 함수 AhaWiki.Format.wrap / prefixLines(public/js/AhaWiki.Format.js)다 — «전체 텍스트 + 선택»을 받아 «최소 편집(범위·대체·새 선택)»을 돌려준다. DOM 을 모르므로 단위테스트되고(test/ahawiki.format.test.mjs), CodeMirror·textarea 양쪽이 같은 결과를 적용한다.
  • 적용은 CodeMirror replaceRange(undo 보존), textarea 는 값 치환. 배선은 edit.page.js 의 .formatButton 클릭 핸들러. 버튼은 data-format 으로 동작을 고른다.
  • WYSIWYG 이 아니다 — 소스가 정본이고 버튼은 AhaMark 를 넣어 줄 뿐이다(아래 «WYSIWYG» 의 접근 3 첫걸음).

16. WYSIWYG (설계안 — 접근 3 첫걸음만 구현, 나머지 결정 대기)

ToDo 의 «WYSIWYG editing» 설계안이다. 접근 3(서식 툴바)의 첫걸음은 2026-09-16 에 만들어 배포했다 — 위 «서식 툴바» 절. 지금 편집기는 CodeMirror 소스 + 라이브 미리보기 + (표·간트) 구조 편집기 + 서식 툴바다. 진짜 WYSIWYG(접근 2 하이브리드)은 아래 열린 결정 을 소유자가 정한 뒤 단계별로 만든다.

16.1. 왜 큰가 — 왕복 문제

렌더는 AhaMark → HTML 단방향이다. WYSIWYG 은 반대로 «편집한 리치 문서 → AhaMark» 직렬화가 필요하고, 그게 어렵다.

  • 블록([[[#!Table/Vim/Kanban/Map/Schema/Gantt/Markdown …]]]), 매크로([[…]]), 커스텀 링크 문법([a b]·따옴표), 디렉티브(#!var …), 주석 — 대부분 리치로 «직접» 편집할 수 없고 그대로 보존돼야 한다.
  • 무손실 저장이 핵심 위험이다. 저장 때 AhaMark 를 조용히 재작성하면 페이지가 상한다. 소스가 정본이어야 하고, 손대지 않은 부분은 바이트 그대로 다시 나와야 한다.

16.2. 접근안

  • 풀 리치 WYSIWYG (ProseMirror/TipTap/CKEditor + AhaMark 직렬화기). UX 최고, 노력 최대(수 주 이상). 모든 인터프리터/매크로를 모델링해야 하고 왕복 손실 위험이 크다. 블록은 어차피 «아톰»(소스로만 편집)이 된다.
  • 하이브리드 (권장) — 프로즈 계층만 리치로 한다: 제목·굵게/기울임/밑줄/취소선·목록(새 탭 규칙 포함)·링크·가로선·인라인 코드. 이건 AhaMark 로 정확히 왕복된다. [[[블록]]] 과 [[매크로]] 는 «아톰 노드»로 렌더 결과만 보여 주고, 편집은 기존 표·간트 편집기나 소스 모달로 연다. 지금의 표·간트 편집기 패턴을 그대로 넓히는 방향이다.
  • 리치 툴바 + 구조 편집기 확장 — 소스 편집기 위에 서식 버튼(굵게 → '''…''' 삽입 등) + 블록별 편집기 추가. 진짜 WYSIWYG 은 아니지만 노력·위험 최소이고 UX 이득의 상당수를 준다.
  • 미리보기를 contenteditable 로 — HTML → AhaMark 역변환이 손실적이라 비권장.

권장: 2(하이브리드), 단계별. 3 은 «꼭 진짜 WYSIWYG 이 아니어도 되면» 의 저비용 대안.

16.3. 단계 (하이브리드 기준)

  • Phase 1 — 블록·매크로 없는 프로즈 페이지에서 리치 편집 + AhaMark 왕복 검증. 토글(WYSIWYG/소스), 소스가 정본. 왕복이 손실적이면 저장을 거절하고 알린다.
  • Phase 2 — 블록/매크로를 아톰 노드로(렌더 인라인, 편집은 기존 편집기/소스 모달).
  • Phase 3 — 표를 인라인 표 편집기로 연결, 이미지·붙여넣기.

16.4. 위험·유의

  • 왕복 무손실(최대 위험) — 완화: 소스 정본 + «손대지 않은 부분은 바이트 동일» 게이트(따옴표·백틱 마이그레이션에서 쓴 렌더 대조와 같은 정신).
  • 기존 편집 기능과의 통합: reCAPTCHA 저장 토큰, partial edit, 자동완성, 첨부, 핫키, WebSocket 저장 알림(saveSenderId).
  • 에디터 라이브러리 크기(번들 vs CDN)와 IME(한글 조합) 처리.

16.5. 열린 결정 (소유자)

  • 접근: 2(하이브리드) 인가 3(툴바) 인가?
  • 범위: Phase 1 을 어디까지?
  • 에디터 라이브러리: ProseMirror / TipTap / CKEditor, 번들 vs CDN?
  • 정본: 소스(AhaMark) 유지가 맞나(권장), 아니면 문서 모델로 전환?

17. See Also

17.2. Similar Pages

Similar pages by cosine similarity. Words after page name are term frequency.

  • 37.61% InterpreterSchema schema(21:21), aha(19:10), wiki(14:13), code(18:7), name(8:15), preview(9:9), interpreter(15:2), page(13:1), org(8:6), date(4:10)
  • 35.56% ToDo interpreter(15:19), wiki(14:20), aha(19:5), 편집(20:1), api(15:6), page(13:8), table(12:9), code(18:2), editor(17:3), js(17:2)
  • 30.40% Dev SchemaOrgVocabulary schema(21:45), org(8:40), class(5:25), js(17:8), 없다(13:9), aha(19:2), interpreter(15:1), wiki(14:2), 같은(5:10), get(7:7)
  • 30.11% Api api(15:84), page(13:27), key(6:32), edit(20:15), aha(19:14), wiki(14:15), name(8:20), 저장(10:15), revision(4:19), 페이지(1:19)

17.3. Adjacent Pages

Control
≤ 32
all
1.0x
1.0x
80
-120
ON
Metrics
Nodes(visible/total)0/0
Links(visible/total)0/0
Avg degree0.00
Depth coverage0
Queue(fetch/graph)0 / 0
Zoom(scale)1.00x
Ctrl/⌘ + Scroll: Zoom
Root 1-hop 2-hop+