@markdy/core 0.8.6 → 0.8.8
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/dist/index.js +189 -29
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -351,7 +351,7 @@ var EDGE_OPERATORS = {
|
|
|
351
351
|
};
|
|
352
352
|
var RESERVED_SELECTORS = /* @__PURE__ */ new Set(["$title", "$nodes", "$edges"]);
|
|
353
353
|
var BEAT_CUE_KEYWORDS = /* @__PURE__ */ new Set(["show", "hide", "glow", "focus", "frame", "use"]);
|
|
354
|
-
var SCENE_KEYS = /* @__PURE__ */ new Set(["width", "height", "fps", "theme", "duration", "direction"]);
|
|
354
|
+
var SCENE_KEYS = /* @__PURE__ */ new Set(["width", "height", "fps", "theme", "duration", "direction", "layout"]);
|
|
355
355
|
function nodeRole(kind) {
|
|
356
356
|
return TECHNICAL_NODE_KINDS[kind] ?? "compute";
|
|
357
357
|
}
|
|
@@ -758,24 +758,79 @@ var ParseError = class extends Error {
|
|
|
758
758
|
}
|
|
759
759
|
};
|
|
760
760
|
var FLOW_OP_RE = /(->|<-|~>|--)/;
|
|
761
|
-
var PROP_RE = /(\w[\w.-]*)=(\S+)/g;
|
|
762
761
|
function stripComment(line) {
|
|
763
|
-
|
|
764
|
-
|
|
762
|
+
let inString = false;
|
|
763
|
+
let escaped = false;
|
|
764
|
+
for (let i = 0; i < line.length; i++) {
|
|
765
|
+
const ch = line[i];
|
|
766
|
+
if (escaped) {
|
|
767
|
+
escaped = false;
|
|
768
|
+
continue;
|
|
769
|
+
}
|
|
770
|
+
if (inString && ch === "\\") {
|
|
771
|
+
escaped = true;
|
|
772
|
+
continue;
|
|
773
|
+
}
|
|
774
|
+
if (ch === '"') {
|
|
775
|
+
inString = !inString;
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
778
|
+
if (inString) continue;
|
|
779
|
+
if (ch === "/" && line[i + 1] === "/") return line.slice(0, i);
|
|
780
|
+
if (ch === "#" && (i === 0 || /\s/.test(line[i - 1]))) return line.slice(0, i);
|
|
781
|
+
}
|
|
782
|
+
return line;
|
|
783
|
+
}
|
|
784
|
+
function parsePropValue(raw) {
|
|
785
|
+
let val = raw;
|
|
786
|
+
if (raw.startsWith('"')) {
|
|
787
|
+
const parsed = parseStringToken(raw);
|
|
788
|
+
if (parsed && parsed.rest === "") val = parsed.value;
|
|
789
|
+
}
|
|
790
|
+
if (typeof val === "string") {
|
|
791
|
+
if (/^\d+(\.\d+)?$/.test(val)) val = Number(val);
|
|
792
|
+
else if (val === "true") val = true;
|
|
793
|
+
else if (val === "false") val = false;
|
|
794
|
+
else if (/^\d+ms$/.test(val)) val = Number(val.slice(0, -2)) / 1e3;
|
|
795
|
+
else if (/^\d+(\.\d+)?s$/.test(val)) val = Number(val.slice(0, -1));
|
|
796
|
+
}
|
|
797
|
+
return val;
|
|
765
798
|
}
|
|
766
799
|
function parseProps(raw) {
|
|
767
800
|
const props = {};
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
if (
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
801
|
+
let i = 0;
|
|
802
|
+
while (i < raw.length) {
|
|
803
|
+
const ch = raw[i];
|
|
804
|
+
if (ch === '"') {
|
|
805
|
+
const parsed = parseStringToken(raw.slice(i));
|
|
806
|
+
if (!parsed) break;
|
|
807
|
+
i = raw.length - parsed.rest.length;
|
|
808
|
+
continue;
|
|
809
|
+
}
|
|
810
|
+
const keyMatch = raw.slice(i).match(/^(\w[\w.-]*)=/);
|
|
811
|
+
if (!keyMatch) {
|
|
812
|
+
i++;
|
|
813
|
+
continue;
|
|
777
814
|
}
|
|
778
|
-
|
|
815
|
+
const key = keyMatch[1];
|
|
816
|
+
i += key.length + 1;
|
|
817
|
+
let value = "";
|
|
818
|
+
if (raw[i] === '"') {
|
|
819
|
+
const start = i;
|
|
820
|
+
const parsed = parseStringToken(raw.slice(i));
|
|
821
|
+
if (!parsed) {
|
|
822
|
+
value = raw.slice(i);
|
|
823
|
+
i = raw.length;
|
|
824
|
+
} else {
|
|
825
|
+
i = raw.length - parsed.rest.length;
|
|
826
|
+
value = raw.slice(start, i).trim();
|
|
827
|
+
}
|
|
828
|
+
} else {
|
|
829
|
+
const start = i;
|
|
830
|
+
while (i < raw.length && !/\s/.test(raw[i])) i++;
|
|
831
|
+
value = raw.slice(start, i);
|
|
832
|
+
}
|
|
833
|
+
props[key] = parsePropValue(value);
|
|
779
834
|
}
|
|
780
835
|
return props;
|
|
781
836
|
}
|
|
@@ -800,6 +855,56 @@ function parseStringToken(raw) {
|
|
|
800
855
|
function splitTargets(raw) {
|
|
801
856
|
return raw.split(/[\s,]+/).map((t) => t.trim()).filter(Boolean);
|
|
802
857
|
}
|
|
858
|
+
function splitOutsideQuotes(raw, separator) {
|
|
859
|
+
const parts = [];
|
|
860
|
+
let start = 0;
|
|
861
|
+
let inString = false;
|
|
862
|
+
let escaped = false;
|
|
863
|
+
for (let i = 0; i < raw.length; i++) {
|
|
864
|
+
const ch = raw[i];
|
|
865
|
+
if (escaped) {
|
|
866
|
+
escaped = false;
|
|
867
|
+
continue;
|
|
868
|
+
}
|
|
869
|
+
if (inString && ch === "\\") {
|
|
870
|
+
escaped = true;
|
|
871
|
+
continue;
|
|
872
|
+
}
|
|
873
|
+
if (ch === '"') {
|
|
874
|
+
inString = !inString;
|
|
875
|
+
continue;
|
|
876
|
+
}
|
|
877
|
+
if (!inString && ch === separator && /\s/.test(raw[i - 1] ?? "") && /\s/.test(raw[i + 1] ?? "")) {
|
|
878
|
+
parts.push(raw.slice(start, i).trim());
|
|
879
|
+
start = i + 1;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
parts.push(raw.slice(start).trim());
|
|
883
|
+
return parts.filter(Boolean);
|
|
884
|
+
}
|
|
885
|
+
function stripCueProps(raw, keys) {
|
|
886
|
+
let inString = false;
|
|
887
|
+
let escaped = false;
|
|
888
|
+
for (let i = 0; i < raw.length; i++) {
|
|
889
|
+
const ch = raw[i];
|
|
890
|
+
if (escaped) {
|
|
891
|
+
escaped = false;
|
|
892
|
+
continue;
|
|
893
|
+
}
|
|
894
|
+
if (inString && ch === "\\") {
|
|
895
|
+
escaped = true;
|
|
896
|
+
continue;
|
|
897
|
+
}
|
|
898
|
+
if (ch === '"') {
|
|
899
|
+
inString = !inString;
|
|
900
|
+
continue;
|
|
901
|
+
}
|
|
902
|
+
if (inString || !/\s/.test(ch)) continue;
|
|
903
|
+
const rest = raw.slice(i + 1);
|
|
904
|
+
if (keys.some((key) => rest.startsWith(`${key}=`))) return raw.slice(0, i).trim();
|
|
905
|
+
}
|
|
906
|
+
return raw.trim();
|
|
907
|
+
}
|
|
803
908
|
function parseFlowChain(line, lineNo) {
|
|
804
909
|
const segments = [];
|
|
805
910
|
const parts = line.split(FLOW_OP_RE).map((p) => p.trim()).filter(Boolean);
|
|
@@ -837,16 +942,22 @@ function splitTargetLabel(token, lineNo) {
|
|
|
837
942
|
function parseCueLine(line, lineNo) {
|
|
838
943
|
const trimmed = line.trim();
|
|
839
944
|
const props = parseProps(trimmed);
|
|
840
|
-
if (trimmed
|
|
841
|
-
|
|
945
|
+
if (/^@\+?\d/.test(trimmed) || /^\w[\w.-]*\.\w+\(/.test(trimmed) || /^camera\./.test(trimmed)) {
|
|
946
|
+
throw new ParseError(
|
|
947
|
+
"unsupported timeline command; use beat cues like show, frame, focus, glow, and flow lines",
|
|
948
|
+
lineNo
|
|
949
|
+
);
|
|
950
|
+
}
|
|
951
|
+
const parallelParts = splitOutsideQuotes(trimmed, "&");
|
|
952
|
+
if (parallelParts.length > 1) {
|
|
842
953
|
return {
|
|
843
954
|
kind: "parallel",
|
|
844
|
-
cues:
|
|
955
|
+
cues: parallelParts.map((p, idx) => parseCueLine(p, lineNo + idx * 1e-3)),
|
|
845
956
|
line: lineNo
|
|
846
957
|
};
|
|
847
958
|
}
|
|
848
959
|
if (FLOW_OP_RE.test(trimmed)) {
|
|
849
|
-
const chainPart = trimmed
|
|
960
|
+
const chainPart = stripCueProps(trimmed, ["dur", "stagger", "color", "strength", "zoom", "after"]);
|
|
850
961
|
return {
|
|
851
962
|
kind: "flow",
|
|
852
963
|
segments: parseFlowChain(chainPart, lineNo),
|
|
@@ -857,7 +968,7 @@ function parseCueLine(line, lineNo) {
|
|
|
857
968
|
const [head, ...rest] = trimmed.split(/\s+/);
|
|
858
969
|
const keyword = head.toLowerCase();
|
|
859
970
|
if (keyword === "show" || keyword === "hide") {
|
|
860
|
-
const targetRaw = rest.join(" ")
|
|
971
|
+
const targetRaw = stripCueProps(rest.join(" "), ["dur", "stagger"]);
|
|
861
972
|
return {
|
|
862
973
|
kind: keyword,
|
|
863
974
|
targets: splitTargets(targetRaw),
|
|
@@ -867,7 +978,7 @@ function parseCueLine(line, lineNo) {
|
|
|
867
978
|
};
|
|
868
979
|
}
|
|
869
980
|
if (keyword === "glow") {
|
|
870
|
-
const targetRaw = rest.join(" ")
|
|
981
|
+
const targetRaw = stripCueProps(rest.join(" "), ["color", "strength", "dur"]);
|
|
871
982
|
return {
|
|
872
983
|
kind: "glow",
|
|
873
984
|
targets: splitTargets(targetRaw),
|
|
@@ -878,7 +989,7 @@ function parseCueLine(line, lineNo) {
|
|
|
878
989
|
};
|
|
879
990
|
}
|
|
880
991
|
if (keyword === "focus") {
|
|
881
|
-
const targetRaw = rest.join(" ")
|
|
992
|
+
const targetRaw = stripCueProps(rest.join(" "), ["zoom", "dur"]);
|
|
882
993
|
return {
|
|
883
994
|
kind: "focus",
|
|
884
995
|
targets: splitTargets(targetRaw),
|
|
@@ -888,7 +999,7 @@ function parseCueLine(line, lineNo) {
|
|
|
888
999
|
};
|
|
889
1000
|
}
|
|
890
1001
|
if (keyword === "frame") {
|
|
891
|
-
const targetRaw = rest.join(" ")
|
|
1002
|
+
const targetRaw = stripCueProps(rest.join(" "), ["zoom", "dur"]);
|
|
892
1003
|
const targets = splitTargets(targetRaw);
|
|
893
1004
|
if (targets.length === 0) throw new ParseError(`expected frame target`, lineNo);
|
|
894
1005
|
return {
|
|
@@ -992,6 +1103,45 @@ function readIndentedBody(blocks, startIdx, parentIndent) {
|
|
|
992
1103
|
}
|
|
993
1104
|
return { body, nextIdx: i };
|
|
994
1105
|
}
|
|
1106
|
+
function readBody(blocks, startIdx, parentIndent, braceDelimited) {
|
|
1107
|
+
if (!braceDelimited) return readIndentedBody(blocks, startIdx, parentIndent);
|
|
1108
|
+
const body = [];
|
|
1109
|
+
let i = startIdx;
|
|
1110
|
+
while (i < blocks.length) {
|
|
1111
|
+
if (blocks[i].text === "}") return { body, nextIdx: i + 1 };
|
|
1112
|
+
body.push(blocks[i]);
|
|
1113
|
+
i++;
|
|
1114
|
+
}
|
|
1115
|
+
return { body, nextIdx: i };
|
|
1116
|
+
}
|
|
1117
|
+
function normalizeCueBlocks(blocks) {
|
|
1118
|
+
const normalized = [];
|
|
1119
|
+
for (const block of blocks) {
|
|
1120
|
+
if (block.text.startsWith("& ")) {
|
|
1121
|
+
const prev = normalized[normalized.length - 1];
|
|
1122
|
+
if (!prev) throw new ParseError(`parallel continuation must follow a cue`, block.line);
|
|
1123
|
+
prev.text = `${prev.text} ${block.text}`;
|
|
1124
|
+
continue;
|
|
1125
|
+
}
|
|
1126
|
+
normalized.push({ ...block });
|
|
1127
|
+
}
|
|
1128
|
+
return normalized;
|
|
1129
|
+
}
|
|
1130
|
+
function unsupportedSyntaxMessage(line) {
|
|
1131
|
+
if (/^var\s+/.test(line)) {
|
|
1132
|
+
return "unsupported variable declaration; use scene properties and style declarations instead";
|
|
1133
|
+
}
|
|
1134
|
+
if (/^actor\s+/.test(line) || /\bfigure\s*\(/.test(line) || /\bbox\s*\(/.test(line) || /\bat\s*\(/.test(line)) {
|
|
1135
|
+
return "unsupported manual drawing syntax; declare architecture nodes like service API, cache Redis, and database DB";
|
|
1136
|
+
}
|
|
1137
|
+
if (/^@\+?\d/.test(line)) {
|
|
1138
|
+
return "unsupported timeline command; put flow and cue lines inside beat blocks";
|
|
1139
|
+
}
|
|
1140
|
+
if (/^camera\./.test(line)) {
|
|
1141
|
+
return "unsupported camera command; use frame NodeOrGroup zoom=... inside a beat";
|
|
1142
|
+
}
|
|
1143
|
+
return null;
|
|
1144
|
+
}
|
|
995
1145
|
function pushWarning(diagnostics, seen, line, message) {
|
|
996
1146
|
const key = `${line}:${message}`;
|
|
997
1147
|
if (seen.has(key)) return;
|
|
@@ -1077,14 +1227,24 @@ function parse(source, opts = {}) {
|
|
|
1077
1227
|
const block = blocks[i];
|
|
1078
1228
|
const line = block.text;
|
|
1079
1229
|
const lineNo = block.line;
|
|
1230
|
+
const unsupportedMessage = unsupportedSyntaxMessage(line);
|
|
1231
|
+
if (unsupportedMessage) throw new ParseError(unsupportedMessage, lineNo);
|
|
1080
1232
|
if (line.startsWith("scene")) {
|
|
1081
1233
|
const rest = line.slice(5).trim();
|
|
1234
|
+
if (line.endsWith("{")) {
|
|
1235
|
+
throw new ParseError(`nested scene blocks are not supported; use one scene with multiple beat blocks`, lineNo);
|
|
1236
|
+
}
|
|
1082
1237
|
let remainder = rest;
|
|
1083
1238
|
const str = parseStringToken(rest);
|
|
1084
1239
|
if (str) {
|
|
1085
1240
|
title = str.value;
|
|
1086
1241
|
remainder = str.rest;
|
|
1087
1242
|
}
|
|
1243
|
+
const inlineLayout = remainder.match(/\blayout\s+(LR|RL|TB|BT)\b/i);
|
|
1244
|
+
if (inlineLayout) {
|
|
1245
|
+
meta.direction = inlineLayout[1].toUpperCase();
|
|
1246
|
+
remainder = remainder.replace(/\blayout\s+(LR|RL|TB|BT)\b/i, " ");
|
|
1247
|
+
}
|
|
1088
1248
|
const props = parseProps(remainder);
|
|
1089
1249
|
for (const [k, v] of Object.entries(props)) {
|
|
1090
1250
|
if (!SCENE_KEYS.has(k)) {
|
|
@@ -1094,7 +1254,7 @@ function parse(source, opts = {}) {
|
|
|
1094
1254
|
if (k === "width" || k === "height" || k === "fps") meta[k] = Number(v);
|
|
1095
1255
|
else if (k === "duration") meta.duration = Number(v);
|
|
1096
1256
|
else if (k === "theme") meta.theme = String(v);
|
|
1097
|
-
else if (k === "direction") meta.direction = String(v).toUpperCase();
|
|
1257
|
+
else if (k === "direction" || k === "layout") meta.direction = String(v).toUpperCase();
|
|
1098
1258
|
}
|
|
1099
1259
|
i++;
|
|
1100
1260
|
continue;
|
|
@@ -1112,11 +1272,11 @@ function parse(source, opts = {}) {
|
|
|
1112
1272
|
continue;
|
|
1113
1273
|
}
|
|
1114
1274
|
if (line.startsWith("pattern ")) {
|
|
1115
|
-
const m = line.match(/^pattern\s+(\w+)\s*\(([^)]*)\)\s
|
|
1275
|
+
const m = line.match(/^pattern\s+(\w+)\s*\(([^)]*)\)\s*(?::|\{)\s*$/);
|
|
1116
1276
|
if (!m) throw new ParseError(`expected pattern name(params):`, lineNo);
|
|
1117
1277
|
const params = m[2].trim() ? m[2].split(",").map((p) => p.trim()) : [];
|
|
1118
|
-
const { body, nextIdx } =
|
|
1119
|
-
const cues = body.map((b) => parseCueLine(b.text, b.line));
|
|
1278
|
+
const { body, nextIdx } = readBody(blocks, i + 1, block.indent, line.endsWith("{"));
|
|
1279
|
+
const cues = normalizeCueBlocks(body).map((b) => parseCueLine(b.text, b.line));
|
|
1120
1280
|
patterns[m[1]] = { name: m[1], params, body: cues, line: lineNo };
|
|
1121
1281
|
i = nextIdx;
|
|
1122
1282
|
continue;
|
|
@@ -1153,10 +1313,10 @@ function parse(source, opts = {}) {
|
|
|
1153
1313
|
continue;
|
|
1154
1314
|
}
|
|
1155
1315
|
if (line.startsWith("beat ")) {
|
|
1156
|
-
const m = line.match(/^beat\s+(\w+)(?:\s+"([^"]*)")?\s
|
|
1316
|
+
const m = line.match(/^beat\s+(\w+)(?:\s+"([^"]*)")?\s*(?::|\{)\s*$/);
|
|
1157
1317
|
if (!m) throw new ParseError(`expected beat name:`, lineNo);
|
|
1158
|
-
const { body, nextIdx } =
|
|
1159
|
-
let cues = body.map((b) => parseCueLine(b.text, b.line));
|
|
1318
|
+
const { body, nextIdx } = readBody(blocks, i + 1, block.indent, line.endsWith("{"));
|
|
1319
|
+
let cues = normalizeCueBlocks(body).map((b) => parseCueLine(b.text, b.line));
|
|
1160
1320
|
cues = expandPatternCues(cues, patterns, lineNo);
|
|
1161
1321
|
beats.push({ name: m[1], label: m[2], cues, line: lineNo });
|
|
1162
1322
|
i = nextIdx;
|
package/package.json
CHANGED