feat(spreadsheet): 우클릭 메뉴에 구글 시트 항목 덧붙임(81-3)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MPcuokNm1ZR2kqgGGDfaJA
This commit is contained in:
@@ -28,10 +28,35 @@ function within(range: CellRange, r: number, c: number): boolean {
|
||||
|
||||
/** `ctx.root` 의 contextmenu 를 받음(root null) */
|
||||
export function attachMenu(ctx: SpreadsheetContext): PartHandle {
|
||||
if (ctx.readOnly) return { root: null, refresh() {}, destroy() {} };
|
||||
|
||||
const menu = createMapContextMenu("ss");
|
||||
ctx.root.append(menu.element);
|
||||
|
||||
// 읽기 전용 · 미리보기 시트 — 메뉴 없이 무반응 대신 [복사] 한 줄만(81-3)
|
||||
if (ctx.readOnly) {
|
||||
const onReadOnlyMenu = (ev: MouseEvent): void => {
|
||||
if (!ctx.grid.hitTest(ev.clientX, ev.clientY)) return;
|
||||
ev.preventDefault();
|
||||
const box = ctx.root.getBoundingClientRect();
|
||||
menu.open(ev.clientX - box.left, ev.clientY - box.top, [
|
||||
[st("MenuCopyOnly"), () => document.execCommand("copy")],
|
||||
]);
|
||||
};
|
||||
ctx.root.addEventListener("contextmenu", onReadOnlyMenu);
|
||||
return {
|
||||
root: menu.element,
|
||||
refresh() {},
|
||||
destroy() {
|
||||
ctx.root.removeEventListener("contextmenu", onReadOnlyMenu);
|
||||
menu.close();
|
||||
menu.element.remove();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const loadMenuItems = () => import("./spreadsheet_menu_items");
|
||||
const fallbackPasteHint = (ok: boolean): void => {
|
||||
if (!ok) showToast(st("MenuPasteHint"), "info");
|
||||
};
|
||||
ctx.cellMenuExtra ??= () => []; // 칸 메뉴 있음 — 조건 만들기가 이 끝에 붙음
|
||||
|
||||
function isMerged(range: CellRange): boolean {
|
||||
@@ -79,7 +104,40 @@ export function attachMenu(ctx: SpreadsheetContext): PartHandle {
|
||||
return [
|
||||
[st("MenuCut"), () => cutCopy("cut")],
|
||||
[st("MenuCopy"), () => cutCopy("copy")],
|
||||
[st("MenuPasteHint"), () => showToast(st("MenuPasteHint"), "info")],
|
||||
[
|
||||
st("MenuPaste"),
|
||||
() => void loadMenuItems().then((m) => m.pasteFromMenu(ctx).then(fallbackPasteHint)),
|
||||
],
|
||||
{
|
||||
label: st("MenuPasteSpecial"),
|
||||
children: [
|
||||
[
|
||||
st("MenuPasteValues"),
|
||||
() =>
|
||||
void loadMenuItems().then((m) =>
|
||||
m.pasteSpecialFromMenu(ctx, "값").then(fallbackPasteHint),
|
||||
),
|
||||
],
|
||||
[
|
||||
st("MenuPasteFormat"),
|
||||
() =>
|
||||
void loadMenuItems().then((m) =>
|
||||
m.pasteSpecialFromMenu(ctx, "서식").then(fallbackPasteHint),
|
||||
),
|
||||
],
|
||||
],
|
||||
},
|
||||
[st("MenuInsertCells"), () => void loadMenuItems().then((m) => m.insertCells(ctx))],
|
||||
[st("MenuDeleteCells"), () => void loadMenuItems().then((m) => m.deleteCells(ctx))],
|
||||
[
|
||||
st("MenuDeleteRow"),
|
||||
() => ctx.dispatch({ 종류: "행지우기", 시트: ctx.selection.시트, at: r, 수: 1 }),
|
||||
],
|
||||
[
|
||||
st("MenuDeleteCol"),
|
||||
() => ctx.dispatch({ 종류: "열지우기", 시트: ctx.selection.시트, at: c, 수: 1 }),
|
||||
],
|
||||
{ divider: true },
|
||||
[
|
||||
st("MenuClearContent"),
|
||||
() => ctx.dispatch({ 종류: "내용지움", 시트: ctx.selection.시트, 범위: [range] }),
|
||||
@@ -107,6 +165,15 @@ export function attachMenu(ctx: SpreadsheetContext): PartHandle {
|
||||
...(note
|
||||
? ([[st("MenuCommentDelete"), () => deleteComment(ctx, r, c)]] as MapContextMenuItem[])
|
||||
: []),
|
||||
{ divider: true },
|
||||
[st("MenuAutofit"), () => void loadMenuItems().then((m) => m.autofitCols(ctx, [c]))],
|
||||
{ divider: true },
|
||||
[st("MenuCondFmt"), () => void loadMenuItems().then((m) => m.openCondFmt(ctx))],
|
||||
[st("MenuValidation"), () => void loadMenuItems().then((m) => m.openValidation(ctx))],
|
||||
[st("MenuNames"), () => void loadMenuItems().then((m) => m.openNames(ctx))],
|
||||
{ divider: true },
|
||||
[st("MenuFreeze"), () => void loadMenuItems().then((m) => m.freezeAtActiveCell(ctx))],
|
||||
[st("MenuUnfreeze"), () => void loadMenuItems().then((m) => m.unfreezePanes(ctx))],
|
||||
...(ctx.cellMenuExtra?.(r, c) ?? []),
|
||||
];
|
||||
}
|
||||
@@ -117,11 +184,24 @@ export function attachMenu(ctx: SpreadsheetContext): PartHandle {
|
||||
return [
|
||||
[st("MenuCut"), () => cutCopy("cut")],
|
||||
[st("MenuCopy"), () => cutCopy("copy")],
|
||||
[st("MenuPasteHint"), () => showToast(st("MenuPasteHint"), "info")],
|
||||
[
|
||||
st("MenuInsertRow"),
|
||||
st("MenuPaste"),
|
||||
() => void loadMenuItems().then((m) => m.pasteFromMenu(ctx).then(fallbackPasteHint)),
|
||||
],
|
||||
[
|
||||
st("MenuInsertRowAbove"),
|
||||
() => ctx.dispatch({ 종류: "행넣기", 시트: ctx.selection.시트, at, 수 }),
|
||||
],
|
||||
[
|
||||
st("MenuInsertRowBelow"),
|
||||
() =>
|
||||
ctx.dispatch({
|
||||
종류: "행넣기",
|
||||
시트: ctx.selection.시트,
|
||||
at: rows[rows.length - 1] + 1,
|
||||
수,
|
||||
}),
|
||||
],
|
||||
[
|
||||
st("MenuDeleteRow"),
|
||||
() => ctx.dispatch({ 종류: "행지우기", 시트: ctx.selection.시트, at, 수 }),
|
||||
@@ -148,6 +228,25 @@ export function attachMenu(ctx: SpreadsheetContext): PartHandle {
|
||||
숨김: false,
|
||||
}),
|
||||
],
|
||||
{ divider: true },
|
||||
[st("MenuRowSize"), () => void loadMenuItems().then((m) => m.openRowSizeDialog(ctx, rows))],
|
||||
{ divider: true },
|
||||
[st("MenuFilterToggle"), () => void loadMenuItems().then((m) => m.filterToggle(ctx))],
|
||||
[st("MenuFreeze"), () => void loadMenuItems().then((m) => m.freezeFirstRow(ctx))],
|
||||
[st("MenuUnfreeze"), () => void loadMenuItems().then((m) => m.unfreezePanes(ctx))],
|
||||
{
|
||||
label: st("MenuGroup"),
|
||||
children: [
|
||||
[
|
||||
st("MenuGroupOn"),
|
||||
() => void loadMenuItems().then((m) => m.groupAxis(ctx, "행", rows, true)),
|
||||
],
|
||||
[
|
||||
st("MenuGroupOff"),
|
||||
() => void loadMenuItems().then((m) => m.groupAxis(ctx, "행", rows, false)),
|
||||
],
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -157,11 +256,24 @@ export function attachMenu(ctx: SpreadsheetContext): PartHandle {
|
||||
return [
|
||||
[st("MenuCut"), () => cutCopy("cut")],
|
||||
[st("MenuCopy"), () => cutCopy("copy")],
|
||||
[st("MenuPasteHint"), () => showToast(st("MenuPasteHint"), "info")],
|
||||
[
|
||||
st("MenuInsertCol"),
|
||||
st("MenuPaste"),
|
||||
() => void loadMenuItems().then((m) => m.pasteFromMenu(ctx).then(fallbackPasteHint)),
|
||||
],
|
||||
[
|
||||
st("MenuInsertColLeft"),
|
||||
() => ctx.dispatch({ 종류: "열넣기", 시트: ctx.selection.시트, at, 수 }),
|
||||
],
|
||||
[
|
||||
st("MenuInsertColRight"),
|
||||
() =>
|
||||
ctx.dispatch({
|
||||
종류: "열넣기",
|
||||
시트: ctx.selection.시트,
|
||||
at: cols[cols.length - 1] + 1,
|
||||
수,
|
||||
}),
|
||||
],
|
||||
[
|
||||
st("MenuDeleteCol"),
|
||||
() => ctx.dispatch({ 종류: "열지우기", 시트: ctx.selection.시트, at, 수 }),
|
||||
@@ -188,6 +300,32 @@ export function attachMenu(ctx: SpreadsheetContext): PartHandle {
|
||||
숨김: false,
|
||||
}),
|
||||
],
|
||||
{ divider: true },
|
||||
[st("MenuColSize"), () => void loadMenuItems().then((m) => m.openColSizeDialog(ctx, cols))],
|
||||
[st("MenuAutofit"), () => void loadMenuItems().then((m) => m.autofitCols(ctx, cols))],
|
||||
{ divider: true },
|
||||
[st("MenuSortAsc"), () => void loadMenuItems().then((m) => m.sortAsc(ctx))],
|
||||
[st("MenuSortDesc"), () => void loadMenuItems().then((m) => m.sortDesc(ctx))],
|
||||
[st("MenuFilterToggle"), () => void loadMenuItems().then((m) => m.filterToggle(ctx))],
|
||||
[st("MenuFreeze"), () => void loadMenuItems().then((m) => m.freezeFirstColumn(ctx))],
|
||||
[st("MenuUnfreeze"), () => void loadMenuItems().then((m) => m.unfreezePanes(ctx))],
|
||||
{
|
||||
label: st("MenuGroup"),
|
||||
children: [
|
||||
[
|
||||
st("MenuGroupOn"),
|
||||
() => void loadMenuItems().then((m) => m.groupAxis(ctx, "열", cols, true)),
|
||||
],
|
||||
[
|
||||
st("MenuGroupOff"),
|
||||
() => void loadMenuItems().then((m) => m.groupAxis(ctx, "열", cols, false)),
|
||||
],
|
||||
],
|
||||
},
|
||||
{ divider: true },
|
||||
[st("MenuCondFmt"), () => void loadMenuItems().then((m) => m.openCondFmt(ctx))],
|
||||
[st("MenuValidation"), () => void loadMenuItems().then((m) => m.openValidation(ctx))],
|
||||
[st("MenuNames"), () => void loadMenuItems().then((m) => m.openNames(ctx))],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user