formatferry 1.0.6 → 1.0.9

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.
Files changed (2) hide show
  1. package/dist/index.js +409 -406
  2. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -17256,8 +17256,8 @@ var require_turndown_cjs = __commonJS({
17256
17256
  [/_/g, "\\_"],
17257
17257
  [/^(\d+)\. /g, "$1\\. "]
17258
17258
  ];
17259
- function TurndownService2(options) {
17260
- if (!(this instanceof TurndownService2)) return new TurndownService2(options);
17259
+ function TurndownService3(options) {
17260
+ if (!(this instanceof TurndownService3)) return new TurndownService3(options);
17261
17261
  var defaults = {
17262
17262
  rules,
17263
17263
  headingStyle: "setext",
@@ -17284,7 +17284,7 @@ var require_turndown_cjs = __commonJS({
17284
17284
  this.options = extend({}, defaults, options);
17285
17285
  this.rules = new Rules(this.options);
17286
17286
  }
17287
- TurndownService2.prototype = {
17287
+ TurndownService3.prototype = {
17288
17288
  /**
17289
17289
  * The entry point for converting a string or DOM node to Markdown
17290
17290
  * @public
@@ -17405,7 +17405,7 @@ var require_turndown_cjs = __commonJS({
17405
17405
  function canConvert(input) {
17406
17406
  return input != null && (typeof input === "string" || input.nodeType && (input.nodeType === 1 || input.nodeType === 9 || input.nodeType === 11));
17407
17407
  }
17408
- module.exports = TurndownService2;
17408
+ module.exports = TurndownService3;
17409
17409
  }
17410
17410
  });
17411
17411
 
@@ -19490,7 +19490,7 @@ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "
19490
19490
  import { extname } from "path";
19491
19491
 
19492
19492
  // ../../src/shared/conversion-engine.ts
19493
- var import_turndown = __toESM(require_turndown_cjs(), 1);
19493
+ var import_turndown2 = __toESM(require_turndown_cjs(), 1);
19494
19494
 
19495
19495
  // ../../src/shared/input-sanitizer.ts
19496
19496
  import DOMPurifyImport from "dompurify";
@@ -19978,6 +19978,357 @@ function normalizeAtlassianHTML(html, domParser) {
19978
19978
  return doc.body.innerHTML;
19979
19979
  }
19980
19980
 
19981
+ // ../../src/shared/confluence-slack-services.ts
19982
+ var import_turndown = __toESM(require_turndown_cjs(), 1);
19983
+ function createSlackService(deps) {
19984
+ const service = new import_turndown.default({
19985
+ headingStyle: "atx",
19986
+ hr: "---",
19987
+ bulletListMarker: "-",
19988
+ codeBlockStyle: "fenced",
19989
+ fence: "```",
19990
+ emDelimiter: "_"
19991
+ });
19992
+ deps.addListRules(service);
19993
+ deps.addPdfPageMarkerRules(service);
19994
+ service.addRule("slackBold", {
19995
+ filter: (node) => {
19996
+ if (node.nodeName === "STRONG" || node.nodeName === "B") return true;
19997
+ if (node.nodeName === "SPAN") {
19998
+ const style = node.getAttribute("style") || "";
19999
+ if (style.includes("font-weight: bold") || style.includes("font-weight:bold")) return true;
20000
+ const fwMatch = style.match(/font-weight:\s*(\d+)/);
20001
+ if (fwMatch && parseInt(fwMatch[1]) >= 600) return true;
20002
+ const ffMatch = style.match(/font-family:\s*['"]*([^'"]+)['"]*[;]?/);
20003
+ if (ffMatch && ffMatch[1].toLowerCase().includes("bold")) return true;
20004
+ }
20005
+ return false;
20006
+ },
20007
+ replacement: (content) => content.trim() ? `*${content}*` : content
20008
+ });
20009
+ service.addRule("slackItalic", {
20010
+ filter: (node) => {
20011
+ if (node.nodeName === "EM" || node.nodeName === "I") return true;
20012
+ if (node.nodeName === "SPAN") {
20013
+ const style = node.getAttribute("style") || "";
20014
+ if (style.includes("font-style: italic") || style.includes("font-style:italic")) return true;
20015
+ const ffMatch = style.match(/font-family:\s*['"]*([^'"]+)['"]*[;]?/);
20016
+ if (ffMatch && ffMatch[1].toLowerCase().includes("italic")) return true;
20017
+ }
20018
+ return false;
20019
+ },
20020
+ replacement: (content) => content.trim() ? `_${content}_` : content
20021
+ });
20022
+ service.addRule("slackStrikethrough", {
20023
+ filter: (node) => {
20024
+ if (["DEL", "S", "STRIKE"].includes(node.nodeName)) return true;
20025
+ if (node.nodeName === "SPAN") {
20026
+ const style = node.getAttribute("style") || "";
20027
+ return style.includes("text-decoration: line-through") || style.includes("text-decoration:line-through") || style.includes("text-decoration-line: line-through") || style.includes("text-decoration-line:line-through");
20028
+ }
20029
+ return false;
20030
+ },
20031
+ replacement: (content) => content.trim() ? `~${content}~` : content
20032
+ });
20033
+ service.addRule("slackMonospaceCode", {
20034
+ filter: (node) => {
20035
+ if (node.nodeName !== "SPAN") return false;
20036
+ const style = node.getAttribute("style") || "";
20037
+ const fontFamily = style.toLowerCase();
20038
+ return fontFamily.includes("monospace") || fontFamily.includes("courier") || fontFamily.includes("consolas") || fontFamily.includes("monaco") || fontFamily.includes("menlo") || fontFamily.includes("source code") || fontFamily.includes("fira code") || fontFamily.includes("roboto mono") || fontFamily.includes("ubuntu mono");
20039
+ },
20040
+ replacement: (content) => {
20041
+ const trimmed = content.trim();
20042
+ if (!trimmed) return content;
20043
+ return trimmed.includes("`") ? `\`\` ${trimmed} \`\`` : `\`${trimmed}\``;
20044
+ }
20045
+ });
20046
+ service.addRule("slackHeading", {
20047
+ filter: ["h1", "h2", "h3", "h4", "h5", "h6"],
20048
+ replacement: (content) => content.trim() ? `*${content.trim()}*
20049
+
20050
+ ` : content
20051
+ });
20052
+ service.addRule("slackGoogleDocsHeadings", {
20053
+ filter: (node) => {
20054
+ if (node.nodeName !== "P" && node.nodeName !== "DIV") return false;
20055
+ const style = node.getAttribute("style") || "";
20056
+ const fontSizeMatch = style.match(/font-size:\s*(\d+(?:\.\d+)?)px/);
20057
+ if (fontSizeMatch && parseFloat(fontSizeMatch[1]) > 16) return true;
20058
+ if (style.includes("font-weight: bold") || style.includes("font-weight:bold")) {
20059
+ const textContent = node.textContent || "";
20060
+ return textContent.length < 100 && textContent.trim().length > 0;
20061
+ }
20062
+ return false;
20063
+ },
20064
+ replacement: (content) => content.trim() ? `*${content.trim()}*
20065
+
20066
+ ` : content
20067
+ });
20068
+ service.addRule("slackUnderline", {
20069
+ filter: (node) => {
20070
+ if (node.nodeName === "U" || node.nodeName === "INS") return true;
20071
+ if (node.nodeName === "SPAN") {
20072
+ const style = node.getAttribute("style") || "";
20073
+ return style.includes("text-decoration: underline") || style.includes("text-decoration:underline");
20074
+ }
20075
+ return false;
20076
+ },
20077
+ replacement: (content) => content
20078
+ });
20079
+ service.addRule("slackLink", {
20080
+ filter: (node) => node.nodeName === "A" && !!node.getAttribute("href"),
20081
+ replacement: (content, node) => {
20082
+ const href = node.getAttribute("href") || "";
20083
+ const text = content.trim().replace(/\|/g, " ");
20084
+ if (!text || text === href) return `<${href}>`;
20085
+ return `<${href}|${text}>`;
20086
+ }
20087
+ });
20088
+ service.addRule("slackImage", {
20089
+ filter: "img",
20090
+ replacement: (_content, node) => {
20091
+ const src = node.getAttribute("src") || "";
20092
+ const alt = (node.getAttribute("alt") || "image").replace(/\|/g, " ");
20093
+ return `<${src}|${alt}>`;
20094
+ }
20095
+ });
20096
+ service.addRule("slackTable", {
20097
+ filter: "table",
20098
+ replacement: (_content, node) => {
20099
+ const plainText = deps.tableToPlainText(node);
20100
+ return `\`\`\`
20101
+ ${plainText}
20102
+ \`\`\`
20103
+
20104
+ `;
20105
+ }
20106
+ });
20107
+ return service;
20108
+ }
20109
+ function processConfluenceList(list, service, depth) {
20110
+ const isOrdered = list.nodeName === "OL";
20111
+ const marker = (isOrdered ? "#" : "*").repeat(depth);
20112
+ const items = Array.from(list.children).filter((c) => c.nodeName === "LI");
20113
+ let result = depth === 1 ? "\n" : "";
20114
+ items.forEach((item) => {
20115
+ const itemContent = processConfluenceListItem(item, service, depth);
20116
+ result += `${marker} ${itemContent}
20117
+ `;
20118
+ });
20119
+ return depth === 1 ? result + "\n" : result;
20120
+ }
20121
+ function processConfluenceListItem(item, service, depth) {
20122
+ let content = "";
20123
+ const TEXT_NODE = 3;
20124
+ const ELEMENT_NODE = 1;
20125
+ Array.from(item.childNodes).forEach((child) => {
20126
+ if (child.nodeType === TEXT_NODE) {
20127
+ content += child.textContent || "";
20128
+ } else if (child.nodeType === ELEMENT_NODE) {
20129
+ const el = child;
20130
+ if (el.nodeName === "UL" || el.nodeName === "OL") {
20131
+ content += "\n" + processConfluenceList(el, service, depth + 1);
20132
+ } else {
20133
+ content += service.turndown(el.outerHTML);
20134
+ }
20135
+ }
20136
+ });
20137
+ return content.trim();
20138
+ }
20139
+ function createConfluenceService(deps) {
20140
+ const service = new import_turndown.default({
20141
+ headingStyle: "atx",
20142
+ hr: "---",
20143
+ bulletListMarker: "-",
20144
+ codeBlockStyle: "fenced",
20145
+ emDelimiter: "_"
20146
+ });
20147
+ deps.addPdfPageMarkerRules(service);
20148
+ service.addRule("confluenceHeading", {
20149
+ filter: ["h1", "h2", "h3", "h4", "h5", "h6"],
20150
+ replacement: (content, node) => {
20151
+ const level = parseInt(node.nodeName[1], 10);
20152
+ return `
20153
+
20154
+ h${level}. ${content.trim()}
20155
+
20156
+ `;
20157
+ }
20158
+ });
20159
+ service.addRule("confluenceCodeBlock", {
20160
+ filter: "pre",
20161
+ replacement: (_content, node) => {
20162
+ const codeEl = node.querySelector("code");
20163
+ const langMatch = codeEl?.className?.match(/language-(\w+)/);
20164
+ const lang = langMatch ? langMatch[1] : "";
20165
+ const codeContent = codeEl?.textContent || node.textContent || "";
20166
+ const langAttr = lang ? `:language=${lang}` : "";
20167
+ return `
20168
+
20169
+ {code${langAttr}}
20170
+ ${codeContent}
20171
+ {code}
20172
+
20173
+ `;
20174
+ }
20175
+ });
20176
+ service.addRule("confluenceInlineCode", {
20177
+ filter: (node) => node.nodeName === "CODE" && node.parentNode?.nodeName !== "PRE",
20178
+ replacement: (content) => content ? `{{${content.replace(/}+/g, (m) => m.split("").join(" "))}}}` : ""
20179
+ });
20180
+ service.addRule("confluenceBold", {
20181
+ filter: (node) => {
20182
+ if (node.nodeName === "STRONG" || node.nodeName === "B") return true;
20183
+ if (node.nodeName === "SPAN") {
20184
+ const style = node.getAttribute("style") || "";
20185
+ if (style.includes("font-weight: bold") || style.includes("font-weight:bold")) return true;
20186
+ const fwMatch = style.match(/font-weight:\s*(\d+)/);
20187
+ if (fwMatch && parseInt(fwMatch[1]) >= 600) return true;
20188
+ const ffMatch = style.match(/font-family:\s*['"]*([^'"]+)['"]*[;]?/);
20189
+ if (ffMatch && ffMatch[1].toLowerCase().includes("bold")) return true;
20190
+ }
20191
+ return false;
20192
+ },
20193
+ replacement: (content) => content.trim() ? `*${content}*` : content
20194
+ });
20195
+ service.addRule("confluenceItalic", {
20196
+ filter: (node) => {
20197
+ if (node.nodeName === "EM" || node.nodeName === "I") return true;
20198
+ if (node.nodeName === "SPAN") {
20199
+ const style = node.getAttribute("style") || "";
20200
+ if (style.includes("font-style: italic") || style.includes("font-style:italic")) return true;
20201
+ const ffMatch = style.match(/font-family:\s*['"]*([^'"]+)['"]*[;]?/);
20202
+ if (ffMatch && ffMatch[1].toLowerCase().includes("italic")) return true;
20203
+ }
20204
+ return false;
20205
+ },
20206
+ replacement: (content) => content.trim() ? `_${content}_` : content
20207
+ });
20208
+ service.addRule("confluenceUnderline", {
20209
+ filter: (node) => {
20210
+ if (node.nodeName === "U" || node.nodeName === "INS") return true;
20211
+ if (node.nodeName === "SPAN") {
20212
+ const style = node.getAttribute("style") || "";
20213
+ return style.includes("text-decoration: underline") || style.includes("text-decoration:underline");
20214
+ }
20215
+ return false;
20216
+ },
20217
+ replacement: (content) => content.trim() ? `+${content}+` : content
20218
+ });
20219
+ service.addRule("confluenceStrikethrough", {
20220
+ filter: (node) => {
20221
+ if (["DEL", "S", "STRIKE"].includes(node.nodeName)) return true;
20222
+ if (node.nodeName === "SPAN") {
20223
+ const style = node.getAttribute("style") || "";
20224
+ return style.includes("text-decoration: line-through") || style.includes("text-decoration:line-through") || style.includes("text-decoration-line: line-through") || style.includes("text-decoration-line:line-through");
20225
+ }
20226
+ return false;
20227
+ },
20228
+ replacement: (content) => content.trim() ? `-${content}-` : content
20229
+ });
20230
+ service.addRule("confluenceMonospaceCode", {
20231
+ filter: (node) => {
20232
+ if (node.nodeName !== "SPAN") return false;
20233
+ const style = node.getAttribute("style") || "";
20234
+ const fontFamily = style.toLowerCase();
20235
+ return fontFamily.includes("monospace") || fontFamily.includes("courier") || fontFamily.includes("consolas") || fontFamily.includes("monaco") || fontFamily.includes("menlo") || fontFamily.includes("source code") || fontFamily.includes("fira code") || fontFamily.includes("roboto mono") || fontFamily.includes("ubuntu mono");
20236
+ },
20237
+ replacement: (content) => content.trim() ? `{{${content.trim().replace(/}+/g, (m) => m.split("").join(" "))}}}` : content
20238
+ });
20239
+ service.addRule("confluenceLink", {
20240
+ filter: (node) => node.nodeName === "A" && !!node.getAttribute("href"),
20241
+ replacement: (content, node) => {
20242
+ const href = node.getAttribute("href") || "";
20243
+ const text = content.trim().replace(/\|/g, " ");
20244
+ if (!text || text === href) return `[${href}]`;
20245
+ return `[${text}|${href}]`;
20246
+ }
20247
+ });
20248
+ service.addRule("confluenceImage", {
20249
+ filter: "img",
20250
+ replacement: (_content, node) => {
20251
+ const src = node.getAttribute("src") || "";
20252
+ return src ? `!${src}!` : "";
20253
+ }
20254
+ });
20255
+ service.addRule("confluenceHr", {
20256
+ filter: "hr",
20257
+ replacement: () => "\n\n----\n\n"
20258
+ });
20259
+ service.addRule("confluenceBlockquote", {
20260
+ filter: "blockquote",
20261
+ replacement: (content) => `
20262
+
20263
+ {quote}
20264
+ ${content.trim()}
20265
+ {quote}
20266
+
20267
+ `
20268
+ });
20269
+ service.addRule("confluenceGoogleDocsHeadings", {
20270
+ filter: (node) => {
20271
+ if (node.nodeName !== "P" && node.nodeName !== "DIV") return false;
20272
+ const style = node.getAttribute("style") || "";
20273
+ const fontSizeMatch = style.match(/font-size:\s*(\d+(?:\.\d+)?)px/);
20274
+ if (fontSizeMatch && parseFloat(fontSizeMatch[1]) > 16) return true;
20275
+ if (style.includes("font-weight: bold") || style.includes("font-weight:bold")) {
20276
+ const textContent = node.textContent || "";
20277
+ return textContent.length < 100 && textContent.trim().length > 0;
20278
+ }
20279
+ return false;
20280
+ },
20281
+ replacement: (content, node) => {
20282
+ const style = node?.getAttribute("style") || "";
20283
+ const fontSizeMatch = style.match(/font-size:\s*(\d+(?:\.\d+)?)px/);
20284
+ const trimmed = content.trim();
20285
+ if (!trimmed) return content;
20286
+ if (!fontSizeMatch) {
20287
+ return `
20288
+
20289
+ *${trimmed}*
20290
+
20291
+ `;
20292
+ }
20293
+ const fontSize = parseFloat(fontSizeMatch[1]);
20294
+ let level = 5;
20295
+ if (fontSize >= 28) level = 1;
20296
+ else if (fontSize >= 24) level = 2;
20297
+ else if (fontSize >= 20) level = 3;
20298
+ else if (fontSize >= 18) level = 4;
20299
+ return `
20300
+
20301
+ h${level}. ${trimmed}
20302
+
20303
+ `;
20304
+ }
20305
+ });
20306
+ service.addRule("confluenceList", {
20307
+ filter: ["ul", "ol"],
20308
+ replacement: (_content, node) => {
20309
+ return processConfluenceList(node, service, 1);
20310
+ }
20311
+ });
20312
+ service.addRule("confluenceTable", {
20313
+ filter: "table",
20314
+ replacement: (_content, node) => {
20315
+ const table = node;
20316
+ const rows = Array.from(table.querySelectorAll("tr"));
20317
+ if (rows.length === 0) return "";
20318
+ let result = "\n";
20319
+ rows.forEach((row) => {
20320
+ const cells = Array.from(row.querySelectorAll("th, td"));
20321
+ const isHeaderRow = cells.some((c) => c.nodeName === "TH");
20322
+ const delim = isHeaderRow ? "||" : "|";
20323
+ const cellTexts = cells.map((c) => (c.textContent || "").trim());
20324
+ result += delim + cellTexts.join(delim) + delim + "\n";
20325
+ });
20326
+ return result + "\n";
20327
+ }
20328
+ });
20329
+ return service;
20330
+ }
20331
+
19981
20332
  // ../../src/shared/conversion-engine.ts
19982
20333
  var ConversionEngine = class {
19983
20334
  githubService;
@@ -19992,21 +20343,21 @@ var ConversionEngine = class {
19992
20343
  this.initializeServices();
19993
20344
  }
19994
20345
  initializeServices() {
19995
- this.githubService = new import_turndown.default({
20346
+ this.githubService = new import_turndown2.default({
19996
20347
  headingStyle: "atx",
19997
20348
  hr: "---",
19998
20349
  bulletListMarker: "-",
19999
20350
  codeBlockStyle: "fenced",
20000
20351
  emDelimiter: "_"
20001
20352
  });
20002
- this.commonmarkService = new import_turndown.default({
20353
+ this.commonmarkService = new import_turndown2.default({
20003
20354
  headingStyle: "atx",
20004
20355
  hr: "***",
20005
20356
  bulletListMarker: "*",
20006
20357
  codeBlockStyle: "indented",
20007
20358
  emDelimiter: "*"
20008
20359
  });
20009
- this.customService = new import_turndown.default({
20360
+ this.customService = new import_turndown2.default({
20010
20361
  headingStyle: "atx",
20011
20362
  hr: "---",
20012
20363
  bulletListMarker: "-",
@@ -20028,8 +20379,13 @@ var ConversionEngine = class {
20028
20379
  this.discordService = this.createDiscordService();
20029
20380
  this.redditService = this.createRedditService();
20030
20381
  this.rmarkdownService = this.createRMarkdownService();
20031
- this.slackService = this.createSlackService();
20032
- this.confluenceService = this.createConfluenceService();
20382
+ const deps = {
20383
+ addListRules: this.addListRules.bind(this),
20384
+ addPdfPageMarkerRules: this.addPdfPageMarkerRules.bind(this),
20385
+ tableToPlainText: this.tableToPlainText.bind(this)
20386
+ };
20387
+ this.slackService = createSlackService(deps);
20388
+ this.confluenceService = createConfluenceService(deps);
20033
20389
  }
20034
20390
  /**
20035
20391
  * Create Discord-optimized Turndown service
@@ -20039,7 +20395,7 @@ var ConversionEngine = class {
20039
20395
  * - Images converted to links
20040
20396
  */
20041
20397
  createDiscordService() {
20042
- const service = new import_turndown.default({
20398
+ const service = new import_turndown2.default({
20043
20399
  headingStyle: "atx",
20044
20400
  hr: "---",
20045
20401
  bulletListMarker: "-",
@@ -20216,7 +20572,7 @@ ${plainText}
20216
20572
  * - Fenced code blocks (new Reddit)
20217
20573
  */
20218
20574
  createRedditService() {
20219
- const service = new import_turndown.default({
20575
+ const service = new import_turndown2.default({
20220
20576
  headingStyle: "atx",
20221
20577
  hr: "---",
20222
20578
  bulletListMarker: "-",
@@ -20274,7 +20630,7 @@ ${plainText}
20274
20630
  * - Math expression preservation
20275
20631
  */
20276
20632
  createRMarkdownService() {
20277
- const service = new import_turndown.default({
20633
+ const service = new import_turndown2.default({
20278
20634
  headingStyle: "atx",
20279
20635
  hr: "---",
20280
20636
  bulletListMarker: "-",
@@ -20337,377 +20693,6 @@ ${plainText}
20337
20693
  });
20338
20694
  return service;
20339
20695
  }
20340
- /**
20341
- * Create Slack mrkdwn-optimized Turndown service
20342
- * - Bold uses *text* (single asterisk, not **)
20343
- * - Italic uses _text_
20344
- * - Strikethrough uses ~text~ (single tilde)
20345
- * - Links use <url|text> format
20346
- * - Headers converted to bold (Slack has no heading support)
20347
- * - Tables converted to code blocks
20348
- */
20349
- createSlackService() {
20350
- const service = new import_turndown.default({
20351
- headingStyle: "atx",
20352
- hr: "---",
20353
- bulletListMarker: "-",
20354
- codeBlockStyle: "fenced",
20355
- fence: "```",
20356
- emDelimiter: "_"
20357
- });
20358
- this.addListRules(service);
20359
- this.addPdfPageMarkerRules(service);
20360
- service.addRule("slackBold", {
20361
- filter: (node) => {
20362
- if (node.nodeName === "STRONG" || node.nodeName === "B") return true;
20363
- if (node.nodeName === "SPAN") {
20364
- const style = node.getAttribute("style") || "";
20365
- if (style.includes("font-weight: bold") || style.includes("font-weight:bold")) return true;
20366
- const fwMatch = style.match(/font-weight:\s*(\d+)/);
20367
- if (fwMatch && parseInt(fwMatch[1]) >= 600) return true;
20368
- const ffMatch = style.match(/font-family:\s*['"]*([^'"]+)['"]*[;]?/);
20369
- if (ffMatch && ffMatch[1].toLowerCase().includes("bold")) return true;
20370
- }
20371
- return false;
20372
- },
20373
- replacement: (content) => content.trim() ? `*${content}*` : content
20374
- });
20375
- service.addRule("slackItalic", {
20376
- filter: (node) => {
20377
- if (node.nodeName === "EM" || node.nodeName === "I") return true;
20378
- if (node.nodeName === "SPAN") {
20379
- const style = node.getAttribute("style") || "";
20380
- if (style.includes("font-style: italic") || style.includes("font-style:italic")) return true;
20381
- const ffMatch = style.match(/font-family:\s*['"]*([^'"]+)['"]*[;]?/);
20382
- if (ffMatch && ffMatch[1].toLowerCase().includes("italic")) return true;
20383
- }
20384
- return false;
20385
- },
20386
- replacement: (content) => content.trim() ? `_${content}_` : content
20387
- });
20388
- service.addRule("slackStrikethrough", {
20389
- filter: (node) => {
20390
- if (["DEL", "S", "STRIKE"].includes(node.nodeName)) return true;
20391
- if (node.nodeName === "SPAN") {
20392
- const style = node.getAttribute("style") || "";
20393
- return style.includes("text-decoration: line-through") || style.includes("text-decoration:line-through") || style.includes("text-decoration-line: line-through") || style.includes("text-decoration-line:line-through");
20394
- }
20395
- return false;
20396
- },
20397
- replacement: (content) => content.trim() ? `~${content}~` : content
20398
- });
20399
- service.addRule("slackMonospaceCode", {
20400
- filter: (node) => {
20401
- if (node.nodeName !== "SPAN") return false;
20402
- const style = node.getAttribute("style") || "";
20403
- const fontFamily = style.toLowerCase();
20404
- return fontFamily.includes("monospace") || fontFamily.includes("courier") || fontFamily.includes("consolas") || fontFamily.includes("monaco") || fontFamily.includes("menlo") || fontFamily.includes("source code") || fontFamily.includes("fira code") || fontFamily.includes("roboto mono") || fontFamily.includes("ubuntu mono");
20405
- },
20406
- replacement: (content) => {
20407
- const trimmed = content.trim();
20408
- if (!trimmed) return content;
20409
- return trimmed.includes("`") ? `\`\` ${trimmed} \`\`` : `\`${trimmed}\``;
20410
- }
20411
- });
20412
- service.addRule("slackHeading", {
20413
- filter: ["h1", "h2", "h3", "h4", "h5", "h6"],
20414
- replacement: (content) => content.trim() ? `*${content.trim()}*
20415
-
20416
- ` : content
20417
- });
20418
- service.addRule("slackGoogleDocsHeadings", {
20419
- filter: (node) => {
20420
- if (node.nodeName !== "P" && node.nodeName !== "DIV") return false;
20421
- const style = node.getAttribute("style") || "";
20422
- const fontSizeMatch = style.match(/font-size:\s*(\d+(?:\.\d+)?)px/);
20423
- if (fontSizeMatch && parseFloat(fontSizeMatch[1]) > 16) return true;
20424
- if (style.includes("font-weight: bold") || style.includes("font-weight:bold")) {
20425
- const textContent = node.textContent || "";
20426
- return textContent.length < 100 && textContent.trim().length > 0;
20427
- }
20428
- return false;
20429
- },
20430
- replacement: (content) => content.trim() ? `*${content.trim()}*
20431
-
20432
- ` : content
20433
- });
20434
- service.addRule("slackUnderline", {
20435
- filter: (node) => {
20436
- if (node.nodeName === "U" || node.nodeName === "INS") return true;
20437
- if (node.nodeName === "SPAN") {
20438
- const style = node.getAttribute("style") || "";
20439
- return style.includes("text-decoration: underline") || style.includes("text-decoration:underline");
20440
- }
20441
- return false;
20442
- },
20443
- replacement: (content) => content
20444
- });
20445
- service.addRule("slackLink", {
20446
- filter: (node) => node.nodeName === "A" && !!node.getAttribute("href"),
20447
- replacement: (content, node) => {
20448
- const href = node.getAttribute("href") || "";
20449
- const text = content.trim().replace(/\|/g, " ");
20450
- if (!text || text === href) return `<${href}>`;
20451
- return `<${href}|${text}>`;
20452
- }
20453
- });
20454
- service.addRule("slackImage", {
20455
- filter: "img",
20456
- replacement: (_content, node) => {
20457
- const src = node.getAttribute("src") || "";
20458
- const alt = (node.getAttribute("alt") || "image").replace(/\|/g, " ");
20459
- return `<${src}|${alt}>`;
20460
- }
20461
- });
20462
- service.addRule("slackTable", {
20463
- filter: "table",
20464
- replacement: (_content, node) => {
20465
- const plainText = this.tableToPlainText(node);
20466
- return `\`\`\`
20467
- ${plainText}
20468
- \`\`\`
20469
-
20470
- `;
20471
- }
20472
- });
20473
- return service;
20474
- }
20475
- /**
20476
- * Create Confluence wiki markup Turndown service
20477
- * - Headings: h1. h2. etc.
20478
- * - Bold: *text*, Italic: _text_, Underline: +text+, Strikethrough: -text-
20479
- * - Inline code: {{text}}, Code blocks: {code:language=x}...{code}
20480
- * - Links: [text|url], Images: !url!
20481
- * - Tables: ||header|| / |cell| format
20482
- * - Lists: depth-aware * and # notation
20483
- * - Blockquote: {quote}...{quote}
20484
- */
20485
- createConfluenceService() {
20486
- const service = new import_turndown.default({
20487
- headingStyle: "atx",
20488
- hr: "---",
20489
- bulletListMarker: "-",
20490
- codeBlockStyle: "fenced",
20491
- emDelimiter: "_"
20492
- });
20493
- this.addPdfPageMarkerRules(service);
20494
- service.addRule("confluenceHeading", {
20495
- filter: ["h1", "h2", "h3", "h4", "h5", "h6"],
20496
- replacement: (content, node) => {
20497
- const level = parseInt(node.nodeName[1], 10);
20498
- return `
20499
-
20500
- h${level}. ${content.trim()}
20501
-
20502
- `;
20503
- }
20504
- });
20505
- service.addRule("confluenceCodeBlock", {
20506
- filter: "pre",
20507
- replacement: (_content, node) => {
20508
- const codeEl = node.querySelector("code");
20509
- const langMatch = codeEl?.className?.match(/language-(\w+)/);
20510
- const lang = langMatch ? langMatch[1] : "";
20511
- const codeContent = codeEl?.textContent || node.textContent || "";
20512
- const langAttr = lang ? `:language=${lang}` : "";
20513
- return `
20514
-
20515
- {code${langAttr}}
20516
- ${codeContent}
20517
- {code}
20518
-
20519
- `;
20520
- }
20521
- });
20522
- service.addRule("confluenceInlineCode", {
20523
- filter: (node) => node.nodeName === "CODE" && node.parentNode?.nodeName !== "PRE",
20524
- replacement: (content) => content ? `{{${content.replace(/}+/g, (m) => m.split("").join(" "))}}}` : ""
20525
- });
20526
- service.addRule("confluenceBold", {
20527
- filter: (node) => {
20528
- if (node.nodeName === "STRONG" || node.nodeName === "B") return true;
20529
- if (node.nodeName === "SPAN") {
20530
- const style = node.getAttribute("style") || "";
20531
- if (style.includes("font-weight: bold") || style.includes("font-weight:bold")) return true;
20532
- const fwMatch = style.match(/font-weight:\s*(\d+)/);
20533
- if (fwMatch && parseInt(fwMatch[1]) >= 600) return true;
20534
- const ffMatch = style.match(/font-family:\s*['"]*([^'"]+)['"]*[;]?/);
20535
- if (ffMatch && ffMatch[1].toLowerCase().includes("bold")) return true;
20536
- }
20537
- return false;
20538
- },
20539
- replacement: (content) => content.trim() ? `*${content}*` : content
20540
- });
20541
- service.addRule("confluenceItalic", {
20542
- filter: (node) => {
20543
- if (node.nodeName === "EM" || node.nodeName === "I") return true;
20544
- if (node.nodeName === "SPAN") {
20545
- const style = node.getAttribute("style") || "";
20546
- if (style.includes("font-style: italic") || style.includes("font-style:italic")) return true;
20547
- const ffMatch = style.match(/font-family:\s*['"]*([^'"]+)['"]*[;]?/);
20548
- if (ffMatch && ffMatch[1].toLowerCase().includes("italic")) return true;
20549
- }
20550
- return false;
20551
- },
20552
- replacement: (content) => content.trim() ? `_${content}_` : content
20553
- });
20554
- service.addRule("confluenceUnderline", {
20555
- filter: (node) => {
20556
- if (node.nodeName === "U" || node.nodeName === "INS") return true;
20557
- if (node.nodeName === "SPAN") {
20558
- const style = node.getAttribute("style") || "";
20559
- return style.includes("text-decoration: underline") || style.includes("text-decoration:underline");
20560
- }
20561
- return false;
20562
- },
20563
- replacement: (content) => content.trim() ? `+${content}+` : content
20564
- });
20565
- service.addRule("confluenceStrikethrough", {
20566
- filter: (node) => {
20567
- if (["DEL", "S", "STRIKE"].includes(node.nodeName)) return true;
20568
- if (node.nodeName === "SPAN") {
20569
- const style = node.getAttribute("style") || "";
20570
- return style.includes("text-decoration: line-through") || style.includes("text-decoration:line-through") || style.includes("text-decoration-line: line-through") || style.includes("text-decoration-line:line-through");
20571
- }
20572
- return false;
20573
- },
20574
- replacement: (content) => content.trim() ? `-${content}-` : content
20575
- });
20576
- service.addRule("confluenceMonospaceCode", {
20577
- filter: (node) => {
20578
- if (node.nodeName !== "SPAN") return false;
20579
- const style = node.getAttribute("style") || "";
20580
- const fontFamily = style.toLowerCase();
20581
- return fontFamily.includes("monospace") || fontFamily.includes("courier") || fontFamily.includes("consolas") || fontFamily.includes("monaco") || fontFamily.includes("menlo") || fontFamily.includes("source code") || fontFamily.includes("fira code") || fontFamily.includes("roboto mono") || fontFamily.includes("ubuntu mono");
20582
- },
20583
- replacement: (content) => content.trim() ? `{{${content.trim().replace(/}+/g, (m) => m.split("").join(" "))}}}` : content
20584
- });
20585
- service.addRule("confluenceLink", {
20586
- filter: (node) => node.nodeName === "A" && !!node.getAttribute("href"),
20587
- replacement: (content, node) => {
20588
- const href = node.getAttribute("href") || "";
20589
- const text = content.trim().replace(/\|/g, " ");
20590
- if (!text || text === href) return `[${href}]`;
20591
- return `[${text}|${href}]`;
20592
- }
20593
- });
20594
- service.addRule("confluenceImage", {
20595
- filter: "img",
20596
- replacement: (_content, node) => {
20597
- const src = node.getAttribute("src") || "";
20598
- return src ? `!${src}!` : "";
20599
- }
20600
- });
20601
- service.addRule("confluenceHr", {
20602
- filter: "hr",
20603
- replacement: () => "\n\n----\n\n"
20604
- });
20605
- service.addRule("confluenceBlockquote", {
20606
- filter: "blockquote",
20607
- replacement: (content) => `
20608
-
20609
- {quote}
20610
- ${content.trim()}
20611
- {quote}
20612
-
20613
- `
20614
- });
20615
- service.addRule("confluenceGoogleDocsHeadings", {
20616
- filter: (node) => {
20617
- if (node.nodeName !== "P" && node.nodeName !== "DIV") return false;
20618
- const style = node.getAttribute("style") || "";
20619
- const fontSizeMatch = style.match(/font-size:\s*(\d+(?:\.\d+)?)px/);
20620
- if (fontSizeMatch && parseFloat(fontSizeMatch[1]) > 16) return true;
20621
- if (style.includes("font-weight: bold") || style.includes("font-weight:bold")) {
20622
- const textContent = node.textContent || "";
20623
- return textContent.length < 100 && textContent.trim().length > 0;
20624
- }
20625
- return false;
20626
- },
20627
- replacement: (content, node) => {
20628
- const style = node?.getAttribute("style") || "";
20629
- const fontSizeMatch = style.match(/font-size:\s*(\d+(?:\.\d+)?)px/);
20630
- const trimmed = content.trim();
20631
- if (!trimmed) return content;
20632
- if (!fontSizeMatch) {
20633
- return `
20634
-
20635
- *${trimmed}*
20636
-
20637
- `;
20638
- }
20639
- const fontSize = parseFloat(fontSizeMatch[1]);
20640
- let level = 5;
20641
- if (fontSize >= 28) level = 1;
20642
- else if (fontSize >= 24) level = 2;
20643
- else if (fontSize >= 20) level = 3;
20644
- else if (fontSize >= 18) level = 4;
20645
- return `
20646
-
20647
- h${level}. ${trimmed}
20648
-
20649
- `;
20650
- }
20651
- });
20652
- service.addRule("confluenceList", {
20653
- filter: ["ul", "ol"],
20654
- replacement: (_content, node) => {
20655
- return this.processConfluenceList(node, service, 1);
20656
- }
20657
- });
20658
- service.addRule("confluenceTable", {
20659
- filter: "table",
20660
- replacement: (_content, node) => {
20661
- const table = node;
20662
- const rows = Array.from(table.querySelectorAll("tr"));
20663
- if (rows.length === 0) return "";
20664
- let result = "\n";
20665
- rows.forEach((row) => {
20666
- const cells = Array.from(row.querySelectorAll("th, td"));
20667
- const isHeaderRow = cells.some((c) => c.nodeName === "TH");
20668
- const delim = isHeaderRow ? "||" : "|";
20669
- const cellTexts = cells.map((c) => (c.textContent || "").trim());
20670
- result += delim + cellTexts.join(delim) + delim + "\n";
20671
- });
20672
- return result + "\n";
20673
- }
20674
- });
20675
- return service;
20676
- }
20677
- /**
20678
- * Process a Confluence list element into depth-aware wiki markup.
20679
- * Uses * for unordered and # for ordered, repeated for nesting depth.
20680
- */
20681
- processConfluenceList(list, service, depth) {
20682
- const isOrdered = list.nodeName === "OL";
20683
- const marker = (isOrdered ? "#" : "*").repeat(depth);
20684
- const items = Array.from(list.children).filter((c) => c.nodeName === "LI");
20685
- let result = depth === 1 ? "\n" : "";
20686
- items.forEach((item) => {
20687
- const itemContent = this.processConfluenceListItem(item, service, depth);
20688
- result += `${marker} ${itemContent}
20689
- `;
20690
- });
20691
- return depth === 1 ? result + "\n" : result;
20692
- }
20693
- processConfluenceListItem(item, service, depth) {
20694
- let content = "";
20695
- const TEXT_NODE = 3;
20696
- const ELEMENT_NODE = 1;
20697
- Array.from(item.childNodes).forEach((child) => {
20698
- if (child.nodeType === TEXT_NODE) {
20699
- content += child.textContent || "";
20700
- } else if (child.nodeType === ELEMENT_NODE) {
20701
- const el = child;
20702
- if (el.nodeName === "UL" || el.nodeName === "OL") {
20703
- content += "\n" + this.processConfluenceList(el, service, depth + 1);
20704
- } else {
20705
- content += service.turndown(el.outerHTML);
20706
- }
20707
- }
20708
- });
20709
- return content.trim();
20710
- }
20711
20696
  /**
20712
20697
  * Convert HTML table to plain text for Discord code blocks
20713
20698
  */
@@ -21122,34 +21107,52 @@ async function loadLibrary(format) {
21122
21107
  }
21123
21108
  }
21124
21109
  async function importMammoth() {
21125
- return import("mammoth");
21110
+ const mod = await import("mammoth");
21111
+ const modDefault = mod.default;
21112
+ if (mod && !mod.convertToHtml && modDefault && modDefault.convertToHtml) {
21113
+ return mod.default;
21114
+ }
21115
+ return mod;
21126
21116
  }
21127
21117
  async function importXlsx() {
21128
- return import("xlsx/dist/xlsx.mini.min.js");
21118
+ const mod = await import("xlsx");
21119
+ const modDefault = mod.default;
21120
+ if (mod && !mod.read && modDefault && modDefault.read) {
21121
+ return mod.default;
21122
+ }
21123
+ return mod;
21129
21124
  }
21130
21125
  async function importPdfjs() {
21131
- if (typeof process !== "undefined" && typeof process.getBuiltinModule !== "function") {
21132
- let requireWorker;
21133
- try {
21134
- const moduleName = "module";
21135
- const mod = await import(
21136
- /* @vite-ignore */
21137
- moduleName
21138
- );
21139
- if (mod && mod.createRequire) {
21140
- requireWorker = mod.createRequire(import.meta.url);
21141
- }
21142
- } catch {
21143
- }
21144
- process.getBuiltinModule = (name) => {
21126
+ if (typeof process !== "undefined") {
21127
+ const proxyProcess = process;
21128
+ if (typeof proxyProcess.getBuiltinModule !== "function") {
21129
+ let requireWorker;
21145
21130
  try {
21146
- return requireWorker ? requireWorker(name) : void 0;
21131
+ const moduleName = "module";
21132
+ const mod2 = await import(
21133
+ /* @vite-ignore */
21134
+ moduleName
21135
+ );
21136
+ if (mod2 && typeof mod2.createRequire === "function") {
21137
+ requireWorker = mod2.createRequire(import.meta.url);
21138
+ }
21147
21139
  } catch {
21148
- return void 0;
21149
21140
  }
21150
- };
21141
+ proxyProcess.getBuiltinModule = (name) => {
21142
+ try {
21143
+ return requireWorker ? requireWorker(name) : void 0;
21144
+ } catch {
21145
+ return void 0;
21146
+ }
21147
+ };
21148
+ }
21149
+ }
21150
+ const mod = await import("pdfjs-dist/legacy/build/pdf.mjs");
21151
+ const modDefault = mod.default;
21152
+ if (mod && !mod.getDocument && modDefault && modDefault.getDocument) {
21153
+ return mod.default;
21151
21154
  }
21152
- return import("pdfjs-dist/legacy/build/pdf.mjs");
21155
+ return mod;
21153
21156
  }
21154
21157
  async function loadLibraryInternal(format) {
21155
21158
  try {
@@ -23126,8 +23129,8 @@ import { readFileSync as readFileSync4 } from "fs";
23126
23129
  import { resolve, dirname as dirname3 } from "path";
23127
23130
  import { fileURLToPath } from "url";
23128
23131
  var getVersion = () => {
23129
- if ("1.0.6") {
23130
- return "1.0.6";
23132
+ if ("1.0.9") {
23133
+ return "1.0.9";
23131
23134
  }
23132
23135
  try {
23133
23136
  const __dirname = dirname3(fileURLToPath(import.meta.url));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "formatferry",
3
- "version": "1.0.6",
3
+ "version": "1.0.9",
4
4
  "description": "FormatFerry CLI — Convert HTML, DOCX, PDF, XLSX, CSV to Markdown",
5
5
  "keywords": [
6
6
  "markdown",
@@ -22,6 +22,7 @@
22
22
  "node": ">=18"
23
23
  },
24
24
  "scripts": {
25
+ "prepublishOnly": "npm run build",
25
26
  "build": "node build.mjs",
26
27
  "test": "vitest --run"
27
28
  },