Skip to content
Open
28 changes: 21 additions & 7 deletions docs/content/en/api-reference/editor/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ firmware that violates those barriers.
interface EditorCommitRequest {
baseRevision: number;
mutationId: string;
gestureId?: string;
gestureIds?: string[];
changes: EditorPatchV1;
}

Expand Down Expand Up @@ -113,6 +115,12 @@ intended for immediate IPC retries; the in-memory deduplication window does not
survive an app restart. Reusing a retained ID for a different request is
rejected.

`gestureId` is the representative history gesture. `gestureIds` carries every
preview session coalesced into the request so the committed event can echo the
complete set. Both fields are optional and accept UUIDs only. The `gestureIds`
array can contain at most 32 entries, and the combined unique set across both
fields is also limited to 32 IDs.

If the submitted values are already current, the result keeps the current
revision, returns an empty `changedFields`, and emits no canonical committed
event. Compatibility wrappers may still project their legacy per-field refresh
Expand Down Expand Up @@ -153,6 +161,8 @@ the human-readable `message`.
type EditorCommitErrorCode =
| 'REVISION_CONFLICT'
| 'VALIDATION_FAILED'
| 'TOO_MANY_GESTURE_IDS'
| 'INVALID_GESTURE_ID'
| 'PAIRED_UPDATE_REQUIRED'
| 'MUTATION_ID_REUSED'
| 'IO_ERROR';
Expand All @@ -169,13 +179,15 @@ interface EditorCommitError {
}
```

| Code | Meaning | `retryable` |
| ------------------------ | ---------------------------------------------------------- | ----------- |
| `REVISION_CONFLICT` | `baseRevision` is stale; read, reconcile, and commit again | `true` |
| `VALIDATION_FAILED` | The completed document violates an editor validation rule | `false` |
| `PAIRED_UPDATE_REQUIRED` | A structural key change omitted its paired collection | `false` |
| `MUTATION_ID_REUSED` | The same mutation ID was used for a different request | `false` |
| `IO_ERROR` | The document could not be persisted | `true` |
| Code | Meaning | `retryable` |
| ------------------------ | ------------------------------------------------------------------------- | ----------- |
| `REVISION_CONFLICT` | `baseRevision` is stale; read, reconcile, and commit again | `true` |
| `VALIDATION_FAILED` | The completed document violates an editor validation rule | `false` |
| `TOO_MANY_GESTURE_IDS` | `gestureIds` exceeds 32 entries or the combined unique set exceeds 32 IDs | `false` |
| `INVALID_GESTURE_ID` | A gesture ID is not a UUID within the 64-byte limit | `false` |
| `PAIRED_UPDATE_REQUIRED` | A structural key change omitted its paired collection | `false` |
| `MUTATION_ID_REUSED` | The same mutation ID was used for a different request | `false` |
| `IO_ERROR` | The document could not be persisted | `true` |

`details.currentRevision`, `details.validationCode`, or `details.field` is
included when it applies to the error.
Expand All @@ -192,6 +204,8 @@ interface EditorCommittedV1 {
schemaVersion: 1;
revision: number;
mutationId: string;
gestureId?: string | null;
gestureIds?: string[];
origin?: string;
changedFields: EditorField[];
patch: EditorPatchV1;
Expand Down
1 change: 1 addition & 0 deletions docs/content/en/declarative-api/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ Use `resizable` option to let users resize elements directly in the grid.
### resizable

Set `resizable: true` to show 8-direction resize handles:
The initial size is 200×150. A saved or estimated size takes precedence when available.

```javascript
dmn.plugin.defineElement({
Expand Down
2 changes: 1 addition & 1 deletion docs/content/en/guide/installation/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ description: How to download, install, and run DM Note

DM Note can be downloaded from GitHub Releases.

1. Download the latest version from the [GitHub Releases page](https://github.com/lee-sihun/DmNote/releases).
1. Download the latest version from the [GitHub Releases page](https://github.com/DmNote-App/DmNote/releases).
2. Extract the downloaded ZIP file to your desired location.
3. Run the `DM Note.exe` file.

Expand Down
7 changes: 7 additions & 0 deletions docs/content/en/guide/settings/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ When enabled, the overlay window ignores mouse events (click-through) allowing y
When locked, you cannot directly interact with the overlay.
</Callout>

### Detaching the Properties Panel

Use the detach button at the top of the properties panel to move it into a separate window at any time. Key, note, and counter editing as well as layer management work the same in the detached window.

- Use the X button on the detached window or the reattach button at the top of the panel to return it inline.
- While detached, picking a gradient color anchor on the canvas is limited.

## Graphics Settings

### Rendering Option
Expand Down
33 changes: 23 additions & 10 deletions docs/content/ko/api-reference/editor/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -75,15 +75,17 @@ console.log(revision, document.keyPositions);
고장까지 애플리케이션이 절대 보장할 수는 없습니다.

<Callout type="info">
앱의 Undo/Redo도 편집 컬렉션 6개, 커스텀 탭 정보, 선택 모드, 카운터,
프리셋 설정, 탭별 노트 설정을 백엔드 store 트랜잭션 한 번으로 함께 복원합니다.
내부 커맨드는 공개 플러그인 API로 노출하지 않습니다.
앱의 Undo/Redo도 편집 컬렉션 6개, 커스텀 탭 정보, 선택 모드, 카운터, 프리셋
설정, 탭별 노트 설정을 백엔드 store 트랜잭션 한 번으로 함께 복원합니다.
내부 커맨드는 공개 플러그인 API로 노출하지 않습니다.
</Callout>

```typescript
interface EditorCommitRequest {
baseRevision: number;
mutationId: string;
gestureId?: string;
gestureIds?: string[];
changes: EditorPatchV1;
}

Expand Down Expand Up @@ -111,6 +113,11 @@ console.log('커밋된 revision:', result.revision);
방지는 즉시 IPC 재시도용이며 앱을 재시작하면 유지되지 않습니다. 아직 보관 중인
ID를 다른 요청에 재사용하면 거절됩니다.

`gestureId`는 히스토리 병합의 대표 게스처입니다. `gestureIds`는 요청 하나로
합쳐진 모든 프리뷰 세션을 전달하여 committed 이벤트가 전체 집합을 echo하게
합니다. 두 필드는 모두 선택 사항이며 UUID만 허용합니다. `gestureIds` 배열은
항목이 최대 32개이고, 두 필드를 합친 고유 ID 집합도 최대 32개로 제한됩니다.

제출한 값이 이미 현재 값과 같으면 현재 revision과 빈 `changedFields`를
반환하고 canonical committed 이벤트는 발행하지 않습니다. 호환 wrapper는 예전
필드별 refresh 이벤트를 한 번 투영할 수 있지만, 같은 `mutationId` 재시도에서는
Expand Down Expand Up @@ -151,6 +158,8 @@ await dmn.editor.commit({
type EditorCommitErrorCode =
| 'REVISION_CONFLICT'
| 'VALIDATION_FAILED'
| 'TOO_MANY_GESTURE_IDS'
| 'INVALID_GESTURE_ID'
| 'PAIRED_UPDATE_REQUIRED'
| 'MUTATION_ID_REUSED'
| 'IO_ERROR';
Expand All @@ -167,13 +176,15 @@ interface EditorCommitError {
}
```

| 코드 | 의미 | `retryable` |
| ------------------------ | ------------------------------------------------- | ----------- |
| `REVISION_CONFLICT` | `baseRevision`이 오래됨. 다시 조회·조정한 뒤 커밋 | `true` |
| `VALIDATION_FAILED` | 완성된 문서가 에디터 검증 규칙을 위반함 | `false` |
| `PAIRED_UPDATE_REQUIRED` | 키 구조 변경에 짝 컬렉션이 빠짐 | `false` |
| `MUTATION_ID_REUSED` | 같은 mutation ID를 다른 요청에 재사용함 | `false` |
| `IO_ERROR` | 문서를 디스크에 저장하지 못함 | `true` |
| 코드 | 의미 | `retryable` |
| ------------------------ | --------------------------------------------------------- | ----------- |
| `REVISION_CONFLICT` | `baseRevision`이 오래됨. 다시 조회·조정한 뒤 커밋 | `true` |
| `VALIDATION_FAILED` | 완성된 문서가 에디터 검증 규칙을 위반함 | `false` |
| `TOO_MANY_GESTURE_IDS` | `gestureIds`가 32개를 넘거나 합친 고유 ID가 32개를 초과함 | `false` |
| `INVALID_GESTURE_ID` | gesture ID가 UUID가 아니거나 64바이트를 초과함 | `false` |
| `PAIRED_UPDATE_REQUIRED` | 키 구조 변경에 짝 컬렉션이 빠짐 | `false` |
| `MUTATION_ID_REUSED` | 같은 mutation ID를 다른 요청에 재사용함 | `false` |
| `IO_ERROR` | 문서를 디스크에 저장하지 못함 | `true` |

오류에 해당할 때 `details.currentRevision`, `details.validationCode`,
`details.field`가 함께 제공됩니다.
Expand All @@ -190,6 +201,8 @@ interface EditorCommittedV1 {
schemaVersion: 1;
revision: number;
mutationId: string;
gestureId?: string | null;
gestureIds?: string[];
origin?: string;
changedFields: EditorField[];
patch: EditorPatchV1;
Expand Down
1 change: 1 addition & 0 deletions docs/content/ko/declarative-api/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ dmn.plugin.defineElement({
### resizable

`resizable: true`로 설정하면 요소에 8방향 리사이즈 핸들이 표시됩니다.
첫 생성 크기는 200×150이며, 저장된 크기나 추정 크기가 있으면 해당 값을 우선합니다.

```javascript
dmn.plugin.defineElement({
Expand Down
2 changes: 1 addition & 1 deletion docs/content/ko/guide/installation/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ description: DM Note 다운로드, 설치 및 실행 방법

DM Note는 GitHub Releases에서 다운로드할 수 있습니다.

1. [GitHub Releases 페이지](https://github.com/lee-sihun/DmNote/releases)에서 최신 버전을 다운로드합니다.
1. [GitHub Releases 페이지](https://github.com/DmNote-App/DmNote/releases)에서 최신 버전을 다운로드합니다.
2. 다운로드한 ZIP 파일의 압축을 원하는 위치에 해제합니다.
3. `DM Note.exe` 파일을 실행합니다.

Expand Down
7 changes: 7 additions & 0 deletions docs/content/ko/guide/settings/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ DM Note의 다양한 설정 옵션을 안내합니다.
고정을 켠 상태에서는 오버레이를 직접 조작할 수 없습니다.
</Callout>

### 속성 패널 분리

캔버스 우측 속성 패널의 상단 분리 버튼을 누르면 언제든 별도 창으로 분리할 수 있습니다. 분리된 창에서도 키·노트·카운터 편집과 레이어 관리를 동일하게 사용할 수 있습니다.

- 분리 창의 X 버튼 또는 패널 상단의 되돌리기 버튼으로 다시 인라인 패널로 복귀합니다.
- 분리 상태에서는 그라디언트 색상의 캔버스 앵커 지정이 제한됩니다.

## 그래픽 설정

### 렌더링 옵션
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/gen/schemas/acl-manifests.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src-tauri/permissions/dmnote-allow-all.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"app_quit",
"app_quit_after_editor_flush",
"app_restart",
"commit_gesture",
"counter_animation_create",
"counter_animation_delete",
"counter_animation_list",
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/src/commands/app/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ fn app_auto_update_windows(
) -> CmdResult<AutoUpdateResult> {
use std::time::Duration;

const REPO_OWNER: &str = "lee-sihun";
const REPO_OWNER: &str = "DmNote-App";
const REPO_NAME: &str = "DmNote";
const ASSET_NAME: &str = "DM.NOTE.exe";
const SIGNATURE_ASSET_NAME: &str = "DM.NOTE.exe.sig";
Expand Down
90 changes: 90 additions & 0 deletions src-tauri/src/commands/editor/gesture.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
use tauri::{AppHandle, State, WebviewWindow};

use crate::{
commands::{
editor::state::{emit_best_effort, publish_editor_change, publish_legacy_editor_fields},
plugin::instances::publish_plugin_instances_changed,
},
errors::{CmdResult, EditorCommitError},
models::{GestureCommitRequest, GestureCommitResult, PluginInstancesChangedPayload},
services::preview_broker::PreviewBroker,
state::{gesture::validate_gesture_commit_request, AppState},
};

const MAIN_WINDOW_LABEL: &str = "main";

#[tauri::command]
pub fn commit_gesture(
state: State<'_, AppState>,
broker: State<'_, PreviewBroker>,
app: AppHandle,
window: WebviewWindow,
request: GestureCommitRequest,
) -> CmdResult<GestureCommitResult> {
validate_gesture_commit_request(&request)?;
if window.label() != MAIN_WINDOW_LABEL {
return Err(crate::errors::CommandError::msg(
"GESTURE_MUTATION_NOT_ALLOWED",
));
}

let authority = state
.plugin_authority()
.admit(request.authority_generation)
.map_err(crate::errors::CommandError::msg)?;
let admission = state
.admit_frontend_history_mutation(window.label())
.map_err(|_| EditorCommitError::history_in_progress())?;
let mutation_id = request.mutation_id.clone();
let gesture_id = request.gesture_id.clone();
let requested_fields = request
.editor_changes
.as_ref()
.map_or_else(Vec::new, |changes| changes.included_fields());
let previous_mode = requested_fields
.contains(&crate::models::EditorField::Keys)
.then(|| state.keyboard.current_mode());

let committed = state
.store
.commit_gesture_with_admission(request, admission)?;
let outcome = &committed.outcome;
if let Err(error) =
broker.finish_committed_session(window.label(), &gesture_id, outcome.change.is_none())
{
log::warn!("failed to finish committed gesture preview session: {error}");
}

if let Some(change) = outcome.change.as_ref() {
publish_editor_change(state.inner(), &app, change, false);
if !outcome.replayed {
publish_legacy_editor_fields(state.inner(), &app, change, &requested_fields);
if previous_mode.is_some_and(|mode| mode != change.selected_key_type) {
emit_best_effort(
&app,
"keys:mode-changed",
&serde_json::json!({ "mode": &change.selected_key_type }),
);
}
}
}
if !outcome.replayed {
for plugin_id in &outcome.changed_plugin_ids {
publish_plugin_instances_changed(
&app,
&PluginInstancesChangedPayload {
plugin_id: plugin_id.clone(),
revision: outcome.result.plugin_model_revision,
origin_mutation_id: Some(mutation_id.clone()),
},
);
}
}
if let Some(status) = outcome.history_status.as_ref() {
emit_best_effort(&app, "history:status", status);
}

let mut result = outcome.result.clone();
result.authority_generation = authority.generation();
Ok(result)
}
29 changes: 29 additions & 0 deletions src-tauri/src/commands/editor/history.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,20 @@ fn run_history_operation(
);
}
Some(HistoryAuxChange::PluginElements { .. }) => {}
Some(HistoryAuxChange::PluginElementsBatch { .. }) => {
if let Some(change) = committed_change.filter(|change| {
change
.result
.changed_fields
.contains(&crate::models::EditorField::Keys)
}) {
state.apply_committed_editor_keys_without_counters(
outcome.runtime_publication_generation,
&change.document.keys,
&change.selected_key_type,
);
}
}
None => {
if let Some(change) = committed_change.filter(|change| {
change
Expand Down Expand Up @@ -325,6 +339,21 @@ fn publish_history_aux_change(
},
);
}
Some(HistoryAuxChange::PluginElementsBatch {
plugin_ids,
revision,
}) => {
for plugin_id in plugin_ids {
publish_plugin_instances_changed(
app,
&PluginInstancesChangedPayload {
plugin_id: plugin_id.clone(),
revision: *revision,
origin_mutation_id: None,
},
);
}
}
None => {}
}
}
Expand Down
1 change: 1 addition & 0 deletions src-tauri/src/commands/editor/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod css;
pub mod gesture;
pub mod history;
pub mod js;
pub mod note_tab;
Expand Down
3 changes: 1 addition & 2 deletions src-tauri/src/commands/editor/preview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ pub fn editor_preview_cancel(
broker: State<'_, PreviewBroker>,
window: WebviewWindow,
session_id: String,
min_revision: Option<u64>,
) -> Result<(), String> {
broker.cancel(window.label(), &session_id, min_revision)
broker.cancel(window.label(), &session_id)
}
Loading
Loading