aboutsummaryrefslogtreecommitdiffstats
path: root/src/scripts
diff options
context:
space:
mode:
authorArpit Chakladar <arpitchakladar+git@gmail.com>2026-07-13 23:32:26 +0530
committerGitHub <noreply@github.com>2026-07-13 23:32:26 +0530
commitce401d03e75bcbfd1cdfa963954e0807219fb80d (patch)
tree52a813c1705a952312c20c328b00fa845a3453f9 /src/scripts
parent98e7019c61ea0ffe344ef7ce809d38d62254a9fc (diff)
parentdfba7ff6fff7bde33b40a343a2318b35c429b40f (diff)
downloadbanglar-bhumi-utils-ce401d03e75bcbfd1cdfa963954e0807219fb80d.tar.gz
banglar-bhumi-utils-ce401d03e75bcbfd1cdfa963954e0807219fb80d.zip
Merge pull request #6 from arpitchakladar/adding-documentation-comments
Adding documentation comments
Diffstat (limited to 'src/scripts')
-rw-r--r--src/scripts/captcha/application-receipt.ts4
-rw-r--r--src/scripts/captcha/know-your-property.ts4
-rw-r--r--src/scripts/common/login/captcha.ts128
-rw-r--r--src/scripts/common/login/index.ts1
-rw-r--r--src/scripts/common/stop-blocking.ts52
-rw-r--r--src/scripts/functionality/mutation-application.ts56
-rw-r--r--src/scripts/functionality/sheet-map/index.ts160
-rw-r--r--src/scripts/login/index.ts109
-rw-r--r--src/scripts/pages/application-receipt/captcha.ts13
-rw-r--r--src/scripts/pages/application-receipt/index.ts1
-rw-r--r--src/scripts/pages/know-your-property/captcha.ts13
-rw-r--r--src/scripts/pages/know-your-property/index.ts1
-rw-r--r--src/scripts/pages/mutation-application/download-button.ts63
-rw-r--r--src/scripts/pages/mutation-application/index.ts1
-rw-r--r--src/scripts/pages/sheet-map/download-map-pdf-page-content.html (renamed from src/scripts/functionality/sheet-map/download-map-pdf-page-content.html)0
-rw-r--r--src/scripts/pages/sheet-map/index.ts197
-rw-r--r--src/scripts/pages/sheet-map/map-plot-number-label-text-element.html (renamed from src/scripts/functionality/sheet-map/map-plot-number-label-text-element.html)0
-rw-r--r--src/scripts/pages/sheet-map/map-plot-polygon-path-element.html (renamed from src/scripts/functionality/sheet-map/map-plot-polygon-path-element.html)0
-rw-r--r--src/scripts/pages/sheet-map/plot-information-element.html (renamed from src/scripts/functionality/sheet-map/plot-information-element.html)0
-rw-r--r--src/scripts/pages/view-khatian/download-information-pdf-page-content.html (renamed from src/scripts/functionality/view-khatian/download-information-pdf-page-content.html)0
-rw-r--r--src/scripts/pages/view-khatian/index.ts (renamed from src/scripts/functionality/view-khatian/index.ts)47
-rw-r--r--src/scripts/stop-blocking.ts21
22 files changed, 502 insertions, 369 deletions
diff --git a/src/scripts/captcha/application-receipt.ts b/src/scripts/captcha/application-receipt.ts
deleted file mode 100644
index 595195e..0000000
--- a/src/scripts/captcha/application-receipt.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-document.addEventListener("DOMContentLoaded", () => {
- $("#werter > div > form > div:nth-child(4)").hide();
- (window as any).validateForm = () => true;
-});
diff --git a/src/scripts/captcha/know-your-property.ts b/src/scripts/captcha/know-your-property.ts
deleted file mode 100644
index 6e11a3f..0000000
--- a/src/scripts/captcha/know-your-property.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-document.addEventListener("DOMContentLoaded", () => {
- $("#khatianPlotDiv > div:nth-child(6) > div").hide();
- (window as any).validateCaptcha = () => true;
-});
diff --git a/src/scripts/common/login/captcha.ts b/src/scripts/common/login/captcha.ts
new file mode 100644
index 0000000..a63aa24
--- /dev/null
+++ b/src/scripts/common/login/captcha.ts
@@ -0,0 +1,128 @@
+import { observeDOM } from "@/shared/observe-dom";
+import { OCRRequest, OCRResponse } from "@/shared/ocr-request";
+
+/**
+ * Pre-processes a CAPTCHA image on a canvas by removing grayish
+ * background noise while preserving dark pixels, producing a clean
+ * binary image suitable for OCR.
+ *
+ * @param ctx - The 2D rendering context of the canvas.
+ * @param width - Canvas width in pixels.
+ * @param height - Canvas height in pixels.
+ */
+export function prepareCaptcha(ctx: CanvasRenderingContext2D, width: number, height: number): void {
+ const imgData = ctx.getImageData(0, 0, width, height);
+ const data = imgData.data;
+ const radius = 1;
+
+ /** Returns true when all three channels are below 50 (very dark). */
+ function isBlack(r: number, g: number, b: number): boolean {
+ return r < 50 && g < 50 && b < 50;
+ }
+
+ /** Returns true when the colour is a mid-range grey (no strong hue). */
+ function isGrayish(r: number, g: number, b: number): boolean {
+ return Math.abs(r - g) < 15 && Math.abs(g - b) < 15 && r > 100 && r < 200;
+ }
+
+ /**
+ * Checks whether a black pixel exists within `radius` pixels of (x, y).
+ * Used to preserve dark structures when removing grey noise.
+ */
+ function hasNearbyBlack(x: number, y: number): boolean {
+ for (let dx = -radius; dx <= radius; dx++) {
+ for (let dy = -radius; dy <= radius; dy++) {
+ const nx = x + dx;
+ const ny = y + dy;
+ if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
+ const i = (ny * width + nx) * 4;
+ if (isBlack(data[i], data[i + 1], data[i + 2])) return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ for (let y = 0; y < height; y++) {
+ for (let x = 0; x < width; x++) {
+ const i = (y * width + x) * 4;
+ const [r, g, b] = [data[i], data[i + 1], data[i + 2]];
+ if (isGrayish(r, g, b) && !hasNearbyBlack(x, y)) {
+ data[i] = data[i + 1] = data[i + 2] = 255;
+ }
+ }
+ }
+ for (let y = 0; y < height; y++) {
+ for (let x = 0; x < width; x++) {
+ const i = (y * width + x) * 4;
+ const [r, g, b] = [data[i], data[i + 1], data[i + 2]];
+
+ if (isGrayish(r, g, b)) {
+ data[i] = data[i + 1] = data[i + 2] = 0; // make black
+ }
+ }
+ }
+ ctx.putImageData(imgData, 0, 0);
+}
+
+observeDOM(() => {
+ const loginFormElement = document.querySelector<HTMLFormElement>("#loginform");
+
+ if (loginFormElement) {
+ const captchaImg = loginFormElement.querySelector<HTMLImageElement>("#captchaImg");
+ const captchaInput = loginFormElement.querySelector<HTMLInputElement>("#txtInput");
+
+ if (!captchaImg || !captchaInput)
+ return false;
+
+ // Wait for image to load to get correct dimensions
+ captchaImg.addEventListener("load", function(): void {
+ const { width, height } = captchaImg;
+
+ // 1. Create canvas
+ const canvas = document.createElement("canvas");
+ canvas.width = width;
+ canvas.height = height;
+ canvas.style.display = "block";
+
+ // 2. Copy styles (optional)
+ canvas.style.cssText = getComputedStyle(captchaImg).cssText;
+ canvas.style.display = "none";
+
+ // 3. Insert canvas before image
+ captchaImg.parentNode?.querySelectorAll("canvas")
+ .forEach((c) => { c.remove(); });
+ captchaImg.parentNode?.insertBefore(canvas, captchaImg);
+
+ // 4. Hide image
+ captchaImg.style.display = "none";
+
+ // 5. Draw the image onto the canvas
+ const ctx = canvas.getContext("2d");
+ if (!ctx)
+ return;
+ ctx.drawImage(captchaImg, 0, 0);
+ prepareCaptcha(ctx, canvas.width, canvas.height);
+ canvas.style.display = "";
+ const dataURL = canvas.toDataURL("image/png");
+
+ // Perform OCR
+ chrome.runtime.sendMessage<OCRRequest, OCRResponse>(
+ { type: "OCR", dataURL },
+ function(ocrResponse): void {
+ if (ocrResponse.success) {
+ const textResult = ocrResponse.text.trim();
+ if (ocrResponse.confidence < 80 || textResult.length !== 6)
+ // Reset the captcha if confidence is low
+ captchaImg.src = `generateCaptcha?${new Date().getTime().toString()}`;
+ else
+ captchaInput.value = textResult;
+ }
+ }
+ );
+ });
+ }
+
+ // This runs forever
+ return false;
+});
diff --git a/src/scripts/common/login/index.ts b/src/scripts/common/login/index.ts
new file mode 100644
index 0000000..95b9c5b
--- /dev/null
+++ b/src/scripts/common/login/index.ts
@@ -0,0 +1 @@
+import "@/scripts/common/login/captcha";
diff --git a/src/scripts/common/stop-blocking.ts b/src/scripts/common/stop-blocking.ts
new file mode 100644
index 0000000..8588834
--- /dev/null
+++ b/src/scripts/common/stop-blocking.ts
@@ -0,0 +1,52 @@
+/**
+ * Proxies jQuery's `.bind()` (for the `"cut copy paste"` event) and
+ * `.keydown()` so that the website's copy/paste/right-click blocking
+ * is disabled. Reverts the prototypes after patching to avoid
+ * interfering with other code.
+ */
+
+type JQueryBindFn = (
+ eventName: string,
+ handler: (event: JQuery.Event) => unknown
+) => JQuery;
+
+type JQueryKeydownFn = (
+ handler: (event: JQuery.Event) => unknown
+) => JQuery;
+
+type JQueryPrototypeShape = {
+ bind: JQueryBindFn;
+ keydown: JQueryKeydownFn
+};
+
+const jQueryPrototype = $.prototype as unknown as JQueryPrototypeShape;
+
+document.addEventListener("DOMContentLoaded", () => {
+ const proxiedBind = jQueryPrototype.bind;
+ /** Proxies jQuery `.bind()` to no-op the `"cut copy paste"` event. */
+ jQueryPrototype.bind = function(
+ this: JQuery,
+ ...args: Parameters<JQueryBindFn>
+ ): JQuery {
+ const [eventName, handler] = args;
+ const noop = (): undefined => undefined;
+ return proxiedBind.call(
+ this,
+ eventName,
+ eventName.trim() === "cut copy paste" ? noop : handler
+ );
+ };
+
+ const proxiedKeydown = jQueryPrototype.keydown;
+ /** Proxies jQuery `.keydown()` so all key presses are allowed. */
+ jQueryPrototype.keydown = function(
+ this: JQuery,
+ ..._args: Parameters<JQueryKeydownFn>
+ ): JQuery {
+ return proxiedKeydown.call(this, () => true);
+ };
+
+ jQueryPrototype.bind = proxiedBind;
+ jQueryPrototype.keydown = proxiedKeydown;
+ document.oncontextmenu = (): boolean => true;
+});
diff --git a/src/scripts/functionality/mutation-application.ts b/src/scripts/functionality/mutation-application.ts
deleted file mode 100644
index 96aec8a..0000000
--- a/src/scripts/functionality/mutation-application.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-import { PDFDocument, StandardFonts } from "pdf-lib";
-import { observeDOM } from "@/shared/observe-dom";
-
-const sanghaFacilitationCentreBannerUrl = "$l{ /assets/sangha-facilitation-centre-banner.jpg }l$";
-
-document.addEventListener("DOMContentLoaded", () => {
- observeDOM(() => {
- const formElement = $("#form_MutationApplication > div > div:nth-child(14) > div.col-sm-2.btreset > form");
- const submitButtonElement = $("#btnDeclareForm");
- if (formElement.length > 0 && submitButtonElement.length > 0) {
- formElement.removeAttr("action");
- submitButtonElement.click(e => {
- e.preventDefault();
-
- (async () => {
- const pdfDoc = await PDFDocument.load(await fetch("/BanglarBhumi/MutationDeclarationForm.action", {
- method: "post",
- credentials: "same-origin",
- headers: {
- "Content-Type": "application/x-www-form-urlencoded"
- },
- body: "hdnAppNo="
- }).then(res => res.arrayBuffer()));
- const pages = pdfDoc.getPages();
- const lastPage = pages[pages.length - 1];
- const { width, height } = lastPage.getSize();
- const bannerImage = await pdfDoc.embedJpg(await fetch(sanghaFacilitationCentreBannerUrl).then(res => res.arrayBuffer()));
- const bannerImageHeight = width * (bannerImage.height / bannerImage.width);
-
- lastPage.drawImage(bannerImage, {
- x: 0,
- y: 0,
- width: width,
- height: bannerImageHeight
- });
-
- lastPage.drawText(pdfDoc.getCreationDate()!.toLocaleString(), {
- x: 10,
- y: bannerImageHeight,
- size: 12,
- font: await pdfDoc.embedFont(StandardFonts.TimesRoman)
- });
-
- const link = document.createElement("a");
- link.href = window.URL.createObjectURL(new Blob([new Uint8Array(await pdfDoc.save())], {type: "application/pdf"}));
- link.download = "Declaration.pdf";
- link.click();
- })();
- });
-
- return true;
- } else {
- return false;
- }
- });
-});
diff --git a/src/scripts/functionality/sheet-map/index.ts b/src/scripts/functionality/sheet-map/index.ts
deleted file mode 100644
index 71228fe..0000000
--- a/src/scripts/functionality/sheet-map/index.ts
+++ /dev/null
@@ -1,160 +0,0 @@
-import { generateWebPage } from "@/shared/generate-web-page";
-import getDownloadMapPDFPageContent from "@/scripts/functionality/sheet-map/download-map-pdf-page-content.html";
-import getMapPlotPolygonPathElement from "@/scripts/functionality/sheet-map/map-plot-polygon-path-element.html";
-import getMapPlotNumberLabelTextElement from "@/scripts/functionality/sheet-map/map-plot-number-label-text-element.html";
-import getPlotInformationElement from "@/scripts/functionality/sheet-map/plot-information-element.html";
-
-interface PlotPolygon {
- plotArea: number;
- plotno: string;
- polygon: string;
-};
-
-interface PlotNumberLabel {
- plotno: string;
- point: string;
-}
-
-const headerElement = document.querySelector("#headerTable > tbody > tr")!;
-const plotNumberInput = document.querySelector<HTMLInputElement>("#txtPlotNo")!;
-const plotInformation = document.createElement("div") as HTMLDivElement;
-let plotPolygonPathElements = "";
-let plotNumberLabelTextElements = "";
-
-document.body.appendChild(plotInformation);
-
-const createHeaderButton = (text: string) => {
- const buttonContainer = document.createElement("td");
- const button = document.createElement("button");
- button.innerHTML = text;
- button.style.display = "none";
- buttonContainer.appendChild(button);
- headerElement.appendChild(buttonContainer);
- return button;
-};
-
-const setPlotInformation = (plotPolygon: PlotPolygon | null | undefined = null) => {
- plotInformation.innerHTML = plotPolygon ? getPlotInformationElement({
- area: (plotPolygon.plotArea/1000).toFixed(3),
- plotNumber: plotPolygon.plotno
- }) : getPlotInformationElement({
- area: "0.000",
- plotNumber: ""
- });
-};
-
-const downloadPDF = (labelPoints: boolean = true) => {
- const _getMapDetail = (i: number) => {
- const detail = document.querySelector(`#headerTable > tbody > tr > td:nth-child(${i})`)!
- .innerHTML
- .split("[")[1]
- .trim();
- return detail.substr(0, detail.length - 1);
- };
- generateWebPage(
- getDownloadMapPDFPageContent({
- district: _getMapDetail(1),
- block: _getMapDetail(2),
- mouza: _getMapDetail(3),
- mapContent: plotPolygonPathElements + (labelPoints ? plotNumberLabelTextElements : "")
- }),
- "map"
- );
-};
-
-const downloadPDFButton = createHeaderButton("SAVE PDF");
-const downloadPDFNoLabelButton = createHeaderButton("SAVE PDF (NO LABEL)");
-
-downloadPDFButton.addEventListener("click", e => {
- e.preventDefault();
- downloadPDF();
-});
-
-downloadPDFNoLabelButton.addEventListener("click", e => {
- e.preventDefault();
- downloadPDF(false);
-});
-
-setPlotInformation();
-
-(async () => {
- const searchParams = new URLSearchParams(window.location.search);
- const response1 = await fetch(
- "/BanglarBhumi/sheetMap_populateLayerData?lstSheetNo=" + searchParams.get("lstSheetNo"),
- {
- method: "POST"
- });
- const plotPolygonList: PlotPolygon[] = (await response1.json()).features;
- const response2 = await fetch(
- "/BanglarBhumi/sheetMap_populateCentroidData?lstSheetNo=" + searchParams.get("lstSheetNo"),
- {
- method: "POST"
- });
- const plotNumberLabelList: PlotNumberLabel[] = (await response2.json()).features;
- let plotPolygons: { [key: string]: PlotPolygon } = {};
-
- for (const plotPolygon of plotPolygonList) {
- const plotPolygonPoints = plotPolygon.polygon.split("(((")[1].split(")))")[0];
- plotPolygonPathElements += getMapPlotPolygonPathElement({
- polygonVertices: plotPolygonPoints
- });
- }
-
- for (const plotNumberLabel of plotNumberLabelList) {
- const plotNumberLabelPoint = plotNumberLabel.point.split("(")[1].split(")")[0].split(" ");
- plotNumberLabelTextElements += getMapPlotNumberLabelTextElement({
- x: plotNumberLabelPoint[0],
- y: plotNumberLabelPoint[1],
- plotNumber: plotNumberLabel.plotno
- });
- }
-
- downloadPDFButton.style.display = "inherit";
- downloadPDFNoLabelButton.style.display = "inherit";
-
- document.querySelector("#btnSearch")!.addEventListener("click", () => {
- let plotPolygon;
- for (const p of plotPolygonList) {
- if (p.plotno === plotNumberInput.value) {
- plotPolygon = p;
- break;
- }
- }
- setPlotInformation(plotPolygon);
- });
-
- await new Promise<void>((resolve, reject) => {
- const plotElementIdsControl = setInterval(() => {
- let i = 0;
- let plotElements = document.querySelector("#OpenLayers_Layer_Vector_25_vroot")?.children || [];
- if (plotElements.length) {
- clearInterval(plotElementIdsControl);
- for (let i = 0; i < plotElements.length; i++) {
- plotPolygons[plotElements[i].id] = plotPolygonList[i];
- }
- resolve();
- }
- }, 50);
- });
-
- setInterval(() => {
- for (const plotElementId in plotPolygons) {
- const e = document.getElementById(plotElementId);
- if (e) {
- e.removeEventListener("click", handlePlotClick);
- e.addEventListener("click", handlePlotClick);
- }
- }
- }, 300);
-
- const handlePlotClick = (e: MouseEvent) => {
- document.querySelectorAll("path").forEach(e => {
- e.setAttribute("fill", "#ffcc66");
- });
-
- const target = e.target as SVGPathElement;
-
- target.setAttribute("fill", "#8aeeef");
- setPlotInformation(plotPolygons[target.id]);
- };
-})();
diff --git a/src/scripts/login/index.ts b/src/scripts/login/index.ts
deleted file mode 100644
index bed05f9..0000000
--- a/src/scripts/login/index.ts
+++ /dev/null
@@ -1,109 +0,0 @@
-import { interceptPost, interceptGet } from "@/shared/intercept-jquery-ajax";
-
-function prepareCaptcha(ctx: CanvasRenderingContext2D, width: number, height: number) {
- const imgData = ctx.getImageData(0, 0, width, height);
- const data = imgData.data;
- const radius = 1;
-
- function isBlack(r: number, g: number, b: number) {
- return r < 50 && g < 50 && b < 50;
- }
-
- function isGrayish(r: number, g: number, b: number) {
- return Math.abs(r - g) < 15 && Math.abs(g - b) < 15 && r > 100 && r < 200;
- }
-
- function hasNearbyBlack(x: number, y: number) {
- for (let dx = -radius; dx <= radius; dx++) {
- for (let dy = -radius; dy <= radius; dy++) {
- const nx = x + dx;
- const ny = y + dy;
- if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
- const i = (ny * width + nx) * 4;
- if (isBlack(data[i], data[i + 1], data[i + 2])) return true;
- }
- }
- }
- return false;
- }
-
- for (let y = 0; y < height; y++) {
- for (let x = 0; x < width; x++) {
- const i = (y * width + x) * 4;
- const [r, g, b] = [data[i], data[i + 1], data[i + 2]];
- if (isGrayish(r, g, b) && !hasNearbyBlack(x, y)) {
- data[i] = data[i + 1] = data[i + 2] = 255;
- }
- }
- }
- for (let y = 0; y < height; y++) {
- for (let x = 0; x < width; x++) {
- const i = (y * width + x) * 4;
- const [r, g, b] = [data[i], data[i + 1], data[i + 2]];
-
- if (isGrayish(r, g, b)) {
- data[i] = data[i + 1] = data[i + 2] = 0; // make black
- }
- }
- }
- ctx.putImageData(imgData, 0, 0);
-}
-
-const observer = new MutationObserver((mutationsList, obs) => {
- for (const mutation of mutationsList) {
- for (const node of mutation.addedNodes) {
- if ((node as any).nodeType === 1 && (node as any).id === "loginform") {
- const img = (node as HTMLFormElement).querySelector("#captchaImg") as HTMLImageElement;
- const captchaInput = (node as HTMLFormElement).querySelector("#txtInput") as HTMLInputElement;
-
- // Wait for image to load to get correct dimensions
- img.addEventListener("load", async () => {
- const { width, height } = img;
-
- // 1. Create canvas
- const canvas = document.createElement("canvas");
- canvas.width = width;
- canvas.height = height;
- canvas.style.display = "block";
-
- // 2. Copy styles (optional)
- canvas.style.cssText = getComputedStyle(img).cssText;
- canvas.style.display = "none";
-
- // 3. Insert canvas before image
- img.parentNode!.querySelectorAll("canvas").forEach(c => c.remove());
- img.parentNode!.insertBefore(canvas, img);
-
- // 4. Hide image
- img.style.display = "none";
-
- // 5. Draw the image onto the canvas
- const ctx = canvas.getContext("2d")!;
- ctx.drawImage(img, 0, 0);
- prepareCaptcha(ctx, canvas.width, canvas.height);
- canvas.style.display = "";
- const dataURL = canvas.toDataURL("image/png");
-
- // Perform OCR
- chrome.runtime.sendMessage(
- { type: "OCR", dataURL },
- ({ success, text, confidence }) => {
- if (success) {
- const textResult = text.trim();
- if (confidence < 80 || textResult.length !== 6) {
- // Reset the captcha if confidence is low
- img.src = "generateCaptcha?" + new Date().getTime();
- } else {
- captchaInput.value = textResult;
- }
- }
- },
- );
- });
- }
- }
- }
-});
-
-// Start observing the body
-observer.observe(document.body, { childList: true, subtree: true });
diff --git a/src/scripts/pages/application-receipt/captcha.ts b/src/scripts/pages/application-receipt/captcha.ts
new file mode 100644
index 0000000..94e6b0e
--- /dev/null
+++ b/src/scripts/pages/application-receipt/captcha.ts
@@ -0,0 +1,13 @@
+/**
+ * Hides the CAPTCHA section on the application-receipt page and
+ * stubs the form validation so the user can proceed without
+ * solving a CAPTCHA.
+ */
+type WindowApplicationReceipt = {
+ validateForm?: () => boolean
+} & Window;
+
+document.addEventListener("DOMContentLoaded", () => {
+ $("#werter > div > form > div:nth-child(4)").hide();
+ (window as WindowApplicationReceipt).validateForm = () => true;
+});
diff --git a/src/scripts/pages/application-receipt/index.ts b/src/scripts/pages/application-receipt/index.ts
new file mode 100644
index 0000000..5a8d0a6
--- /dev/null
+++ b/src/scripts/pages/application-receipt/index.ts
@@ -0,0 +1 @@
+import "@/scripts/pages/application-receipt/captcha";
diff --git a/src/scripts/pages/know-your-property/captcha.ts b/src/scripts/pages/know-your-property/captcha.ts
new file mode 100644
index 0000000..e304342
--- /dev/null
+++ b/src/scripts/pages/know-your-property/captcha.ts
@@ -0,0 +1,13 @@
+/**
+ * Hides the CAPTCHA section on the Know-Your-Property page and
+ * stubs the captcha validation so the user can proceed without
+ * solving a CAPTCHA.
+ */
+type WindowKnowYourProperty = {
+ validateCaptcha?: () => boolean
+} & Window;
+
+document.addEventListener("DOMContentLoaded", () => {
+ $("#khatianPlotDiv > div:nth-child(6) > div").hide();
+ (window as WindowKnowYourProperty).validateCaptcha = () => true;
+});
diff --git a/src/scripts/pages/know-your-property/index.ts b/src/scripts/pages/know-your-property/index.ts
new file mode 100644
index 0000000..d4d7f63
--- /dev/null
+++ b/src/scripts/pages/know-your-property/index.ts
@@ -0,0 +1 @@
+import "@/scripts/pages/know-your-property/captcha";
diff --git a/src/scripts/pages/mutation-application/download-button.ts b/src/scripts/pages/mutation-application/download-button.ts
new file mode 100644
index 0000000..6463079
--- /dev/null
+++ b/src/scripts/pages/mutation-application/download-button.ts
@@ -0,0 +1,63 @@
+import { PDFDocument, StandardFonts } from "pdf-lib";
+
+import { observeDOM } from "@/shared/observe-dom";
+
+const sanghaFacilitationCentreBannerUrl = "$l{ /assets/sangha-facilitation-centre-banner.jpg }l$";
+
+/**
+ * On the Mutation Application form, removes the form action (to prevent
+ * the default server-side submit) and attaches a custom click handler
+ * that fetches the declaration PDF, overlays a banner image and date
+ * stamp on the last page, then triggers a download.
+ */
+observeDOM(() => {
+ const formElement = $("#form_MutationApplication > div > div:nth-child(14) > div.col-sm-2.btreset > form");
+ const submitButtonElement = $("#btnDeclareForm");
+ if (formElement.length > 0 && submitButtonElement.length > 0) {
+ formElement.removeAttr("action");
+ submitButtonElement.on("click", (submitButtonClickEvent) => {
+ submitButtonClickEvent.preventDefault();
+
+ void (async function(): Promise<void> {
+ const pdfDoc = await PDFDocument.load(await fetch("/BanglarBhumi/MutationDeclarationForm.action", {
+ method: "post",
+ credentials: "same-origin",
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded"
+ },
+ body: "hdnAppNo="
+ }).then((res) => res.arrayBuffer()));
+ const pages = pdfDoc.getPages();
+ const lastPage = pages[pages.length - 1];
+ const { width } = lastPage.getSize();
+ const bannerImage
+ = await pdfDoc.embedJpg(await fetch(sanghaFacilitationCentreBannerUrl)
+ .then((res) => res.arrayBuffer()));
+ const bannerImageHeight = width * (bannerImage.height / bannerImage.width);
+
+ lastPage.drawImage(bannerImage, {
+ x: 0,
+ y: 0,
+ width: width,
+ height: bannerImageHeight
+ });
+
+ lastPage.drawText(pdfDoc.getCreationDate()?.toLocaleString() ?? "", {
+ x: 10,
+ y: bannerImageHeight,
+ size: 12,
+ font: await pdfDoc.embedFont(StandardFonts.TimesRoman)
+ });
+
+ const link = document.createElement("a");
+ link.href = window.URL.createObjectURL(new Blob([new Uint8Array(await pdfDoc.save())], { type: "application/pdf" }));
+ link.download = "Declaration.pdf";
+ link.click();
+ })();
+ });
+
+ return true;
+ } else {
+ return false;
+ }
+});
diff --git a/src/scripts/pages/mutation-application/index.ts b/src/scripts/pages/mutation-application/index.ts
new file mode 100644
index 0000000..93e07ad
--- /dev/null
+++ b/src/scripts/pages/mutation-application/index.ts
@@ -0,0 +1 @@
+import "@/scripts/pages/mutation-application/download-button";
diff --git a/src/scripts/functionality/sheet-map/download-map-pdf-page-content.html b/src/scripts/pages/sheet-map/download-map-pdf-page-content.html
index da765ec..da765ec 100644
--- a/src/scripts/functionality/sheet-map/download-map-pdf-page-content.html
+++ b/src/scripts/pages/sheet-map/download-map-pdf-page-content.html
diff --git a/src/scripts/pages/sheet-map/index.ts b/src/scripts/pages/sheet-map/index.ts
new file mode 100644
index 0000000..c16a2ae
--- /dev/null
+++ b/src/scripts/pages/sheet-map/index.ts
@@ -0,0 +1,197 @@
+import getDownloadMapPDFPageContent from "@/scripts/pages/sheet-map/download-map-pdf-page-content.html";
+import getMapPlotNumberLabelTextElement from "@/scripts/pages/sheet-map/map-plot-number-label-text-element.html";
+import getMapPlotPolygonPathElement from "@/scripts/pages/sheet-map/map-plot-polygon-path-element.html";
+import getPlotInformationElement from "@/scripts/pages/sheet-map/plot-information-element.html";
+import { generateWebPage } from "@/shared/generate-web-page";
+
+type PlotPolygon = {
+ plotArea: number;
+ plotno: string;
+ polygon: string
+};
+
+type PlotNumberLabel = {
+ plotno: string;
+ point: string
+};
+
+const headerElement = document.querySelector<HTMLTableRowElement>("#headerTable > tbody > tr");
+const plotNumberInput = document.querySelector<HTMLInputElement>("#txtPlotNo");
+const plotInformation = document.createElement("div");
+let plotPolygonPathElements = "";
+let plotNumberLabelTextElements = "";
+
+document.body.appendChild(plotInformation);
+
+/**
+ * Creates a header toolbar button and appends it to the page header.
+ * The button starts hidden and is shown when data is ready.
+ *
+ * @param text - The button label text.
+ * @returns The created button element.
+ */
+function createHeaderButton(text: string): HTMLButtonElement {
+ const buttonContainer = document.createElement("td");
+ const button = document.createElement("button");
+ button.innerHTML = text;
+ button.style.display = "none";
+ buttonContainer.appendChild(button);
+ if (headerElement)
+ headerElement.appendChild(buttonContainer);
+ return button;
+};
+
+/**
+ * Displays the area and plot number for the given polygon in a floating
+ * info panel, or shows "0.000" / empty if no polygon is selected.
+ *
+ * @param plotPolygon - The selected plot's data, or `null`/`undefined` to clear.
+ */
+function setPlotInformation(plotPolygon: PlotPolygon | null | undefined = null): void {
+ plotInformation.innerHTML = plotPolygon
+ ? getPlotInformationElement({
+ area: (plotPolygon.plotArea / 1000).toFixed(3),
+ plotNumber: plotPolygon.plotno
+ })
+ : getPlotInformationElement({
+ area: "0.000",
+ plotNumber: ""
+ });
+};
+
+/**
+ * Opens a new window with a printable PDF view of the map, optionally
+ * including plot-number labels.
+ *
+ * @param labelPoints - Whether to include plot number text labels.
+ */
+function downloadPDF(labelPoints = true): void {
+ /** Extracts a detail value (district/block/mouza) from the header table by column index. */
+ function _getMapDetail(i: number): string {
+ const detail = (document.querySelector<HTMLTableCellElement>(`#headerTable > tbody > tr > td:nth-child(${i.toString()})`)?.innerHTML ?? "")
+ .split("[")[1]
+ .trim();
+ return detail.substring(0, detail.length - 1);
+ };
+ generateWebPage(
+ getDownloadMapPDFPageContent({
+ district: _getMapDetail(1),
+ block: _getMapDetail(2),
+ mouza: _getMapDetail(3),
+ mapContent: plotPolygonPathElements + (labelPoints ? plotNumberLabelTextElements : "")
+ }),
+ "map"
+ );
+};
+
+const downloadPDFButton = createHeaderButton("SAVE PDF");
+const downloadPDFNoLabelButton = createHeaderButton("SAVE PDF (NO LABEL)");
+
+downloadPDFButton.addEventListener("click", (e) => {
+ e.preventDefault();
+ downloadPDF();
+});
+
+downloadPDFNoLabelButton.addEventListener("click", (e) => {
+ e.preventDefault();
+ downloadPDF(false);
+});
+
+setPlotInformation();
+
+void (async function(): Promise<void> {
+ const searchParams = new URLSearchParams(window.location.search);
+ const sheetMapLayerDataResponse = await fetch(
+ `/BanglarBhumi/sheetMap_populateLayerData?lstSheetNo=${searchParams.get("lstSheetNo")?.toString() ?? ""}`,
+ {
+ method: "POST"
+ });
+ type SheetMapLayerDataResponseType = {
+ features: PlotPolygon[]
+ };
+ const sheetMapLayerDataResponseJson
+ = await sheetMapLayerDataResponse.json() as SheetMapLayerDataResponseType;
+ const plotPolygonList: PlotPolygon[] = sheetMapLayerDataResponseJson.features;
+ const sheetMapCentroidDataResponse = await fetch(
+ `/BanglarBhumi/sheetMap_populateCentroidData?lstSheetNo=${searchParams.get("lstSheetNo")?.toString() ?? ""}`,
+ {
+ method: "POST"
+ });
+ type SheetMapCentroidDataResponseType = {
+ features: PlotNumberLabel[]
+ };
+ const sheetMapCentroidDataResponseJson
+ = await sheetMapCentroidDataResponse.json() as SheetMapCentroidDataResponseType;
+ const plotNumberLabelList: PlotNumberLabel[] = sheetMapCentroidDataResponseJson.features;
+ const plotPolygons: Record<string, PlotPolygon> = {};
+
+ for (const plotPolygon of plotPolygonList) {
+ const plotPolygonPoints = plotPolygon.polygon.split("(((")[1].split(")))")[0];
+ plotPolygonPathElements += getMapPlotPolygonPathElement({
+ polygonVertices: plotPolygonPoints
+ });
+ }
+
+ for (const plotNumberLabel of plotNumberLabelList) {
+ const plotNumberLabelPoint = plotNumberLabel.point.split("(")[1].split(")")[0].split(" ");
+ plotNumberLabelTextElements += getMapPlotNumberLabelTextElement({
+ x: plotNumberLabelPoint[0],
+ y: plotNumberLabelPoint[1],
+ plotNumber: plotNumberLabel.plotno
+ });
+ }
+
+ downloadPDFButton.style.display = "inherit";
+ downloadPDFNoLabelButton.style.display = "inherit";
+
+ document.querySelector<HTMLButtonElement>("#btnSearch")?.addEventListener("click", () => {
+ let plotPolygon;
+ for (const p of plotPolygonList) {
+ if (p.plotno === plotNumberInput?.value) {
+ plotPolygon = p;
+ break;
+ }
+ }
+ setPlotInformation(plotPolygon);
+ });
+
+ await new Promise<void>((resolve, _reject) => {
+ const plotElementIdsControl = setInterval(() => {
+ const plotElements = document.querySelector("#OpenLayers_Layer_Vector_25_vroot")?.children ?? [];
+ if (plotElements.length) {
+ clearInterval(plotElementIdsControl);
+ for (let i = 0; i < plotElements.length; i++) {
+ plotPolygons[plotElements[i].id] = plotPolygonList[i];
+ }
+ resolve();
+ }
+ }, 50);
+ });
+
+ setInterval(() => {
+ for (const plotElementId in plotPolygons) {
+ const e = document.getElementById(plotElementId);
+ if (e) {
+ e.removeEventListener("click", handlePlotClick);
+ e.addEventListener("click", handlePlotClick);
+ }
+ }
+ }, 300);
+
+ /**
+ * Highlights the clicked plot on the SVG map and shows its
+ * information in the info panel.
+ *
+ * @param e - The mouse click event.
+ */
+ function handlePlotClick(e: MouseEvent): void {
+ document.querySelectorAll("path").forEach((e) => {
+ e.setAttribute("fill", "#ffcc66");
+ });
+
+ const target = e.target as SVGPathElement;
+
+ target.setAttribute("fill", "#8aeeef");
+ setPlotInformation(plotPolygons[target.id]);
+ };
+})();
diff --git a/src/scripts/functionality/sheet-map/map-plot-number-label-text-element.html b/src/scripts/pages/sheet-map/map-plot-number-label-text-element.html
index d220367..d220367 100644
--- a/src/scripts/functionality/sheet-map/map-plot-number-label-text-element.html
+++ b/src/scripts/pages/sheet-map/map-plot-number-label-text-element.html
diff --git a/src/scripts/functionality/sheet-map/map-plot-polygon-path-element.html b/src/scripts/pages/sheet-map/map-plot-polygon-path-element.html
index d4d38a6..d4d38a6 100644
--- a/src/scripts/functionality/sheet-map/map-plot-polygon-path-element.html
+++ b/src/scripts/pages/sheet-map/map-plot-polygon-path-element.html
diff --git a/src/scripts/functionality/sheet-map/plot-information-element.html b/src/scripts/pages/sheet-map/plot-information-element.html
index fd740a8..fd740a8 100644
--- a/src/scripts/functionality/sheet-map/plot-information-element.html
+++ b/src/scripts/pages/sheet-map/plot-information-element.html
diff --git a/src/scripts/functionality/view-khatian/download-information-pdf-page-content.html b/src/scripts/pages/view-khatian/download-information-pdf-page-content.html
index 129608e..129608e 100644
--- a/src/scripts/functionality/view-khatian/download-information-pdf-page-content.html
+++ b/src/scripts/pages/view-khatian/download-information-pdf-page-content.html
diff --git a/src/scripts/functionality/view-khatian/index.ts b/src/scripts/pages/view-khatian/index.ts
index 93d03cf..bb44a38 100644
--- a/src/scripts/functionality/view-khatian/index.ts
+++ b/src/scripts/pages/view-khatian/index.ts
@@ -1,6 +1,6 @@
+import getDownloadInformationPDFPageContent from "@/scripts/pages/view-khatian/download-information-pdf-page-content.html";
import { generateWebPage } from "@/shared/generate-web-page";
-import { interceptPost } from "@/shared/intercept-jquery-ajax";
-import getDownloadInformationPDFPageContent from "@/scripts/functionality/view-khatian/download-information-pdf-page-content.html";
+import { addPostInterceptor, JQueryAjaxArgs } from "@/shared/intercept-jquery-ajax";
const submitButtonElement = $("#khbutton");
const separatorElement = $("#bodycover > div > form > hr:nth-child(2)");
@@ -14,18 +14,28 @@ const styles = Array.from(submitButtonElementComputedStyles)
""
);
-const getValueOfSelectElement = (selector: string) => {
- const element = document.querySelector<HTMLSelectElement>(selector)!;
- return element.options[element.selectedIndex].text;
+/**
+ * Reads the currently selected text from a `<select>` element.
+ *
+ * @param selector - CSS selector for the select element.
+ * @returns The text of the selected option.
+ */
+function getValueOfSelectElement(selector: string): string {
+ const element = document.querySelector<HTMLSelectElement>(selector);
+ return element?.options[element.selectedIndex].text ?? "";
};
let isPlotInformation: boolean | null = null;
-const downloadInformationPDF = () => {
+/**
+ * Opens a new window with a printable PDF view of the current khatian
+ * or plot details, including district/block/mouza info.
+ */
+function downloadInformationPDF(): void {
generateWebPage(
getDownloadInformationPDFPageContent({
isPlotInformation: !!isPlotInformation,
- details: $(isPlotInformation ? "#plotdetails" : "#khdetails").html(),
+ details: $(isPlotInformation ? "#plotdetails" : "#khdetails").html() || "",
district: getValueOfSelectElement("#lstDistrictCode1"),
block: getValueOfSelectElement("#lstBlockCode1"),
mouza: getValueOfSelectElement("#lstMouzaList")
@@ -36,11 +46,18 @@ const downloadInformationPDF = () => {
);
};
-const showDownloadButton = (args: any) => {
+/**
+ * Intercepts the success callback of a jQuery AJAX call. When a valid
+ * details table is returned, it inserts a "Download PDF" button;
+ * otherwise it removes an existing button.
+ *
+ * @param args - The `arguments` object from the intercepted `$.post` call.
+ */
+function showDownloadButton(args: JQueryAjaxArgs): void {
if (isPlotInformation !== null) {
const callback = args[2];
- args[2] = (data: any) => {
- callback(data);
+ args[2] = (...data) => {
+ callback(...data);
const tableElement = $(isPlotInformation
? "#plotdetails > div:nth-child(1) > div:nth-child(4) > table > tbody"
: "#khdetails > table > tbody > tr > td:nth-child(1) > div:nth-child(5) > table > tbody"
@@ -56,23 +73,23 @@ const showDownloadButton = (args: any) => {
downloadPDFButton[0].style.cssText = styles;
downloadPDFButton.css("width", "auto");
downloadPDFButton.css("margin-bottom", "1rem");
- downloadPDFButton.click(downloadInformationPDF);
+ downloadPDFButton.on("click", downloadInformationPDF);
formElement[0].insertBefore(downloadPDFButton[0], separatorElement[0]);
}
} else if (downloadPDFButton) {
- ($(downloadPDFButton as any) as any).remove();
+ ($(downloadPDFButton)).remove();
isPlotInformation = null;
downloadPDFButton = null;
}
- }
+ };
}
};
-interceptPost("plotDetailsAction_LandInfo.action", args => {
+addPostInterceptor("plotDetailsAction_LandInfo.action", (args: JQueryAjaxArgs) => {
isPlotInformation = true;
showDownloadButton(args);
});
-interceptPost("khDetailsAction_LandInfo.action", args => {
+addPostInterceptor("khDetailsAction_LandInfo.action", (args: JQueryAjaxArgs) => {
isPlotInformation = false;
showDownloadButton(args);
});
diff --git a/src/scripts/stop-blocking.ts b/src/scripts/stop-blocking.ts
deleted file mode 100644
index b8f4783..0000000
--- a/src/scripts/stop-blocking.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-document.addEventListener("DOMContentLoaded", () => {
- const proxiedBind = $.prototype.bind;
- $.prototype.bind = function() {
- if (arguments[0].trim() === "cut copy paste") {
- arguments[1] = (_: any) => {};
- }
-
- return proxiedBind.apply(this, Array.from(arguments) as any);
- }
-
- const proxiedKeydown = $.prototype.keydown;
- $.prototype.keydown = function() {
- arguments[0] = (_: any) => true;
-
- return proxiedKeydown.apply(this, Array.from(arguments) as any);
- }
-
- $.prototype.bind = proxiedBind;
- $.prototype.keydown = proxiedKeydown;
- document.oncontextmenu = () => true;
-});