2024-08-07 19:27:25 +08:00
|
|
|
/**
|
|
|
|
* Represents a reference to a page, with optional position, anchor and header.
|
|
|
|
*/
|
|
|
|
export type PageRef = {
|
|
|
|
page: string;
|
|
|
|
pos?: number | { line: number; column: number };
|
|
|
|
anchor?: string;
|
|
|
|
header?: string;
|
|
|
|
meta?: boolean;
|
|
|
|
};
|
|
|
|
|
2024-07-13 19:51:49 +08:00
|
|
|
/**
|
2024-07-23 23:23:30 +08:00
|
|
|
* Checks if a name looks like a full path (with a file extension), is not a conflicted file and not a search page.
|
2024-07-13 02:38:45 +08:00
|
|
|
*/
|
2024-07-13 19:51:49 +08:00
|
|
|
export function looksLikePathWithExtension(name: string): boolean {
|
2024-07-23 23:23:30 +08:00
|
|
|
return /\.[a-zA-Z0-9]+$/.test(name) && !/\.conflicted\./.test(name) &&
|
|
|
|
!name.startsWith("🔍 ");
|
2024-07-13 02:38:45 +08:00
|
|
|
}
|
|
|
|
|
2024-08-07 19:27:25 +08:00
|
|
|
/**
|
|
|
|
* Checks if a name looks like a full path (with a file extension), is not a conflicted file and not a search page.
|
|
|
|
*/
|
2023-07-28 21:20:56 +08:00
|
|
|
export function validatePageName(name: string) {
|
2023-07-06 22:47:50 +08:00
|
|
|
// Page can not be empty and not end with a file extension (e.g. "bla.md")
|
2023-07-28 21:20:56 +08:00
|
|
|
if (name === "") {
|
|
|
|
throw new Error("Page name can not be empty");
|
|
|
|
}
|
|
|
|
if (name.startsWith(".")) {
|
|
|
|
throw new Error("Page name cannot start with a '.'");
|
|
|
|
}
|
2024-07-13 19:51:49 +08:00
|
|
|
if (looksLikePathWithExtension(name)) {
|
2023-07-28 21:20:56 +08:00
|
|
|
throw new Error("Page name can not end with a file extension");
|
|
|
|
}
|
2023-07-06 22:47:50 +08:00
|
|
|
}
|
2024-01-24 18:58:33 +08:00
|
|
|
|
|
|
|
const posRegex = /@(\d+)$/;
|
2024-07-29 02:31:37 +08:00
|
|
|
const linePosRegex = /@[Ll](\d+)(?:[Cc](\d+))?$/; // column is optional, implicit 1
|
2024-01-24 18:58:33 +08:00
|
|
|
const anchorRegex = /\$([a-zA-Z\.\-\/]+[\w\.\-\/]*)$/;
|
2024-01-25 21:51:40 +08:00
|
|
|
const headerRegex = /#([^#]*)$/;
|
2024-01-24 18:58:33 +08:00
|
|
|
|
2024-08-07 19:27:25 +08:00
|
|
|
/**
|
|
|
|
* Parses a page reference string into a PageRef object.
|
|
|
|
* @param name the name of the page reference to parse
|
|
|
|
* @returns the parsed PageRef object
|
|
|
|
*/
|
2024-01-24 18:58:33 +08:00
|
|
|
export function parsePageRef(name: string): PageRef {
|
|
|
|
// Normalize the page name
|
|
|
|
if (name.startsWith("[[") && name.endsWith("]]")) {
|
|
|
|
name = name.slice(2, -2);
|
|
|
|
}
|
|
|
|
const pageRef: PageRef = { page: name };
|
2024-07-17 23:03:25 +08:00
|
|
|
if (pageRef.page.startsWith("^")) {
|
2024-07-29 02:31:37 +08:00
|
|
|
// A caret prefix means we're looking for a meta page, but that doesn't matter for most use cases
|
2024-07-17 23:03:25 +08:00
|
|
|
pageRef.page = pageRef.page.slice(1);
|
|
|
|
pageRef.meta = true;
|
|
|
|
}
|
2024-01-24 18:58:33 +08:00
|
|
|
const posMatch = pageRef.page.match(posRegex);
|
|
|
|
if (posMatch) {
|
|
|
|
pageRef.pos = parseInt(posMatch[1]);
|
|
|
|
pageRef.page = pageRef.page.replace(posRegex, "");
|
|
|
|
}
|
2024-07-29 02:31:37 +08:00
|
|
|
const linePosMatch = pageRef.page.match(linePosRegex);
|
|
|
|
if (linePosMatch) {
|
|
|
|
pageRef.pos = { line: parseInt(linePosMatch[1]), column: 1 };
|
|
|
|
if (linePosMatch[2]) {
|
|
|
|
pageRef.pos.column = parseInt(linePosMatch[2]);
|
|
|
|
}
|
|
|
|
pageRef.page = pageRef.page.replace(linePosRegex, "");
|
|
|
|
}
|
|
|
|
|
2024-01-24 18:58:33 +08:00
|
|
|
const anchorMatch = pageRef.page.match(anchorRegex);
|
|
|
|
if (anchorMatch) {
|
|
|
|
pageRef.anchor = anchorMatch[1];
|
|
|
|
pageRef.page = pageRef.page.replace(anchorRegex, "");
|
|
|
|
}
|
2024-01-25 21:51:40 +08:00
|
|
|
const headerMatch = pageRef.page.match(headerRegex);
|
|
|
|
if (headerMatch) {
|
|
|
|
pageRef.header = headerMatch[1];
|
|
|
|
pageRef.page = pageRef.page.replace(headerRegex, "");
|
|
|
|
}
|
2024-07-29 02:31:37 +08:00
|
|
|
|
2024-01-24 18:58:33 +08:00
|
|
|
return pageRef;
|
|
|
|
}
|
|
|
|
|
2024-08-07 19:27:25 +08:00
|
|
|
/**
|
|
|
|
* The inverse of parsePageRef, encodes a PageRef object into a string.
|
|
|
|
* @param pageRef the page reference to encode
|
|
|
|
* @returns a string representation of the page reference
|
|
|
|
*/
|
2024-01-24 18:58:33 +08:00
|
|
|
export function encodePageRef(pageRef: PageRef): string {
|
|
|
|
let name = pageRef.page;
|
|
|
|
if (pageRef.pos) {
|
2024-07-29 02:31:37 +08:00
|
|
|
if (pageRef.pos instanceof Object) {
|
|
|
|
name += `@L${pageRef.pos.line}`;
|
|
|
|
if (pageRef.pos.column !== 1) {
|
|
|
|
name += `C${pageRef.pos.column}`;
|
|
|
|
}
|
|
|
|
} else { // just a number
|
|
|
|
name += `@${pageRef.pos}`;
|
|
|
|
}
|
2024-01-24 18:58:33 +08:00
|
|
|
}
|
|
|
|
if (pageRef.anchor) {
|
|
|
|
name += `$${pageRef.anchor}`;
|
|
|
|
}
|
2024-01-25 21:51:40 +08:00
|
|
|
if (pageRef.header) {
|
|
|
|
name += `#${pageRef.header}`;
|
|
|
|
}
|
2024-01-24 18:58:33 +08:00
|
|
|
return name;
|
|
|
|
}
|
2024-07-29 02:31:37 +08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Translate line and column number (counting from 1) to position in text (counting from 0)
|
|
|
|
*/
|
|
|
|
export function positionOfLine(
|
|
|
|
text: string,
|
|
|
|
line: number,
|
|
|
|
column: number,
|
|
|
|
): number {
|
|
|
|
const lines = text.split("\n");
|
|
|
|
let targetLine = "";
|
|
|
|
let targetPos = 0;
|
|
|
|
for (let i = 0; i < line && lines.length; i++) {
|
|
|
|
targetLine = lines[i];
|
|
|
|
targetPos += targetLine.length;
|
|
|
|
}
|
|
|
|
// How much to move inside the line, column number starts from 1
|
|
|
|
const columnOffset = Math.max(
|
|
|
|
0,
|
|
|
|
Math.min(targetLine.length, column - 1),
|
|
|
|
);
|
|
|
|
return targetPos - targetLine.length + columnOffset;
|
|
|
|
}
|
2024-08-20 15:38:56 +08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Encodes a page name for use in a URI. Basically does encodeURIComponent, but puts slashes back in place.
|
|
|
|
* @param page page name to encode
|
|
|
|
* @returns
|
|
|
|
*/
|
|
|
|
export function encodePageURI(page: string): string {
|
|
|
|
return encodeURIComponent(page).replace(/%2F/g, "/");
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Decodes a page name from a URI.
|
|
|
|
* @param page page name to decode
|
|
|
|
* @returns
|
|
|
|
*/
|
|
|
|
export function decodePageURI(page: string): string {
|
|
|
|
return decodeURIComponent(page);
|
|
|
|
}
|