Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 102 additions & 83 deletions src/features/attributeSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,36 +41,40 @@ const CONFIG = `roam/js/attribute-select`;
const TEMPLATE_MAP = {
"No styling": {
transform: (text: string) => text,
description: "No styling"
description: "No styling",
},
"Remove Double Brackets": {
transform: (text: string) => text.replace(/\[\[(.*?)\]\]/g, '$1'),
description: "Removes [[text]] format"
transform: (text: string) => text.replace(/\[\[(.*?)\]\]/g, "$1"),
description: "Removes [[text]] format",
},
"Convert to Uppercase": {
transform: (text: string) => text.toUpperCase(),
description: "Makes text all caps"
description: "Makes text all caps",
},
"Capitalize Words": {
transform: (text: string) => text.split(' ').map(word => {
if (!word) return '';
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
}).join(' '),
description: "Makes first letter of each word uppercase"
transform: (text: string) =>
text
.split(" ")
.map((word) => {
if (!word) return "";
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
})
.join(" "),
description: "Makes first letter of each word uppercase",
},
"Custom Format": {
transform: (text: string, pattern?: string, replacement?: string) => {
if (!pattern) return text;
try {
const regex = new RegExp(pattern);
return text.replace(regex, replacement || '');
return text.replace(regex, replacement || "");
} catch (e) {
console.error("Invalid regex:", e);
return text;
}
},
description: "Apply custom regex pattern"
}
description: "Apply custom regex pattern",
},
} as const;

type TemplateName = keyof typeof TEMPLATE_MAP;
Expand All @@ -86,16 +90,16 @@ const applyFormatting = ({
text,
templateName,
customPattern,
customReplacement
customReplacement,
}: FormatParams): string => {
try {
const template = TEMPLATE_MAP[templateName as TemplateName];
if (!template) return text;

if (templateName === "Custom Format" && customPattern) {
return template.transform(text, customPattern, customReplacement);
}

return template.transform(text);
} catch (e) {
console.error("Error in transform function:", e);
Expand Down Expand Up @@ -128,7 +132,7 @@ const AttributeButtonPopover = <T extends ReactText>({
useEffect(() => {
setSliderValue(Number(currentValue));
}, [isOpen, currentValue]);

const formatConfig = useMemo(() => {
try {
const configUid = getPageUidByPageTitle(CONFIG);
Expand All @@ -140,39 +144,42 @@ const AttributeButtonPopover = <T extends ReactText>({
key: attributeName,
parentUid: attributesNode.uid,
}).uid;

return {
templateName: getSettingValueFromTree({
key: "template",
parentUid: attributeUid,
}) || "No styling",

templateName:
getSettingValueFromTree({
key: "template",
parentUid: attributeUid,
}) || "No styling",

customPattern: getSettingValueFromTree({
key: "customPattern",
parentUid: attributeUid,
}),

customReplacement: getSettingValueFromTree({
key: "customReplacement",
parentUid: attributeUid,
})
}),
};
} catch (e) {
console.error("Error getting format config:", e);
return {
templateName: "No styling",
customPattern: undefined,
customReplacement: undefined
customReplacement: undefined,
};
}
}, [attributeName]);

const formatText = useMemo(() =>
(text: string) => applyFormatting({
text,
...formatConfig
}),
[formatConfig]);
const formatText = useMemo(
() => (text: string) =>
applyFormatting({
text,
...formatConfig,
}),
[formatConfig]
);

// Only show filter if we have more than 10 items
const shouldFilter = filterable && items.length > 10;
Expand Down Expand Up @@ -586,36 +593,41 @@ const TabsPanel = ({
const [optionType, setOptionType] = useState(initialOptionType || "text");
const [min, setMin] = useState(Number(rangeNode.children[0]?.text) || 0);
const [max, setMax] = useState(Number(rangeNode.children[1]?.text) || 10);

const { initialTemplate, initialCustomPattern, initialCustomReplacement } = useMemo(() => {
const savedTemplate = getSettingValueFromTree({
key: "template",
parentUid: attributeUid,
}) || "No styling";

const savedCustomPattern = getSettingValueFromTree({
key: "customPattern",
parentUid: attributeUid,
}) || "";

const savedCustomReplacement = getSettingValueFromTree({
key: "customReplacement",
parentUid: attributeUid,
}) || "";

return {
initialTemplate: savedTemplate,
initialCustomPattern: savedCustomPattern,
initialCustomReplacement: savedCustomReplacement
};
}, [attributeUid]);

const { initialTemplate, initialCustomPattern, initialCustomReplacement } =
useMemo(() => {
const savedTemplate =
getSettingValueFromTree({
key: "template",
parentUid: attributeUid,
}) || "No styling";

const savedCustomPattern =
getSettingValueFromTree({
key: "customPattern",
parentUid: attributeUid,
}) || "";

const savedCustomReplacement =
getSettingValueFromTree({
key: "customReplacement",
parentUid: attributeUid,
}) || "";

return {
initialTemplate: savedTemplate,
initialCustomPattern: savedCustomPattern,
initialCustomReplacement: savedCustomReplacement,
};
}, [attributeUid]);

const [selectedTemplate, setSelectedTemplate] = useState(initialTemplate);
const [customPattern, setCustomPattern] = useState(initialCustomPattern);
const [customReplacement, setCustomReplacement] = useState(initialCustomReplacement);
const [customReplacement, setCustomReplacement] = useState(
initialCustomReplacement
);
const [isValidRegex, setIsValidRegex] = useState(true);


// For a better UX replace renderBlock with a controlled list
// add Edit, Delete, and Add New buttons
const contentRef = useRef(null);
Expand Down Expand Up @@ -712,7 +724,7 @@ const TabsPanel = ({

{optionType === "text" && (
<>
<FormGroup
<FormGroup
label={
<div className="flex items-center gap-2">
<span>Display Format</span>
Expand All @@ -723,7 +735,7 @@ const TabsPanel = ({
<Icon icon="info-sign" className="opacity-80" />
</Tooltip>
</div>
}
}
className="m-0"
>
<div className="flex flex-col space-y-2">
Expand All @@ -740,31 +752,35 @@ const TabsPanel = ({
}}
activeItem={selectedTemplate}
/>
<Tooltip
<Tooltip
content={
<div className="text-sm">
<p className="font-bold mb-2">Available Templates:</p>
<ul className="list-disc list-inside space-y-1">
{Object.entries(TEMPLATE_MAP).map(([name, { description }]) => (
<li key={name}>
<span className="font-mono">{name}:</span>{" "}
{description}
</li>
))}
{Object.entries(TEMPLATE_MAP).map(
([name, { description }]) => (
<li key={name}>
<span className="font-mono">{name}:</span>{" "}
{description}
</li>
)
)}
</ul>
</div>
}
placement="top"
>
<Icon icon="info-sign" className="opacity-80" />
>
<Icon icon="info-sign" className="opacity-80" />
</Tooltip>
</div>

{selectedTemplate === "Custom Format" && (
<div className="space-y-2">
<FormGroup label="Pattern (regex)" className="m-0">
<input
className={`bp3-input font-mono text-sm w-full ${!isValidRegex ? 'bp3-intent-danger' : ''}`}
className={`bp3-input font-mono text-sm w-full ${
!isValidRegex ? "bp3-intent-danger" : ""
}`}
placeholder="E.g., \[\[(.*?)\]\]"
value={customPattern}
onChange={(e) => {
Expand All @@ -789,7 +805,7 @@ const TabsPanel = ({
</div>
)}
</FormGroup>

<FormGroup label="Replacement" className="m-0">
<input
className="bp3-input font-mono text-sm w-full"
Expand All @@ -806,22 +822,24 @@ const TabsPanel = ({
}}
/>
</FormGroup>

<div className="bg-gray-100 p-2 rounded text-sm">
<div className="font-bold mb-1">Preview:</div>
<div>
<span className="font-bold">Input:</span> <span className="font-mono">[[Example]]</span>
<span className="font-bold">Input:</span>{" "}
<span className="font-mono">[[Example]]</span>
</div>
<div>
<span className="font-bold">Output:</span> <span className="font-mono">
{customPattern ?
applyFormatting({
text: "[[Example]]",
templateName: "Custom Format",
customPattern,
customReplacement
}) :
"[[Example]]"}
<span className="font-bold">Output:</span>{" "}
<span className="font-mono">
{customPattern
? applyFormatting({
text: "[[Example]]",
templateName: "Custom Format",
customPattern,
customReplacement,
})
: "[[Example]]"}
</span>
</div>
</div>
Expand All @@ -834,7 +852,8 @@ const TabsPanel = ({
text={"Find All Current Values"}
rightIcon={"search"}
onClick={() => {
const potentialOptions = findAllPotentialOptions(attributeName);
const potentialOptions =
findAllPotentialOptions(attributeName);
setPotentialOptions(potentialOptions);
setShowPotentialOptions(true);
}}
Expand Down Expand Up @@ -1010,7 +1029,7 @@ export const toggleFeature = async (flag: boolean) => {
.inline-menu-item-select > span > div {display:inline}
#attribute-select-config .rm-block-separator {display: none;}
`);

definedAttributes = getDefinedAttributes();
const pageUid =
getPageUidByPageTitle(CONFIG) ||
Expand Down