gyojeong 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -5
- package/apps/review-web/dist/assets/feedback-contract-CMrinGBH.js +1 -0
- package/apps/review-web/dist/assets/feedback-wrapper-Bhgs7aQS.js +3 -0
- package/apps/review-web/dist/assets/index-CY49dpth.js +17 -0
- package/apps/review-web/dist/assets/index-DOaH7bry.css +1 -0
- package/apps/review-web/dist/feedback-wrapper.html +2 -2
- package/apps/review-web/dist/index.html +3 -3
- package/package.json +1 -1
- package/scripts/review-cli.mjs +229 -216
- package/scripts/review-server.mjs +35 -203
- package/apps/review-web/dist/_headers +0 -7
- package/apps/review-web/dist/assets/feedback-contract-BsrsBZh-.js +0 -1
- package/apps/review-web/dist/assets/feedback-wrapper-DIWwO_Fr.js +0 -3
- package/apps/review-web/dist/assets/index-BGolyauJ.css +0 -1
- package/apps/review-web/dist/assets/index-Br6KkHpV.js +0 -17
|
@@ -60,19 +60,21 @@ import { hostname, homedir as homedir2 } from "node:os";
|
|
|
60
60
|
import path2 from "node:path";
|
|
61
61
|
|
|
62
62
|
// ../feedback-core/dist/index.js
|
|
63
|
-
var
|
|
63
|
+
var createSelectionValidator = (text) => {
|
|
64
64
|
const normalizedText = text.normalize("NFC");
|
|
65
|
-
const normalizedSelectedText = selection.selected_text.normalize("NFC");
|
|
66
65
|
const textCodePoints = Array.from(normalizedText);
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
66
|
+
return (selection) => {
|
|
67
|
+
if (!Number.isFinite(selection.start) || !Number.isFinite(selection.end) || !Number.isInteger(selection.start) || !Number.isInteger(selection.end) || selection.start < 0 || selection.start >= selection.end || selection.end > textCodePoints.length) {
|
|
68
|
+
return { ok: false, reason: "stale_selection" };
|
|
69
|
+
}
|
|
70
|
+
const selectedSlice = textCodePoints.slice(selection.start, selection.end).join("");
|
|
71
|
+
if (selectedSlice !== selection.selected_text.normalize("NFC")) {
|
|
72
|
+
return { ok: false, reason: "stale_selection" };
|
|
73
|
+
}
|
|
74
|
+
return { ok: true, normalized_text: normalizedText };
|
|
75
|
+
};
|
|
75
76
|
};
|
|
77
|
+
var validateSelection = (text, selection) => createSelectionValidator(text)(selection);
|
|
76
78
|
|
|
77
79
|
// ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/core.js
|
|
78
80
|
var _a;
|
|
@@ -1046,7 +1048,6 @@ var string = (params) => {
|
|
|
1046
1048
|
};
|
|
1047
1049
|
var integer = /^-?\d+$/;
|
|
1048
1050
|
var number = /^-?\d+(?:\.\d+)?$/;
|
|
1049
|
-
var boolean = /^(?:true|false)$/i;
|
|
1050
1051
|
var lowercase = /^[^A-Z]*$/;
|
|
1051
1052
|
var uppercase = /^[^a-z]*$/;
|
|
1052
1053
|
|
|
@@ -1929,27 +1930,6 @@ var $ZodNumberFormat = /* @__PURE__ */ $constructor("$ZodNumberFormat", (inst, d
|
|
|
1929
1930
|
$ZodCheckNumberFormat.init(inst, def);
|
|
1930
1931
|
$ZodNumber.init(inst, def);
|
|
1931
1932
|
});
|
|
1932
|
-
var $ZodBoolean = /* @__PURE__ */ $constructor("$ZodBoolean", (inst, def) => {
|
|
1933
|
-
$ZodType.init(inst, def);
|
|
1934
|
-
inst._zod.pattern = boolean;
|
|
1935
|
-
inst._zod.parse = (payload, _ctx) => {
|
|
1936
|
-
if (def.coerce)
|
|
1937
|
-
try {
|
|
1938
|
-
payload.value = Boolean(payload.value);
|
|
1939
|
-
} catch (_) {
|
|
1940
|
-
}
|
|
1941
|
-
const input = payload.value;
|
|
1942
|
-
if (typeof input === "boolean")
|
|
1943
|
-
return payload;
|
|
1944
|
-
payload.issues.push({
|
|
1945
|
-
expected: "boolean",
|
|
1946
|
-
code: "invalid_type",
|
|
1947
|
-
input,
|
|
1948
|
-
inst
|
|
1949
|
-
});
|
|
1950
|
-
return payload;
|
|
1951
|
-
};
|
|
1952
|
-
});
|
|
1953
1933
|
var $ZodUnknown = /* @__PURE__ */ $constructor("$ZodUnknown", (inst, def) => {
|
|
1954
1934
|
$ZodType.init(inst, def);
|
|
1955
1935
|
inst._zod.parse = (payload) => payload;
|
|
@@ -2565,28 +2545,6 @@ var $ZodEnum = /* @__PURE__ */ $constructor("$ZodEnum", (inst, def) => {
|
|
|
2565
2545
|
return payload;
|
|
2566
2546
|
};
|
|
2567
2547
|
});
|
|
2568
|
-
var $ZodLiteral = /* @__PURE__ */ $constructor("$ZodLiteral", (inst, def) => {
|
|
2569
|
-
$ZodType.init(inst, def);
|
|
2570
|
-
if (def.values.length === 0) {
|
|
2571
|
-
throw new Error("Cannot create literal schema with no valid values");
|
|
2572
|
-
}
|
|
2573
|
-
const values = new Set(def.values);
|
|
2574
|
-
inst._zod.values = values;
|
|
2575
|
-
inst._zod.pattern = new RegExp(`^(${def.values.map((o) => typeof o === "string" ? escapeRegex(o) : o ? escapeRegex(o.toString()) : String(o)).join("|")})$`);
|
|
2576
|
-
inst._zod.parse = (payload, _ctx) => {
|
|
2577
|
-
const input = payload.value;
|
|
2578
|
-
if (values.has(input)) {
|
|
2579
|
-
return payload;
|
|
2580
|
-
}
|
|
2581
|
-
payload.issues.push({
|
|
2582
|
-
code: "invalid_value",
|
|
2583
|
-
values: def.values,
|
|
2584
|
-
input,
|
|
2585
|
-
inst
|
|
2586
|
-
});
|
|
2587
|
-
return payload;
|
|
2588
|
-
};
|
|
2589
|
-
});
|
|
2590
2548
|
var $ZodTransform = /* @__PURE__ */ $constructor("$ZodTransform", (inst, def) => {
|
|
2591
2549
|
$ZodType.init(inst, def);
|
|
2592
2550
|
inst._zod.optin = "optional";
|
|
@@ -3307,13 +3265,6 @@ function _int(Class2, params) {
|
|
|
3307
3265
|
});
|
|
3308
3266
|
}
|
|
3309
3267
|
// @__NO_SIDE_EFFECTS__
|
|
3310
|
-
function _boolean(Class2, params) {
|
|
3311
|
-
return new Class2({
|
|
3312
|
-
type: "boolean",
|
|
3313
|
-
...normalizeParams(params)
|
|
3314
|
-
});
|
|
3315
|
-
}
|
|
3316
|
-
// @__NO_SIDE_EFFECTS__
|
|
3317
3268
|
function _unknown(Class2) {
|
|
3318
3269
|
return new Class2({
|
|
3319
3270
|
type: "unknown"
|
|
@@ -3959,9 +3910,6 @@ var numberProcessor = (schema, ctx, _json, _params) => {
|
|
|
3959
3910
|
if (typeof multipleOf === "number")
|
|
3960
3911
|
json.multipleOf = multipleOf;
|
|
3961
3912
|
};
|
|
3962
|
-
var booleanProcessor = (_schema, _ctx, json, _params) => {
|
|
3963
|
-
json.type = "boolean";
|
|
3964
|
-
};
|
|
3965
3913
|
var neverProcessor = (_schema, _ctx, json, _params) => {
|
|
3966
3914
|
json.not = {};
|
|
3967
3915
|
};
|
|
@@ -3976,46 +3924,6 @@ var enumProcessor = (schema, _ctx, json, _params) => {
|
|
|
3976
3924
|
json.type = "string";
|
|
3977
3925
|
json.enum = values;
|
|
3978
3926
|
};
|
|
3979
|
-
var literalProcessor = (schema, ctx, json, _params) => {
|
|
3980
|
-
const def = schema._zod.def;
|
|
3981
|
-
const vals = [];
|
|
3982
|
-
for (const val of def.values) {
|
|
3983
|
-
if (val === void 0) {
|
|
3984
|
-
if (ctx.unrepresentable === "throw") {
|
|
3985
|
-
throw new Error("Literal `undefined` cannot be represented in JSON Schema");
|
|
3986
|
-
} else {
|
|
3987
|
-
}
|
|
3988
|
-
} else if (typeof val === "bigint") {
|
|
3989
|
-
if (ctx.unrepresentable === "throw") {
|
|
3990
|
-
throw new Error("BigInt literals cannot be represented in JSON Schema");
|
|
3991
|
-
} else {
|
|
3992
|
-
vals.push(Number(val));
|
|
3993
|
-
}
|
|
3994
|
-
} else {
|
|
3995
|
-
vals.push(val);
|
|
3996
|
-
}
|
|
3997
|
-
}
|
|
3998
|
-
if (vals.length === 0) {
|
|
3999
|
-
} else if (vals.length === 1) {
|
|
4000
|
-
const val = vals[0];
|
|
4001
|
-
json.type = val === null ? "null" : typeof val;
|
|
4002
|
-
if (ctx.target === "draft-04" || ctx.target === "openapi-3.0") {
|
|
4003
|
-
json.enum = [val];
|
|
4004
|
-
} else {
|
|
4005
|
-
json.const = val;
|
|
4006
|
-
}
|
|
4007
|
-
} else {
|
|
4008
|
-
if (vals.every((v) => typeof v === "number"))
|
|
4009
|
-
json.type = "number";
|
|
4010
|
-
if (vals.every((v) => typeof v === "string"))
|
|
4011
|
-
json.type = "string";
|
|
4012
|
-
if (vals.every((v) => typeof v === "boolean"))
|
|
4013
|
-
json.type = "boolean";
|
|
4014
|
-
if (vals.every((v) => v === null))
|
|
4015
|
-
json.type = "null";
|
|
4016
|
-
json.enum = vals;
|
|
4017
|
-
}
|
|
4018
|
-
};
|
|
4019
3927
|
var customProcessor = (_schema, ctx, _json, _params) => {
|
|
4020
3928
|
if (ctx.unrepresentable === "throw") {
|
|
4021
3929
|
throw new Error("Custom types cannot be represented in JSON Schema");
|
|
@@ -4720,14 +4628,6 @@ var ZodNumberFormat = /* @__PURE__ */ $constructor("ZodNumberFormat", (inst, def
|
|
|
4720
4628
|
function int(params) {
|
|
4721
4629
|
return _int(ZodNumberFormat, params);
|
|
4722
4630
|
}
|
|
4723
|
-
var ZodBoolean = /* @__PURE__ */ $constructor("ZodBoolean", (inst, def) => {
|
|
4724
|
-
$ZodBoolean.init(inst, def);
|
|
4725
|
-
ZodType.init(inst, def);
|
|
4726
|
-
inst._zod.processJSONSchema = (ctx, json, params) => booleanProcessor(inst, ctx, json, params);
|
|
4727
|
-
});
|
|
4728
|
-
function boolean2(params) {
|
|
4729
|
-
return _boolean(ZodBoolean, params);
|
|
4730
|
-
}
|
|
4731
4631
|
var ZodUnknown = /* @__PURE__ */ $constructor("ZodUnknown", (inst, def) => {
|
|
4732
4632
|
$ZodUnknown.init(inst, def);
|
|
4733
4633
|
ZodType.init(inst, def);
|
|
@@ -4918,27 +4818,6 @@ function _enum(values, params) {
|
|
|
4918
4818
|
...util_exports.normalizeParams(params)
|
|
4919
4819
|
});
|
|
4920
4820
|
}
|
|
4921
|
-
var ZodLiteral = /* @__PURE__ */ $constructor("ZodLiteral", (inst, def) => {
|
|
4922
|
-
$ZodLiteral.init(inst, def);
|
|
4923
|
-
ZodType.init(inst, def);
|
|
4924
|
-
inst._zod.processJSONSchema = (ctx, json, params) => literalProcessor(inst, ctx, json, params);
|
|
4925
|
-
inst.values = new Set(def.values);
|
|
4926
|
-
Object.defineProperty(inst, "value", {
|
|
4927
|
-
get() {
|
|
4928
|
-
if (def.values.length > 1) {
|
|
4929
|
-
throw new Error("This schema contains multiple valid literal values. Use `.values` instead.");
|
|
4930
|
-
}
|
|
4931
|
-
return def.values[0];
|
|
4932
|
-
}
|
|
4933
|
-
});
|
|
4934
|
-
});
|
|
4935
|
-
function literal(value, params) {
|
|
4936
|
-
return new ZodLiteral({
|
|
4937
|
-
type: "literal",
|
|
4938
|
-
values: Array.isArray(value) ? value : [value],
|
|
4939
|
-
...util_exports.normalizeParams(params)
|
|
4940
|
-
});
|
|
4941
|
-
}
|
|
4942
4821
|
var ZodTransform = /* @__PURE__ */ $constructor("ZodTransform", (inst, def) => {
|
|
4943
4822
|
$ZodTransform.init(inst, def);
|
|
4944
4823
|
ZodType.init(inst, def);
|
|
@@ -5135,7 +5014,6 @@ var BoundedCorrectionSchema = trimmedNfcBoundedText(4e3, "correction").refine((v
|
|
|
5135
5014
|
});
|
|
5136
5015
|
var IdempotencyKeySchema = string2().trim().min(1).max(128).regex(/^[A-Za-z0-9._:-]+$/);
|
|
5137
5016
|
var IdentifierSchema = uuid2();
|
|
5138
|
-
var DomainSchema = string2().trim().min(1).max(64).regex(/^[a-z][a-z0-9_-]*$/);
|
|
5139
5017
|
var IsoDateTimeSchema = iso_exports.datetime();
|
|
5140
5018
|
var ReviewAttachmentFilenameSchema = nfcBoundedText(255, "filename").refine((value) => value.length > 0, { message: "filename must not be empty" }).refine((value) => value !== "." && value !== "..", { message: "filename must be a basename" }).refine((value) => !/[\/\\\0]/u.test(value), { message: "filename must be a basename" });
|
|
5141
5019
|
var ReviewAttachmentMediaTypeSchema = _enum(["image/png", "image/jpeg", "image/webp"]);
|
|
@@ -5235,21 +5113,6 @@ var LanguageTagSchema = string2().transform((value, context) => {
|
|
|
5235
5113
|
return NEVER;
|
|
5236
5114
|
}
|
|
5237
5115
|
});
|
|
5238
|
-
var OperationSchema = _enum(["translate", "rewrite", "post_edit", "review"]);
|
|
5239
|
-
var SupportLevelSchema = _enum(["verified", "experimental", "unsupported"]);
|
|
5240
|
-
var RouteSchema = object({
|
|
5241
|
-
operation: OperationSchema,
|
|
5242
|
-
source_language: LanguageTagSchema,
|
|
5243
|
-
target_language: LanguageTagSchema,
|
|
5244
|
-
domain: DomainSchema,
|
|
5245
|
-
support_level: SupportLevelSchema
|
|
5246
|
-
}).strict();
|
|
5247
|
-
var StructuredErrorSchema = object({
|
|
5248
|
-
code: string2().min(1).max(80),
|
|
5249
|
-
retryable: boolean2(),
|
|
5250
|
-
message: string2().min(1).max(1e3),
|
|
5251
|
-
next_action: string2().min(1).max(1e3)
|
|
5252
|
-
}).strict();
|
|
5253
5116
|
var MAX_SURFACE_SELECTOR_CODE_POINTS = 500;
|
|
5254
5117
|
var MAX_SURFACE_TILES = 32;
|
|
5255
5118
|
var MAX_SURFACE_TILE_BYTES = 2 * 1024 * 1024;
|
|
@@ -5304,20 +5167,6 @@ var SurfaceSnapshotInputSchema = object({
|
|
|
5304
5167
|
tiles: array(SurfaceTileInputSchema).min(1).max(MAX_SURFACE_TILES),
|
|
5305
5168
|
units: array(SurfaceUnitSchema).min(1).max(MAX_SURFACE_UNITS)
|
|
5306
5169
|
}).strict();
|
|
5307
|
-
var SurfaceTileManifestSchema = object({
|
|
5308
|
-
index: int().nonnegative(),
|
|
5309
|
-
top: int().nonnegative(),
|
|
5310
|
-
height: int().positive(),
|
|
5311
|
-
media_type: _enum(["image/png", "image/jpeg", "image/webp"]),
|
|
5312
|
-
size_bytes: int().positive()
|
|
5313
|
-
}).strict();
|
|
5314
|
-
var SurfaceSnapshotManifestSchema = object({
|
|
5315
|
-
page_width: int().positive(),
|
|
5316
|
-
page_height: int().positive(),
|
|
5317
|
-
captured_at: IsoDateTimeSchema,
|
|
5318
|
-
tiles: array(SurfaceTileManifestSchema).min(1).max(MAX_SURFACE_TILES),
|
|
5319
|
-
units: array(SurfaceUnitSchema).min(1).max(MAX_SURFACE_UNITS)
|
|
5320
|
-
}).strict();
|
|
5321
5170
|
var SourceSurfaceSchema = object({
|
|
5322
5171
|
target_url: url().refine((value) => ["http:", "https:"].includes(new URL(value).protocol), {
|
|
5323
5172
|
message: "target_url must be an http(s) URL"
|
|
@@ -5334,9 +5183,7 @@ var TranslationPublishInputSchema = object({
|
|
|
5334
5183
|
run_id: IdentifierSchema,
|
|
5335
5184
|
source_text: NfcBoundedTextSchema,
|
|
5336
5185
|
jobs: array(TranslationPublishJobSchema).length(1),
|
|
5337
|
-
attachments: array(unknown()).max(MAX_REVIEW_ATTACHMENTS).pipe(array(ReviewAttachmentInputSchema)).optional()
|
|
5338
|
-
upload_consent: literal(true),
|
|
5339
|
-
idempotency_key: IdempotencyKeySchema
|
|
5186
|
+
attachments: array(unknown()).max(MAX_REVIEW_ATTACHMENTS).pipe(array(ReviewAttachmentInputSchema)).optional()
|
|
5340
5187
|
}).strict().superRefine((value, context) => {
|
|
5341
5188
|
const filenames = /* @__PURE__ */ new Set();
|
|
5342
5189
|
let totalSize = 0;
|
|
@@ -5551,20 +5398,6 @@ var readAttachment = (home, id, attachmentId) => {
|
|
|
5551
5398
|
}
|
|
5552
5399
|
return { media_type: manifest.media_type, bytes: Buffer.from(readFileSync2(file, "utf8"), "base64") };
|
|
5553
5400
|
};
|
|
5554
|
-
var readRun = (home, id) => {
|
|
5555
|
-
const dir = runDir(home, id);
|
|
5556
|
-
assertRunExists(dir);
|
|
5557
|
-
const content = readJson(path2.join(dir, "content.json"), null);
|
|
5558
|
-
const feedback = readJson(path2.join(dir, "feedback.json"), []);
|
|
5559
|
-
const state = readJson(path2.join(dir, "state.json"), { processed_feedback_ids: [] });
|
|
5560
|
-
const processed = new Set(state.processed_feedback_ids ?? []);
|
|
5561
|
-
return {
|
|
5562
|
-
content,
|
|
5563
|
-
feedback,
|
|
5564
|
-
processed_feedback_ids: [...processed],
|
|
5565
|
-
pending_feedback_ids: feedback.map((item) => item.id).filter((fid) => !processed.has(fid))
|
|
5566
|
-
};
|
|
5567
|
-
};
|
|
5568
5401
|
var appendFeedback = (home, id, rawItems) => {
|
|
5569
5402
|
if (!Array.isArray(rawItems) || rawItems.length === 0 || rawItems.length > 100) {
|
|
5570
5403
|
throw problem(400, "invalid_request", "\uD53C\uB4DC\uBC31\uC740 \uD55C \uBC88\uC5D0 1~100\uAC74\uAE4C\uC9C0 \uC800\uC7A5\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.", "\uAC80\uC218\uD568 \uB0B4\uC6A9\uC744 \uD655\uC778\uD574 \uC8FC\uC138\uC694.");
|
|
@@ -5595,6 +5428,8 @@ var appendFeedback = (home, id, rawItems) => {
|
|
|
5595
5428
|
category: item.category,
|
|
5596
5429
|
scope: item.scope,
|
|
5597
5430
|
selection: item.selection,
|
|
5431
|
+
// Offsets refer to this draft; `revise` keeps it so a later round can still verify the item.
|
|
5432
|
+
draft_version: content.draft_version ?? 0,
|
|
5598
5433
|
...item.term ? { term: item.term } : {},
|
|
5599
5434
|
...item.correction ? { correction: item.correction } : {},
|
|
5600
5435
|
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -5625,15 +5460,23 @@ var listRuns = (home, now = Date.now()) => {
|
|
|
5625
5460
|
if (!existsSync2(runsDir(home))) return [];
|
|
5626
5461
|
return readdirSync(runsDir(home)).filter((name) => REVIEW_ID.test(name)).flatMap((id) => {
|
|
5627
5462
|
try {
|
|
5628
|
-
const
|
|
5629
|
-
|
|
5463
|
+
const dir = runDir(home, id);
|
|
5464
|
+
let meta2 = null;
|
|
5465
|
+
try {
|
|
5466
|
+
meta2 = readJson(path2.join(dir, "meta.json"), null);
|
|
5467
|
+
} catch {
|
|
5468
|
+
meta2 = null;
|
|
5469
|
+
}
|
|
5470
|
+
const createdAt = (meta2?.created_at ? meta2 : readJson(path2.join(dir, "content.json"), null)).created_at;
|
|
5471
|
+
const feedback = readJson(path2.join(dir, "feedback.json"), []);
|
|
5472
|
+
const processed = new Set(readJson(path2.join(dir, "state.json"), { processed_feedback_ids: [] }).processed_feedback_ids ?? []);
|
|
5630
5473
|
return [
|
|
5631
5474
|
{
|
|
5632
5475
|
review_id: id,
|
|
5633
5476
|
created_at: createdAt,
|
|
5634
5477
|
age_days: Math.floor((now - Date.parse(createdAt)) / 864e5),
|
|
5635
|
-
feedback:
|
|
5636
|
-
pending:
|
|
5478
|
+
feedback: feedback.length,
|
|
5479
|
+
pending: feedback.filter((item) => !processed.has(item.id)).length
|
|
5637
5480
|
}
|
|
5638
5481
|
];
|
|
5639
5482
|
} catch {
|
|
@@ -5727,7 +5570,7 @@ var allowedOrigin = (request, allowedOrigins, allowedHosts) => {
|
|
|
5727
5570
|
return false;
|
|
5728
5571
|
}
|
|
5729
5572
|
};
|
|
5730
|
-
var handleApi = async (request, response, pathname, context) => {
|
|
5573
|
+
var handleApi = async (request, response, pathname, context, query = new URLSearchParams()) => {
|
|
5731
5574
|
if (pathname === "/api/health" && request.method === "GET") {
|
|
5732
5575
|
sendJson(response, 200, { ready: true, storage: "local", home_id: homeId(context.home) });
|
|
5733
5576
|
return true;
|
|
@@ -5735,26 +5578,15 @@ var handleApi = async (request, response, pathname, context) => {
|
|
|
5735
5578
|
const reviewMatch = pathname.match(new RegExp(`^/api/reviews/${REVIEW_PATH}$`, "u"));
|
|
5736
5579
|
if (reviewMatch && request.method === "GET") {
|
|
5737
5580
|
try {
|
|
5738
|
-
const { check: _check2, ...content } = readContent(context.home, reviewMatch[1]);
|
|
5739
|
-
|
|
5740
|
-
} catch (error2) {
|
|
5741
|
-
const known = knownProblem(error2, "\uAC80\uC218 \uB0B4\uC6A9\uC744 \uBD88\uB7EC\uC624\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.");
|
|
5742
|
-
sendJson(response, known.status, known.body);
|
|
5743
|
-
}
|
|
5744
|
-
return true;
|
|
5745
|
-
}
|
|
5746
|
-
const feedbackMatch = pathname.match(new RegExp(`^/api/reviews/${REVIEW_PATH}/feedback$`, "u"));
|
|
5747
|
-
if (feedbackMatch && request.method === "GET") {
|
|
5748
|
-
try {
|
|
5749
|
-
const run = readRun(context.home, feedbackMatch[1]);
|
|
5581
|
+
const { check: _check2, source_surface: surface, ...content } = readContent(context.home, reviewMatch[1]);
|
|
5582
|
+
const withSurface = query.get("surface") === "1" && surface ? { source_surface: surface } : {};
|
|
5750
5583
|
sendJson(response, 200, {
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
pending_feedback_ids: run.pending_feedback_ids
|
|
5584
|
+
...content,
|
|
5585
|
+
...withSurface,
|
|
5586
|
+
attachments: (content.attachments ?? []).map(({ file: _file2, ...rest }) => rest)
|
|
5755
5587
|
});
|
|
5756
5588
|
} catch (error2) {
|
|
5757
|
-
const known = knownProblem(error2, "\
|
|
5589
|
+
const known = knownProblem(error2, "\uAC80\uC218 \uB0B4\uC6A9\uC744 \uBD88\uB7EC\uC624\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.");
|
|
5758
5590
|
sendJson(response, known.status, known.body);
|
|
5759
5591
|
}
|
|
5760
5592
|
return true;
|
|
@@ -5925,7 +5757,7 @@ var startReviewServer = async ({ host, port, root, home = storeHome(), idle = lo
|
|
|
5925
5757
|
return;
|
|
5926
5758
|
}
|
|
5927
5759
|
const url2 = new URL(request.url ?? "/", "http://127.0.0.1");
|
|
5928
|
-
if (await handleApi(request, response, url2.pathname, context)) return;
|
|
5760
|
+
if (await handleApi(request, response, url2.pathname, context, url2.searchParams)) return;
|
|
5929
5761
|
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
5930
5762
|
response.writeHead(405).end();
|
|
5931
5763
|
return;
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Referrer-Policy: no-referrer
|
|
3
|
-
Cache-Control: no-store
|
|
4
|
-
X-Content-Type-Options: nosniff
|
|
5
|
-
X-Frame-Options: DENY
|
|
6
|
-
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=()
|
|
7
|
-
Content-Security-Policy: default-src 'self'; base-uri 'none'; connect-src 'self'; font-src 'self'; form-action 'none'; frame-ancestors 'none'; img-src 'self' data:; object-src 'none'; script-src 'self'; style-src 'self'
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var e=()=>{if(typeof crypto<`u`&&typeof crypto.randomUUID==`function`)return crypto.randomUUID();let e=new Uint8Array(16);crypto.getRandomValues(e),e[6]=(e[6]??0)&15|64,e[8]=(e[8]??0)&63|128;let t=Array.from(e,e=>e.toString(16).padStart(2,`0`)).join(``);return`${t.slice(0,8)}-${t.slice(8,12)}-${t.slice(12,16)}-${t.slice(16,20)}-${t.slice(20)}`},t=(e,t)=>{let n=e.body;return[n?.message??(e instanceof Error?e.message:t),n?.next_action].filter(Boolean).join(` `)},n=[`terminology`,`meaning_shift`],r={level:`experimental`,correction_required:n,correction_optional:[`unspecified`,`awkward_expression`,`omission`,`addition`,`register`],correction_forbidden:[`deletion`],feedback_categories:[`unspecified`,`deletion`,...n,`awkward_expression`,`omission`,`addition`,`register`],feedback_scopes:[`run_only`,`personal`]},i=(e,t,n,r=!1)=>({code:e,message:t,next_action:n,retryable:r}),a=(e,t,n,r,a=!1)=>Object.assign(Error(t),{status:r,body:i(e,t,n,a)}),o=async e=>{let t;try{t=await e.json()}catch{t=void 0}throw Object.assign(Error(t?.message??`로컬 검수 서버 요청에 실패했습니다.`),{status:e.status,body:t})},s=async(e,t)=>{try{let n=await fetch(e,{cache:`no-store`,...t});return n.ok||await o(n),n}catch(e){throw e.body?e:a(`local_server_unavailable`,`로컬 검수 서버에 연결할 수 없습니다.`,`에이전트에게 검수 링크를 다시 요청해 주세요. 서버가 쉬고 있으면 링크를 만들 때 다시 켜집니다.`,503,!0)}},c=async e=>{let t=await(await s(`/api/reviews/${encodeURIComponent(e)}`)).json();return{run:t.run,job:t.job,source_text:t.source_text,draft_text:t.draft_text,attachments:(t.attachments??[]).map(({file:e,...t})=>t),surface:t.source_surface??null,support:r}},l=async(e,t)=>(await s(`/api/reviews/${encodeURIComponent(e)}/attachments/${encodeURIComponent(t)}`)).blob(),u=async(e,t)=>{if(t.length===0)return[];let n=await s(`/api/review-feedback`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({review_id:e,items:t})}),r;try{r=await n.json()}catch{r=void 0}if(!n.ok){let e=r;throw Object.assign(Error(e?.message??`피드백 저장에 실패했습니다.`),{status:n.status,body:e})}let i=r?.results;if(!Array.isArray(i)||i.length!==t.length)throw a(`invalid_server_response`,`로컬 검수 서버 응답이 올바르지 않습니다.`,`서버를 다시 실행해 주세요.`,502);return i},d=async(e,t=!1)=>{await s(`/api/review-finalize`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({review_id:e,force:t})})},f=120,p={unspecified:`?`,deletion:`삭제하기`,awkward_expression:`어색한 표현`,terminology:`용어`,omission:`누락`,addition:`추가`,meaning_shift:`의미 변화`,register:`문체`},m=new Set([`terminology`,`meaning_shift`]),h=new Set([`terminology`]),g=`용어 기준 후보로 남깁니다.`,_=e=>{if(e.onceOnly)return{scope:`run_only`};if(!h.has(e.category))return{scope:`run_only`,blocked:`category`};let t=(e.term||e.selectedText).normalize(`NFC`);return Array.from(t).length>f?{scope:`run_only`,blocked:`no_term`}:{scope:`personal`}},v=e=>e===`category`?`이 유형은 이번 문서에만 적용됩니다.`:`문장이 길어 이번 문서에만 적용됩니다. 단어를 지정하면 용어 기준 후보가 됩니다.`;export{v as a,d as c,u as d,_ as i,e as l,g as n,c as o,p as r,l as s,m as t,t as u};
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import{a as e,d as t,i as n,n as r,o as i,r as a,t as o,u as s}from"./feedback-contract-BsrsBZh-.js";var c=new URLSearchParams(location.search),l=new URLSearchParams(location.hash.startsWith(`#`)?location.hash.slice(1):location.hash).get(`review`),u=!!l,d=null,ee=c.get(`target`),f=document.getElementById(`source`),p=null,m=null,h=``,g=document.getElementById(`reviewMode`),_=document.getElementById(`basket`),v=document.getElementById(`items`),te=document.getElementById(`count`),y=document.getElementById(`sheet`),ne=document.getElementById(`selection`),b=document.getElementById(`correction`),re=document.getElementById(`status`),ie=document.getElementById(`barMode`),x=document.getElementById(`add`),S=document.getElementById(`copyFeedback`),C=document.getElementById(`remember`),w=`gyojeong:${l||`local`}:pending`,T=c.get(`selectors`)||`h1,h2,h3,h4,h5,h6,p,li,td,th,blockquote,figcaption,[data-review-text]`,E=document.getElementById(`surface`),D=document.getElementById(`categories`),O=document.getElementById(`scope`),ae=document.getElementById(`termLabel`),k=document.getElementById(`term`),A=document.getElementById(`correctionLabel`),oe=document.getElementById(`rememberLabel`),se=document.getElementById(`scopeNote`),j=!0,M=null,N=[],P=[],F=!u,ce=!1,I={feedback_categories:Object.keys(a),correction_forbidden:[]},L=I.feedback_categories[0],R=`sentence`;u&&(S.textContent=`피드백 전송`);function z(){if(typeof crypto.randomUUID==`function`)return crypto.randomUUID();let e=new Uint8Array(16);return crypto.getRandomValues(e),`feedback-${Date.now().toString(36)}-${Array.from(e,e=>e.toString(16).padStart(2,`0`)).join(``)}`}function B(){try{return JSON.parse(localStorage.getItem(w)||`[]`)}catch{return[]}}function V(){try{return localStorage.setItem(w,JSON.stringify(P)),!0}catch{return!1}}function H(e){re.textContent=e,clearTimeout(H.timer),H.timer=setTimeout(()=>{re.textContent=`문장을 눌러 피드백을 남기세요.`},2200)}function U(){return I.correction_forbidden.includes(L)}function W(){return M?R===`paragraph`?{start:M.paragraphStart,end:M.paragraphEnd,text:M.paragraphText}:{start:M.start,end:M.end,text:M.text}:null}function G(){for(let e of O.children){let t=e.dataset.mode===R;e.classList.toggle(`active`,t),e.setAttribute(`aria-checked`,String(t))}let e=R===`word`;ae.hidden=!e,k.hidden=!e,J();let t=W();t&&(ne.textContent=`${M.page}${M.slide?` · ${M.slide}`:``}\n${t.text.trim()}`)}function K(){for(let e of D.children)e.classList.toggle(`active`,e.dataset.category===L),e.setAttribute(`aria-checked`,String(e.dataset.category===L));let e=U();A.hidden=e,b.hidden=e,e&&(b.value=``),A.textContent=o.has(L)?`수정문 · 필수`:`수정문 · 선택 사항`,J()}function q(){return n({category:L,onceOnly:C.checked,term:R===`word`?k.value.normalize(`NFC`).trim():void 0,selectedText:W()?.text??``})}function J(){let t=q();oe.hidden=t.blocked===`category`,se.textContent=t.blocked?e(t.blocked):t.scope===`personal`?r:`이번 문서에만 적용됩니다.`}function le(){D.setAttribute(`role`,`radiogroup`),D.setAttribute(`aria-label`,`문제 유형`),D.replaceChildren(),I.feedback_categories.includes(L)||(L=I.feedback_categories[0]);for(let e of I.feedback_categories){let t=document.createElement(`button`);t.type=`button`,t.dataset.category=e,t.setAttribute(`role`,`radio`),t.textContent=a[e]||e,t.addEventListener(`click`,()=>{L=e,K()}),D.append(t)}K()}function Y(e){ce||(ce=!0,be(),H(`원본에서 문장을 연결할 수 없어 검수용 보기로 전환합니다. (${e})`),u&&setTimeout(()=>{location.href=`/#review=${encodeURIComponent(l)}`},700))}async function X(){let e=await i(l);return d=e.job.id,e}async function ue(){if(!u)return!0;let e=await X();if(e.draft_text!==window.__feedbackDraftText)throw Error(`text-mismatch`);return fe(e),!0}async function de(e){let t=ee,n=null;if(!t&&u){let r=e??await X();t=r.surface?.target_url??null,n=r.surface?.selectors??null}if(!t)throw Error(`missing-target`);let r=new URL(t,location.href);if(r.origin!==location.origin)throw Error(`cross-origin-target`);return n&&!c.get(`selectors`)&&(T=n),r.href}function fe(e){Array.isArray(e.support?.feedback_categories)&&e.support.feedback_categories.length>0&&(I={feedback_categories:e.support.feedback_categories,correction_forbidden:e.support.correction_forbidden??[]},le())}async function pe(){let e=null;if(u&&!ee)try{e=await X()}catch(e){Y(e.message||`review-unavailable`);return}if(e?.surface?.snapshot){fe(e),me(e);return}try{p=await de(e)}catch(e){Y(e.message||`surface-unavailable`);return}w=`gyojeong:${l||`local`}:${p}`,P=B(),Q(),f.src=p}function me(e){m=e.surface.snapshot,h=e.draft_text,p=e.surface.target_url,w=`gyojeong:${l||`local`}:snapshot:${p}`,P=B(),Q(),f.hidden=!0,E.hidden=!1,F=!0,ge(),H(`원본을 캡처한 화면에 검수 레이어를 연결했습니다.`)}function he(e,t){return e.content_base64?(t.src=`data:${e.media_type};base64,${e.content_base64}`,Promise.resolve()):Promise.reject(Error(`tile-missing`))}function ge(){E.replaceChildren();let e=E.clientWidth/m.page_width,t=document.createElement(`div`);t.className=`surface-page`,t.style.width=`${m.page_width*e}px`,t.style.height=`${m.page_height*e}px`,E.append(t);for(let n of m.tiles){let r=document.createElement(`img`);r.alt=``,r.className=`surface-tile`,r.style.top=`${n.top*e}px`,r.style.width=`${m.page_width*e}px`,r.style.height=`${n.height*e}px`,t.append(r),he(n,r).catch(()=>Y(`tile-unavailable`))}if(j)for(let[n,r]of m.units.entries()){let i=`unit-${n}`,a=[...h].slice(r.start,r.end).join(``),o=P.some(e=>e.start===r.start&&e.end===r.end);for(let[n,[s,c,l,u]]of r.rects.entries()){let d=document.createElement(`button`);d.type=`button`,d.className=`surface-hit`,d.dataset.group=i,d.dataset.pinned=String(o),n===0?d.setAttribute(`aria-label`,`${a} 피드백 남기기`):(d.tabIndex=-1,d.setAttribute(`aria-hidden`,`true`)),d.style.left=`${s*e}px`,d.style.top=`${c*e}px`,d.style.width=`${l*e}px`,d.style.height=`${u*e}px`,d.addEventListener(`click`,e=>{e.preventDefault(),Se({group:i,text:a,start:r.start,end:r.end,paragraphText:r.block?[...h].slice(r.block[0],r.block[1]).join(``):a,paragraphStart:r.block?r.block[0]:r.start,paragraphEnd:r.block?r.block[1]:r.end,slide:``,page:``})}),t.append(d)}}}function _e(e){let t=[],n=e.ownerDocument.createTreeWalker(e,NodeFilter.SHOW_TEXT,{acceptNode(e){return!e.data.trim()||e.parentElement.closest(`.notes,[aria-hidden="true"],script,style`)?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT}});for(;n.nextNode();)t.push(n.currentNode);return t}function ve(e,t){let n=0;for(let r of e){let e=n+r.data.length;if(t<=e)return{node:r,offset:Math.max(0,t-n)};n=e}let r=e.at(-1);return r?{node:r,offset:r.data.length}:null}function ye(e){let t=_e(e);if(!t.length)return[];let n=t.map(e=>e.data).join(``);return[...new Intl.Segmenter(`ko`,{granularity:`sentence`}).segment(n)].flatMap(n=>{let r=n.segment,i=r.search(/\S/);if(i<0)return[];let a=r.length-r.trimEnd().length,o=ve(t,n.index+i),s=ve(t,n.index+r.length-a);if(!o||!s)return[];let c=e.ownerDocument.createRange();try{c.setStart(o.node,o.offset),c.setEnd(s.node,s.offset)}catch{return[]}return[{text:r.trim(),range:c,start:n.index+i,end:n.index+r.length-a}]})}function be(){for(let e of N)e.remove();N=[]}function Z(){be();let e;try{e=f.contentDocument}catch{Y(`cross-origin`);return}if(!e?.body||!j)return;let t=e.createElement(`div`);t.id=`feedback-overlay-root`,Object.assign(t.style,{position:`absolute`,inset:`0`,zIndex:`2147483646`,pointerEvents:`none`}),e.body.append(t),N.push(t);let n=[...e.querySelectorAll(T)].filter(e=>!e.closest(`.notes,.controls,.note-panel,#feedback-overlay-root,[aria-hidden="true"]`)&&e.textContent.trim().length>1),r=n.map(e=>_e(e).map(e=>e.data).join(``));window.__feedbackDraftText=r.join(`
|
|
2
|
-
|
|
3
|
-
`);let i=0,a=0;for(let[o,s]of n.entries()){let c=r[o],l=a;for(let n of ye(s)){let r=`sentence-${i++}`,a=[...n.range.getClientRects()].filter(e=>e.width>2&&e.height>2);for(let[i,o]of a.entries()){let a=e.createElement(`button`);a.type=`button`,a.disabled=!F,a.dataset.group=r,i===0?a.setAttribute(`aria-label`,`${n.text} 피드백 남기기`):(a.tabIndex=-1,a.setAttribute(`aria-hidden`,`true`));let u=P.some(e=>e.group===r||e.text===n.text&&e.slide===(s.closest(`.slide`)?.dataset.title||``));a.dataset.pinned=String(u),Object.assign(a.style,{position:`absolute`,left:`${o.left+f.contentWindow.scrollX}px`,top:`${o.top+f.contentWindow.scrollY}px`,width:`${o.width}px`,height:`${o.height}px`,margin:`0`,padding:`0`,border:`1px solid transparent`,borderRadius:`2px`,background:u?`rgba(0,0,0,.18)`:`rgba(0,0,0,.01)`,borderColor:u?`rgba(0,0,0,.78)`:`transparent`,cursor:F?`pointer`:`progress`,pointerEvents:`auto`}),a.addEventListener(`mouseenter`,()=>xe(e,r,!0)),a.addEventListener(`mouseleave`,()=>xe(e,r,!1)),a.addEventListener(`click`,e=>{e.preventDefault(),e.stopPropagation(),Se({group:r,text:n.text,start:l+Array.from(c.slice(0,n.start)).length,end:l+Array.from(c.slice(0,n.end)).length,paragraphText:c,paragraphStart:l,paragraphEnd:l+Array.from(c).length,slide:s.closest(`.slide`)?.dataset.title||``,page:s.closest(`.canvas`)?.dataset.page||``})}),t.append(a)}}a+=Array.from(c).length+(o<n.length-1?2:0)}}function xe(e,t,n){for(let r of e.querySelectorAll(`[data-group="${t}"]`)){let e=r.dataset.pinned===`true`;r.style.borderColor=n||e?`rgba(0,0,0,.78)`:`transparent`,r.style.background=n?`rgba(0,0,0,.10)`:e?`rgba(0,0,0,.18)`:`rgba(0,0,0,.01)`}}function Se(e){M=e,R=`sentence`,b.value=``,k.value=``,C.checked=!1,G(),K(),y.classList.add(`open`),x.hidden=!1}function Q(){let e=P.filter(e=>!e.submitted);if(te.textContent=String(P.length),S.disabled=e.length===0,v.replaceChildren(),!P.length){let e=document.createElement(`p`);e.className=`item`,e.textContent=`아직 담긴 피드백이 없습니다.`,v.append(e);return}P.forEach((e,t)=>{let n=document.createElement(`div`);n.className=`item`;let r=document.createElement(`b`),i=a[e.category]||e.category;r.textContent=`${e.page}${e.slide?` · ${e.slide}`:``} · ${i}${e.term?` · ${e.term}`:``}${e.submitted?` · 전송됨`:``}`;let o=document.createElement(`p`);o.textContent=e.text;let s=document.createElement(`p`);s.textContent=e.correction||`수정 제안 없음`;let c=document.createElement(`button`);c.type=`button`,c.textContent=`제거`,c.addEventListener(`click`,()=>{P.splice(t,1);let e=V();Q(),$(),e||H(`검수함 변경은 현재 탭에서만 유지됩니다.`)}),n.append(r,o,s,c),v.append(n)})}f.addEventListener(`load`,async()=>{try{if(Z(),!f.contentDocument?.body){Y(`iframe-access`);return}await ue(),F=!0,Z()}catch(e){Y(e.message||`validation-failed`);return}H(`원본 iframe에 검수 레이어를 연결했습니다.`)});var $=()=>m?ge():Z(),Ce;addEventListener(`resize`,()=>{clearTimeout(Ce),Ce=setTimeout($,120)}),g.addEventListener(`click`,()=>{j=!j,g.textContent=j?`검수 모드`:`읽기 모드`,g.setAttribute(`aria-pressed`,String(j)),ie.textContent=j?`검수 중`:`읽기 중`,j||(y.classList.remove(`open`),x.hidden=!0),$()}),C.addEventListener(`change`,J),k.addEventListener(`input`,J),O.addEventListener(`click`,e=>{let t=e.target.closest(`button[data-mode]`);!t||!M||(R=t.dataset.mode,G())}),document.getElementById(`showBasket`).addEventListener(`click`,()=>{_.hidden=!_.hidden,Q()}),document.getElementById(`closeBasket`).addEventListener(`click`,()=>{_.hidden=!0}),document.getElementById(`cancel`).addEventListener(`click`,()=>{y.classList.remove(`open`),x.hidden=!0}),document.getElementById(`add`).addEventListener(`click`,()=>{if(!M){H(`선택한 문장을 다시 눌러 주세요.`);return}let e=U()?``:b.value.normalize(`NFC`).trim();if(o.has(L)&&!e){H(`이 분류에는 수정문이 필요합니다.`),b.focus();return}let t=W(),n=R===`word`?k.value.normalize(`NFC`).trim():``;if(R===`word`){if(!n){H(`어떤 표현에 대한 지적인지 적어 주세요.`),k.focus();return}if(!t.text.normalize(`NFC`).includes(n)){H(`대상 표현이 선택한 문장 안에 없습니다.`),k.focus();return}}P.push({...M,id:z(),category:L,mode:R,start:t.start,end:t.end,text:t.text,...n?{term:n}:{},scope:q().scope,correction:e,created_at:new Date().toISOString(),submitted:!1});let r=V();y.classList.remove(`open`),x.hidden=!0,Q(),$(),H(r?`검수함에 추가했습니다.`:`검수함에 추가했습니다. 현재 탭에서만 유지됩니다.`)}),document.getElementById(`copyFeedback`).addEventListener(`click`,async()=>{if(!u){await navigator.clipboard.writeText(JSON.stringify({source:f.src,feedback:P},null,2)),H(`피드백 JSON을 복사했습니다.`);return}if(!d){H(`전송 실패: 작업 정보를 아직 불러오지 못했습니다.`);return}let e=P.filter(e=>!e.submitted);S.disabled=!0,H(`${e.length}개 피드백을 전송하고 있습니다.`);try{let n=await t(l,e.map(e=>({input:{job_id:d,category:e.category,scope:e.scope||`run_only`,selection:{start:e.start,end:e.end,selected_text:e.text},...e.term?{term:e.term}:{},...e.correction?{correction:e.correction}:{}},idempotencyKey:e.id||z()})));e.forEach((e,t)=>{e.submitted=!0,e.feedback_id=n[t].feedback_id});let r=V();Q(),$(),H(r?`피드백을 전송했습니다.`:`피드백을 전송했습니다. 브라우저 검수함 상태는 저장하지 못했습니다.`)}catch(e){Q(),H(`전송 실패: ${s(e,`다시 시도해 주세요.`)}`)}}),le(),Q(),pe();
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
:root{--ink:#000;--paper:#fff;--muted:#6b6b6b;--rule:#d4d4d4;--wash:#f2f2f2;--serif:"Apple SD Gothic Neo", "Noto Serif CJK KR", "Nanum Myeongjo", "AppleMyungjo", serif;--mono:"SF Mono", "Menlo", "D2Coding", "Consolas", monospace;--column:41rem;--bar-height:54px;--safe-bottom:env(safe-area-inset-bottom,0px);--sheet-bottom:calc(var(--bar-height) + var(--safe-bottom));--sheet-max-height:62dvh;color:var(--ink);background:var(--paper);font-family:var(--mono);font-synthesis:none;text-rendering:optimizelegibility}*{box-sizing:border-box}body{background:var(--paper);min-width:320px;min-height:100dvh;margin:0}button,select,textarea,input{font:inherit;color:inherit}button,select,textarea,[tabindex]{outline-offset:2px;outline:2px solid #0000}button:focus-visible,select:focus-visible,textarea:focus-visible,[tabindex]:focus-visible{outline-color:var(--ink)}.desk{width:min(var(--column) + 48px, 100vw - 32px);margin:0 auto;padding:0 0 40dvh}.topline{z-index:5;border-bottom:1px solid var(--ink);background:var(--paper);align-items:baseline;gap:14px;padding:18px 0 10px;display:flex;position:sticky;top:0}.route{letter-spacing:.06em;text-transform:lowercase;margin:0;font-size:.7rem}.badge{color:var(--muted);letter-spacing:.14em;text-transform:uppercase;margin-right:auto;font-size:.66rem}.mode-toggle{border:1px solid var(--ink);background:var(--paper);cursor:pointer;letter-spacing:.08em;min-height:30px;padding:0 10px;font-size:.68rem}.mode-toggle[aria-pressed=true]{background:var(--ink);color:var(--paper)}.doc{max-width:var(--column);font-family:var(--serif);word-break:keep-all;overflow-wrap:anywhere;margin:34px auto 0;font-size:1.08rem;line-height:2.05}.doc-block{margin:0 0 1.35em}.doc-block.doc-h1,.doc-block.doc-h2,.doc-block.doc-h3,.doc-block.doc-h4{font-family:var(--mono);letter-spacing:.2em;text-transform:uppercase;margin:2.4em 0 .9em;font-size:.72rem;font-weight:400;line-height:1.5}.doc-block.doc-h1:before,.doc-block.doc-h2:before,.doc-block.doc-h3:before{content:"";border-top:1px solid var(--ink);width:100%;margin-bottom:.9em;display:block}.doc-block.doc-pre{font-family:var(--mono);font-size:.82rem;line-height:1.7}.doc-block.doc-pre pre{border-left:2px solid var(--ink);background:var(--wash);margin:0;padding:14px 16px;overflow-x:auto}.doc code{font-family:var(--mono);font-size:.86em}.doc a{color:inherit;text-underline-offset:3px;text-decoration-thickness:1px}.doc table{border-collapse:collapse;font-size:.9rem;display:block;overflow-x:auto}.doc :is(th,td){border:1px solid var(--rule);padding:6px 10px}.doc blockquote{border-left:2px solid var(--ink);margin:0;padding-left:14px}.doc img{max-width:100%;height:auto}.preview-attachment{max-width:min(100%,760px);margin:18px 0;display:block}.preview-attachment img{border:1px solid var(--rule);object-fit:contain;max-width:100%;max-height:70vh;display:block}.preview-attachment figcaption{color:var(--muted);font-family:var(--mono);margin-top:6px;font-size:.76rem}.preview-image-note{color:var(--muted);font-family:var(--mono);font-size:.76rem;display:inline-block}.unit{cursor:pointer;transition:background .12s linear,color .12s linear}.doc:not(.is-reading) .unit:hover,.doc:not(.is-reading) .unit:focus-visible,.doc:not(.is-reading) .unit.is-hover-group{text-underline-offset:5px;outline:none;text-decoration:underline;text-decoration-thickness:1px}.doc.is-reading .unit{cursor:text}.unit.is-pinned{background:var(--ink);color:var(--paper);padding:0 1px;text-decoration:none}.unit.is-pinned :is(code,a,strong,em){color:inherit}.pin{font-family:var(--mono);vertical-align:super;margin-left:3px;font-size:.62rem;font-weight:400}.pin-scope{gap:8px;margin:0 0 12px;display:flex}.pin-scope .chip:disabled{opacity:.4;cursor:default}.pin-term{box-sizing:border-box;border:1px solid var(--rule);width:100%;min-height:44px;font-family:var(--mono);margin:0 0 12px;padding:10px 12px;font-size:.92rem}.pin-siblings{border-top:1px solid var(--rule);flex-wrap:wrap;gap:8px;margin:12px 0 0;padding-top:12px;display:flex}.pin-sibling{border:1px solid var(--rule);min-height:44px;font-family:var(--mono);color:var(--muted);cursor:pointer;background:0 0;padding:0 12px;font-size:.78rem}.pin-siblings[hidden]{display:none}.source-panel{max-width:var(--column);border:1px solid var(--rule);margin:24px auto 0}.source-panel>summary{box-sizing:border-box;min-height:44px;font-family:var(--mono);color:var(--muted);cursor:pointer;align-items:center;padding:12px 14px;font-size:.78rem;display:flex}.source-text{border-top:1px solid var(--rule);font-family:var(--serif);color:var(--muted);white-space:pre-wrap;word-break:keep-all;overflow-wrap:anywhere;padding:14px;font-size:.98rem;line-height:1.85}.review-bar{z-index:20;padding:12px max(16px, calc((100vw - var(--column)) / 2));padding-bottom:calc(12px + var(--safe-bottom));background:var(--ink);color:var(--paper);letter-spacing:.08em;align-items:center;gap:16px;font-size:.72rem;display:flex;position:fixed;bottom:0;left:0;right:0}.bar-mode{text-transform:uppercase}.bar-count{color:#b3b3b3;margin-right:auto}.bar-add,.bar-send,.bar-finish{cursor:pointer;letter-spacing:.08em;white-space:nowrap;min-height:30px;padding:0 14px;font-size:.72rem}.bar-add,.bar-finish{color:var(--paper);background:0 0;border:1px solid #6b6b6b}.bar-send{border:1px solid var(--paper);background:var(--paper);color:var(--ink)}.bar-add:disabled,.bar-send:disabled,.bar-finish:disabled{color:#6b6b6b;cursor:default;background:0 0;border-color:#4d4d4d}.review-bar .status{color:#b3b3b3;margin:0}.pin-card{left:0;right:0;bottom:var(--sheet-bottom);z-index:19;max-height:var(--sheet-max-height);padding:22px max(16px, calc((100vw - var(--column)) / 2)) 26px;background:var(--ink);color:var(--paper);border-top:1px solid #4d4d4d;transition:bottom .16s ease-out,max-height .16s ease-out;animation:.16s ease-out sheet-up;position:fixed;overflow-y:auto}@keyframes sheet-up{0%{opacity:0;transform:translateY(12px)}}.pin-card-head{letter-spacing:.16em;text-transform:uppercase;justify-content:space-between;align-items:baseline;margin-bottom:12px;font-size:.68rem;display:flex}.pin-card-close{min-width:30px;min-height:30px;color:var(--paper);cursor:pointer;background:0 0;border:0;font-size:1rem}.pin-quote{border-left:2px solid var(--paper);font-family:var(--serif);max-height:7.2em;margin:0 0 20px;padding-left:12px;font-size:1rem;line-height:1.8;overflow-y:auto}.label{color:#b3b3b3;letter-spacing:.18em;text-transform:uppercase;margin:0 0 8px;font-size:.62rem}.chips{flex-wrap:wrap;gap:6px;margin-bottom:20px;display:flex}.chip{min-height:30px;color:var(--paper);cursor:pointer;background:0 0;border:1px solid #6b6b6b;padding:0 11px;font-size:.72rem}.chip.is-on{border-color:var(--paper);background:var(--paper);color:var(--ink)}.pin-correction{width:100%;min-height:76px;color:var(--paper);font-family:var(--serif);resize:vertical;background:0 0;border:1px solid #6b6b6b;border-radius:0;padding:10px 12px;font-size:.98rem}.pin-correction::placeholder{color:#6b6b6b}.pin-remember{align-items:center;gap:9px;min-height:42px;font-size:.72rem;display:flex}.pin-remember[hidden]{display:none}.pin-remember input{width:14px;height:14px;accent-color:var(--paper)}.pin-actions{align-items:center;gap:10px;margin:2px 0 18px;display:flex}.pin-actions[hidden]{display:none}.pin-remove{color:#b3b3b3;cursor:pointer;text-underline-offset:3px;background:0 0;border:0;min-height:34px;font-size:.72rem;text-decoration:underline}.pin-card .status{color:#b3b3b3;margin:10px 0 0;font-size:.72rem}.pin-actions .status{margin:0}.terminal-state{max-width:var(--column);margin:22vh auto 0}.terminal-state h1{letter-spacing:.2em;text-transform:uppercase;margin:0 0 10px;font-size:.78rem;font-weight:400}.terminal-state p,.empty-note,.status{color:var(--muted);margin:0;font-size:.78rem}@media (width<=720px){.review-bar{padding-top:6px;padding-left:12px;padding-right:12px;padding-bottom:calc(6px + var(--safe-bottom));gap:6px}.bar-add,.bar-send,.bar-finish{padding-left:10px;padding-right:10px}.doc{font-size:1.02rem;line-height:1.95}.pin-card{padding-bottom:12px;padding-left:16px;padding-right:16px}.pin-actions{margin-bottom:0}}@media (pointer:coarse){.chip,.pin-card-close,.pin-remove,.bar-add,.bar-send,.bar-finish,.mode-toggle{min-height:44px}.chip{padding:0 14px}.pin-card-close{min-width:44px}}@media (prefers-reduced-motion:reduce){*,:before,:after{scroll-behavior:auto!important;transition-duration:.001ms!important;animation-duration:.001ms!important}}
|