cmd¶
Index¶
- Constants
- Variables
- func BuildHTTPMux(pbURL, token, graphPath string) *http.ServeMux
- func DashboardAliases() []string
- func Execute()
- func NewGroomCmd(deps *GroomDependencies) *cobra.Command
- func NewMdCmd(deps *MdDependencies) *cobra.Command
- func NewOutlineCmd(deps *OutlineDependencies) *cobra.Command
- func NewSyncCmd(deps *SyncDependencies) *cobra.Command
- func NewTaskAddCmd(deps *TaskAddDependencies) *cobra.Command
- func NewTaskCmd() *cobra.Command
- func NewTaskLsCmd(deps *TaskLsDependencies) *cobra.Command
- func ParseDateFromJournalFlag(journalFlag string, timeNow func() time.Time) (time.Time, error)
- func ResolveEnvWithDefault(key, fallback string) string
- func ResolvePort(cmd *cobra.Command) int
- func addJournalFlag(cmd *cobra.Command, flagVar *string)
- func addKeyFlag(cmd *cobra.Command, flagVar *string)
- func addPageFlag(cmd *cobra.Command, flagVar *string, what string)
- func addParentFlag(cmd *cobra.Command, flagVar *string, what string)
- func applyChanges(pbClient *pocketbase.Client, desired []map[string]any) error
- func authenticate(pbURL, pbUser, pbPass string) (string, error)
- func backlogUnrankedSectionTexts() []string
- func buildDesiredRecords(tasks []logseqapi.TaskJSON, ranks map[string][]lqdsync.RankInfo, tagsByUUID map[string]string, currentTime func() time.Time) []map[string]any
- func collectBacklogRefs(graph *logseq.Graph, config *backlog.Config) (map[string][]lqdsync.RankInfo, []string)
- func collectFocusRefs(graph *logseq.Graph, focusPagePath string, ranks map[string][]lqdsync.RankInfo, backlogOrder *[]string)
- func collectPageRefs(graph *logseq.Graph, backlogPagePath string, ranks map[string][]lqdsync.RankInfo, backlogOrder *[]string)
- func fetchGroomTasks(now, thresholdDate time.Time, limit int) (*pocketbase.Client, []map[string]any, error)
- func fetchLogseqTasks(logseqAPI logseqapi.LogseqAPI) ([]logseqapi.TaskJSON, error)
- func fillOutlineDeps(deps *OutlineDependencies)
- func gracefulShutdown(srv *http.Server) error
- func groomHandleTask(graph *logseq.Graph, api api.LogseqAPI, backlogConfig *backlog.Config, pbUpdater func(recordID string, groomedAt time.Time) error, task map[string]any, now time.Time, counts *groom.Counts) bool
- func groomPrintApplyError(applyErr error, counts *groom.Counts)
- func groomSyncPocketBase(pbUpdater func(recordID string, groomedAt time.Time) error, action *groom.Action, task map[string]any, now time.Time)
- func handleConfig(writer http.ResponseWriter, graphPath string)
- func handleMoveToUnranked(writer http.ResponseWriter, req *http.Request, graphPath, pbURL, token string)
- func hasRankInBacklog(rankInfos []lqdsync.RankInfo, backlogName string) bool
- func init()
- func initCollection(client *pocketbase.Client) error
- func logseqGraphName() string
- func openGroomResources() (*logseq.Graph, api.LogseqAPI, *backlog.Config, error)
- func printTaskCard(task map[string]any, index, total int, now time.Time, termWidth int)
- func processGroomTasks(tasks []map[string]any, now time.Time, graph *logseq.Graph, api api.LogseqAPI, backlogConfig *backlog.Config, pbUpdater func(recordID string, groomedAt time.Time) error) groom.Counts
- func readKey() (string, error)
- func resolveBacklogPage(graphPath, shortName string) string
- func runDashboard(cmd *cobra.Command, _ []string) error
- func runGroomWith(now time.Time, olderThan string, limit int)
- func runOutline(deps *OutlineDependencies, args []string, inPlace bool, moveTo string, keepBreaks bool) error
- func runOutlineInPlace(deps *OutlineDependencies, path string, opts internal.OutlineOptions) error
- func runOutlineMoveTo(deps *OutlineDependencies, path, moveTo string, opts internal.OutlineOptions) error
- func runOutlineToStdout(deps *OutlineDependencies, path string, opts internal.OutlineOptions) error
- func runSyncPipeline(graph *logseq.Graph, logseqAPI logseqapi.LogseqAPI, pbClient *pocketbase.Client, currentTime func() time.Time) error
- func runSyncWith(currentTime func() time.Time, initFlag bool)
- func runTaskLs(deps *TaskLsDependencies, flags *taskLsFlags, args []string) error
- func startHTTPServer(ctx context.Context, port int, mux http.Handler) error
- func terminalWidth() int
- func updateGroomCounts(counts *groom.Counts, actionName string)
- type GroomDependencies
- type MdDependencies
- type OutlineDependencies
- type SyncDependencies
- type TaskAddDependencies
- type TaskLsDependencies
- type taskLsFlags
Constants¶
const (
defaultServePort = 8091
defaultPocketBaseURL = "http://127.0.0.1:8090"
pbReadyTimeout = 10 * time.Second
shutdownTimeout = 5 * time.Second
readHeaderTimeout = 5 * time.Second
)
firstLineCount is the split limit used to extract the first line of block content.
const groomFetchMultiplier = 5 // fetch 5× the limit to absorb tasks filtered out by HasFutureDate
Variables¶
ErrDestinationExists is returned when the destination file already exists during --move-to.
ErrMutuallyExclusive is returned when --in-place and --move-to are both set.
var backlogCmd = &cobra.Command{
Use: "backlog [partial page names]",
Short: "Aggregate tasks from multiple pages into a backlog",
Long: `The backlog command aggregates tasks from one or more pages into a unified backlog.
If partial page names are provided, only page titles that contain the provided names are processed.
Each line on the "backlog" page that includes references to other pages or tags generates a separate backlog.
The first page in the line determines the name of the backlog page.
Tasks are retrieved from all provided pages or tags.
This setup enables users to rearrange tasks using the arrow keys and manage task states (start/stop)
directly within the interface.`,
Run: func(_ *cobra.Command, args []string) {
path := os.Getenv("LOGSEQ_GRAPH_PATH")
logseqAPI := logseqapi.NewLogseqAPI(path,
os.Getenv("LOGSEQ_HOST_URL"), os.Getenv("LOGSEQ_API_TOKEN"))
graph := logseqapi.OpenGraphFromPath(path)
reader := backlog.NewPageConfigReader(graph, "backlog")
proc := backlog.NewBacklog(graph, logseqAPI, reader, time.Now)
err := proc.ProcessAll(args)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
},
}
contentCmd represents the content command.
var contentCmd = &cobra.Command{
Use: "content",
Short: "Append raw Markdown content to Logseq",
Long: `Append raw Markdown content to Logseq.
Pipe your content via stdin.
For now, it will be appended at the end of the current journal page.`,
Run: func(_ *cobra.Command, _ []string) {
graph := api.OpenGraphFromPath(os.Getenv("LOGSEQ_GRAPH_PATH"))
stdin := internal.ReadFromStdin()
var targetDate time.Time
if journalFlag != "" {
parsedDate, err := time.Parse("2006-01-02", journalFlag)
if err != nil {
log.Fatalln("Invalid journal date format. Use YYYY-MM-DD:", err)
}
targetDate = parsedDate
} else {
targetDate = time.Now()
}
_, err := internal.AppendRawMarkdownToJournal(graph, targetDate, stdin)
if err != nil {
log.Fatalln(err)
}
},
}
var dashboardCmd = &cobra.Command{
Use: "dashboard",
Aliases: DashboardAliases(),
Short: "Start PocketBase and the backlog web UI",
Long: `Starts PocketBase as a managed subprocess, waits for it to be ready,
then serves the backlog dashboard at http://localhost:8091 (configurable).
Environment variables:
POCKETBASE_URL PocketBase URL (default http://127.0.0.1:8090)
POCKETBASE_USERNAME PocketBase admin email
POCKETBASE_PASSWORD PocketBase admin password
LOGSEQ_GRAPH_PATH Path to Logseq graph (required for write-back)
LQD_SERVE_PORT HTTP server port (default 8091)`,
RunE: runDashboard,
}
errGroomNoCollection is returned when the lqd_tasks collection does not exist in PocketBase.
groomStyles holds the lipgloss styles for the groom TUI.
var groomStyles = struct {
separator lipgloss.Style
header lipgloss.Style
taskName lipgloss.Style
label lipgloss.Style
value lipgloss.Style
age lipgloss.Style
actions lipgloss.Style
prompt lipgloss.Style
success lipgloss.Style
warning lipgloss.Style
errStyle lipgloss.Style
link lipgloss.Style
}{
separator: lipgloss.NewStyle().Foreground(lipgloss.Color("240")),
header: lipgloss.NewStyle().Foreground(lipgloss.Color("33")).Bold(true),
taskName: lipgloss.NewStyle().Foreground(lipgloss.Color("255")).Bold(true),
label: lipgloss.NewStyle().Foreground(lipgloss.Color("245")),
value: lipgloss.NewStyle().Foreground(lipgloss.Color("252")),
age: lipgloss.NewStyle().Foreground(lipgloss.Color("208")).Italic(true),
actions: lipgloss.NewStyle().Foreground(lipgloss.Color("255")).Bold(true),
prompt: lipgloss.NewStyle().Foreground(lipgloss.Color("33")).Bold(true),
success: lipgloss.NewStyle().Foreground(lipgloss.Color("82")),
warning: lipgloss.NewStyle().Foreground(lipgloss.Color("226")),
errStyle: lipgloss.NewStyle().Foreground(lipgloss.Color("196")).Bold(true),
link: lipgloss.NewStyle().Foreground(lipgloss.Color("39")).Underline(true),
}
mdCmd represents the md command using the default dependencies.
rootCmd represents the base command when called without any subcommands.
var rootCmd = &cobra.Command{
Use: "lqd",
Short: "Logseq Doctor heals your Markdown files for Logseq",
Long: `Logseq Doctor heals your Markdown files for Logseq.
Convert flat Markdown to Logseq outline, clean up Markdown,
prevent invalid content, and more stuff to come.`,
}
taskCmd represents the task command using the default dependencies.
tidyUpCmd represents the tidyUp command.
var tidyUpCmd = &cobra.Command{
Use: "tidy-up file1.md [file2.md ...]",
Short: "Tidy up your Markdown files.",
Long: `Tidy up your Markdown files, checking for invalid content and fixing some of them automatically.
- Check for forbidden references to pages/tags
- Check for running tasks (DOING)
- Check for double spaces`,
Args: cobra.MinimumNArgs(1),
Run: func(_ *cobra.Command, args []string) {
graph := api.OpenGraphFromPath(os.Getenv("LOGSEQ_GRAPH_PATH"))
exitCode := 0
for _, path := range args {
if internal.TidyUpOneFile(graph, path) != 0 {
exitCode = 1
}
}
os.Exit(exitCode)
},
}
func BuildHTTPMux¶
BuildHTTPMux creates the HTTP mux with all routes registered.
func DashboardAliases¶
DashboardAliases returns the Cobra aliases for the dashboard command. Exported so tests can verify the alias without accessing the unexported global.
func Execute¶
Execute adds all child commands to the root command and sets flags appropriately. This is called by main.main(). It only needs to happen once to the rootCmd.
func NewGroomCmd¶
NewGroomCmd creates a new groom command with the specified dependencies. If deps is nil, it uses default (production) implementations.
func NewMdCmd¶
NewMdCmd creates a new md command with the specified dependencies. If deps is nil, it uses default implementations.
func NewOutlineCmd¶
NewOutlineCmd creates the outline command with injectable dependencies. If deps is nil, production defaults are used. Individual nil fields also fall back to their defaults, so tests can inject only the fields they need.
func NewSyncCmd¶
NewSyncCmd creates a new sync command with the specified dependencies. If deps is nil, it uses default (production) implementations.
func NewTaskAddCmd¶
NewTaskAddCmd creates a new task add subcommand with the specified dependencies. If deps is nil, it uses default implementations.
func NewTaskCmd¶
NewTaskCmd creates the parent task command.
func NewTaskLsCmd¶
NewTaskLsCmd creates a new task ls subcommand with the specified dependencies. If deps is nil, it uses default implementations. Individual nil fields also fall back to their defaults, so a test can inject only LogseqAPI + Out and leave GraphName defaulted.
func ParseDateFromJournalFlag¶
ParseDateFromJournalFlag parses the journal flag and returns the target date. If journalFlag is empty, it returns the current time from timeNow. If journalFlag is not empty, it parses it as YYYY-MM-DD format. Returns an error if the date format is invalid.
func ResolveEnvWithDefault¶
ResolveEnvWithDefault returns the env var value or fallback if unset.
func ResolvePort¶
ResolvePort returns the effective port: flag > env var > default.
func addJournalFlag¶
addJournalFlag adds a --journal/-j flag to the command.
func addKeyFlag¶
addKeyFlag adds a --key/-k flag to the command.
func addPageFlag¶
addPageFlag adds a --page/-p flag to the command with customizable help text.
func addParentFlag¶
addParentFlag adds a --parent flag to the command with customizable help text.
func applyChanges¶
func authenticate¶
authenticate obtains a PocketBase token. Returns "" and nil when credentials are absent.
func backlogUnrankedSectionTexts¶
unrankedSectionTexts lists the header texts that mark the start of an unranked section on a backlog page. Any block ref that is a child of one of these headers backlogUnrankedSectionTexts returns the header texts that mark the start of an unranked section on a backlog page. Any block ref that is a child of one of these headers is assigned SectionUnranked during sync.
func buildDesiredRecords¶
func buildDesiredRecords(tasks []logseqapi.TaskJSON, ranks map[string][]lqdsync.RankInfo, tagsByUUID map[string]string, currentTime func() time.Time) []map[string]any
func collectBacklogRefs¶
func collectBacklogRefs(graph *logseq.Graph, config *backlog.Config) (map[string][]lqdsync.RankInfo, []string)
collectBacklogRefs scans the Focus page and all configured backlog pages, returning a rank map (uuid → []RankInfo) and the ordered list of backlog names. Each ref is classified as SectionRanked or SectionUnranked based on which section header it lives under on the page.
func collectFocusRefs¶
func collectFocusRefs(graph *logseq.Graph, focusPagePath string, ranks map[string][]lqdsync.RankInfo, backlogOrder *[]string)
func collectPageRefs¶
func collectPageRefs(graph *logseq.Graph, backlogPagePath string, ranks map[string][]lqdsync.RankInfo, backlogOrder *[]string)
func fetchGroomTasks¶
func fetchGroomTasks(now, thresholdDate time.Time, limit int) (*pocketbase.Client, []map[string]any, error)
fetchGroomTasks initialises PocketBase, checks the collection, and fetches matching tasks. Returns (nil, nil, nil) with a printed message when there are no tasks.
func fetchLogseqTasks¶
func fillOutlineDeps¶
func gracefulShutdown¶
gracefulShutdown shuts the server down with a fresh timeout context. The parent context is already cancelled at this point, so a new one is needed.
func groomHandleTask¶
func groomHandleTask(graph *logseq.Graph, api api.LogseqAPI, backlogConfig *backlog.Config, pbUpdater func(recordID string, groomedAt time.Time) error, task map[string]any, now time.Time, counts *groom.Counts) bool
groomHandleTask reads keypresses for a single task until a valid action is taken. Returns true if the user requested quit.
func groomPrintApplyError¶
func groomSyncPocketBase¶
func groomSyncPocketBase(pbUpdater func(recordID string, groomedAt time.Time) error, action *groom.Action, task map[string]any, now time.Time)
func handleConfig¶
handleConfig returns UI configuration derived from the server environment.
func handleMoveToUnranked¶
func handleMoveToUnranked(writer http.ResponseWriter, req *http.Request, graphPath, pbURL, token string)
handleMoveToUnranked handles POST /internal/move-to-unranked.
func hasRankInBacklog¶
hasRankInBacklog reports whether the given backlog name already has an entry in the rank slice.
func init¶
func initCollection¶
func logseqGraphName¶
logseqGraphName extracts the graph name from the graph path for deep links.
func openGroomResources¶
openGroomResources opens the Logseq graph, API, and reads the backlog config.
func printTaskCard¶
printTaskCard prints a single task card to stdout.
func processGroomTasks¶
func processGroomTasks(tasks []map[string]any, now time.Time, graph *logseq.Graph, api api.LogseqAPI, backlogConfig *backlog.Config, pbUpdater func(recordID string, groomedAt time.Time) error) groom.Counts
processGroomTasks presents tasks one at a time in a plain scrolling terminal loop. Each task card is printed, the user presses a key, the result is printed, then the next task scrolls into view. No alternate screen — every action is permanently visible.
func readKey¶
readKey reads a single keypress from /dev/tty without requiring Enter.
func resolveBacklogPage¶
resolveBacklogPage maps a short backlog name (e.g. "self") to its full page title (e.g. "Backlogs/self") by reading the backlog config page from the graph. Falls back to the short name if the config cannot be read or the name is not found.
func runDashboard¶
func runGroomWith¶
runGroomWith is the testable core of runGroom.
func runOutline¶
func runOutline(deps *OutlineDependencies, args []string, inPlace bool, moveTo string, keepBreaks bool) error
runOutline is the core logic for the outline command.
func runOutlineInPlace¶
func runOutlineInPlace(deps *OutlineDependencies, path string, opts internal.OutlineOptions) error
runOutlineInPlace converts a file and writes the result back to the same path.
func runOutlineMoveTo¶
func runOutlineMoveTo(deps *OutlineDependencies, path, moveTo string, opts internal.OutlineOptions) error
runOutlineMoveTo converts a file and writes the result to a destination directory, then removes the source.
func runOutlineToStdout¶
func runOutlineToStdout(deps *OutlineDependencies, path string, opts internal.OutlineOptions) error
runOutline handles the outline command's stdout-only branch (no flags).
func runSyncPipeline¶
func runSyncPipeline(graph *logseq.Graph, logseqAPI logseqapi.LogseqAPI, pbClient *pocketbase.Client, currentTime func() time.Time) error
func runSyncWith¶
runSyncWith is the testable core of runSync.
func runTaskLs¶
func startHTTPServer¶
startHTTPServer runs the server until a signal arrives or a listen error occurs.
func terminalWidth¶
terminalWidth returns the current terminal width, falling back to groomDefaultTermWidth.
func updateGroomCounts¶
updateGroomCounts increments the appropriate counter for the given action name.
type GroomDependencies¶
GroomDependencies holds all injectable dependencies for the groom command. This enables unit testing without connecting to PocketBase, Logseq, or a terminal.
type MdDependencies¶
MdDependencies holds all the dependencies for the md command.
type MdDependencies struct {
InsertFn func(*internal.InsertMarkdownOptions) error
OpenGraph func(string) *logseq.Graph
ReadStdin func() string
TimeNow func() time.Time
}
type OutlineDependencies¶
OutlineDependencies holds injectable dependencies for the outline command.
type OutlineDependencies struct {
Convert func(input string, opts internal.OutlineOptions) string
ReadFile func(path string) (string, error)
WriteFile func(path string, data string) error
Stat func(path string) (os.FileInfo, error)
Rename func(oldpath, newpath string) error
Remove func(path string) error
Stdin func() string
Out io.Writer
}
type SyncDependencies¶
SyncDependencies holds all injectable dependencies for the sync command. This enables unit testing without connecting to PocketBase or Logseq.
type TaskAddDependencies¶
TaskAddDependencies holds all the dependencies for the task add command.
type TaskAddDependencies struct {
AddTaskFn func(*logseqext.AddTaskOptions) error
OpenGraph func(string) *logseq.Graph
TimeNow func() time.Time
}
type TaskLsDependencies¶
TaskLsDependencies holds all the dependencies for the task ls command.
type TaskLsDependencies struct {
NewAPI func() api.LogseqAPI
GraphName func() string
Out io.Writer
}
type taskLsFlags¶
taskLsFlags holds the flag values for NewTaskLsCmd.
internal¶
Index¶
- Constants
- Variables
- func AppendRawMarkdownToJournal(graph *logseq.Graph, date time.Time, rawMarkdown string) (int, error)
- func FlatMarkdownToOutline(input string, opts OutlineOptions) string
- func InsertMarkdown(opts *InsertMarkdownOptions) error
- func IsAncestor(block, ancestor *content.Block) bool
- func IsValidMarkdownFile(filePath string) bool
- func ReadFromStdin() string
- func SortAndRemoveDuplicates(elements []string) []string
- func TidyUpOneFile(graph *logseq.Graph, path string) int
- func addContent(page logseq.Page, parentBlock *content.Block, contentText string) error
- func convertOnce(body string, opts OutlineOptions) string
- func findFirstChildBlock(block *content.Block) *content.Block
- func findNestedList(item ast.Node) *ast.List
- func insertNewContentNodes(block *content.Block, parsedBlock *content.Block, firstChildBlock *content.Block)
- func outlineLine(level int, lineText string) string
- func removeOldContentNodes(block *content.Block)
- func shouldPreserveNode(node content.Node) bool
- func stripFrontmatter(input string) (string, string)
- func updateExistingBlock(block *content.Block, newContent string) error
- type ChangedContents
- func RemoveUnnecessaryBracketsFromTags(oldContents string) ChangedContents
- type ChangedPage
- func CheckForbiddenReferences(page logseq.Page) ChangedPage
- func CheckRunningTasks(page logseq.Page) ChangedPage
- func RemoveDoubleSpaces(page logseq.Page) ChangedPage
- func RemoveEmptyBullets(page logseq.Page) ChangedPage
- type InsertMarkdownOptions
- type OutlineOptions
- type converter
- func (c *converter) collectInline(node ast.Node, builder *strings.Builder)
- func (c *converter) handleListItem(node ast.Node, isOrdered bool)
- func (c *converter) inlineText(node ast.Node) string
- func (c *converter) listItemText(item ast.Node) string
- func (c *converter) walk(node ast.Node, entering bool) (ast.WalkStatus, error)
- func (c *converter) walkNestedList(list *ast.List)
Constants¶
const (
outlineIndentUnit = "\t" // one tab per indent level, matching Logseq's native format
outlineBullet = "-"
outlineOrderedListProp = "logseq.order-list-type:: number"
// outlinePropertyPrefix is the indent for a block property line: tab(s) + 2 spaces.
outlinePropertySpaces = " "
)
Variables¶
ErrPageIsNil is returned when a page is nil.
PageColor is a color function for page names.
func AppendRawMarkdownToJournal¶
func AppendRawMarkdownToJournal(graph *logseq.Graph, date time.Time, rawMarkdown string) (int, error)
AppendRawMarkdownToJournal appends raw Markdown content to the journal page for the given date. I tried appending blocks with `logseq-go` but there is and with text containing brackets. e.g. "[something]" is escaped like "\[something\]" and this breaks links.
func FlatMarkdownToOutline¶
FlatMarkdownToOutline converts flat Markdown to a Logseq bullet outline. It strips YAML frontmatter before parsing and prepends it unchanged to the result. It is idempotent: if the conversion is a no-op, the original input is returned.
func InsertMarkdown¶
InsertMarkdown inserts Markdown content to a page or journal. If Page is provided, adds to that page. Otherwise, adds to journal for Date. If Key is provided, it searches for an existing block containing that key (case-insensitive) and updates it. Otherwise, creates a new block. If ParentText is provided, it searches for the first block containing that text and inserts the content as a child block. Otherwise, appends to the end.
func IsAncestor¶
IsAncestor checks if ancestor is an ancestor of block by traversing up the parent chain. Returns true if ancestor is found in the parent hierarchy of block, false otherwise. Returns true if block and ancestor are the same block.
func IsValidMarkdownFile¶
IsValidMarkdownFile checks if a file is a Markdown file, by looking at its extension, not its content.
func ReadFromStdin¶
ReadFromStdin reads from stdin and returns the content as a string. It doesn't return an error and aborts the program if it fails because it's an internal function.
func SortAndRemoveDuplicates¶
func TidyUpOneFile¶
func addContent¶
addContent adds content either as a child block to the specified parent or as a top-level block to the page.
func convertOnce¶
convertOnce parses and converts a Markdown body (without frontmatter) to outline form.
func findFirstChildBlock¶
findFirstChildBlock finds the first child block in a block.
func findNestedList¶
findNestedList returns the first *ast.List child of a list item, or nil.
func insertNewContentNodes¶
func insertNewContentNodes(block *content.Block, parsedBlock *content.Block, firstChildBlock *content.Block)
insertNewContentNodes inserts new content nodes from parsedBlock into block.
func outlineLine¶
outlineLine formats a single outline bullet at the given nesting level.
func removeOldContentNodes¶
removeOldContentNodes removes content nodes from a block while preserving Properties, Logbook, and child Blocks.
func shouldPreserveNode¶
shouldPreserveNode returns true if the node should be preserved (Properties, Logbook, or child Blocks).
func stripFrontmatter¶
stripFrontmatter splits YAML frontmatter from the body. If the input starts with "---\n" and contains a closing "\n---\n", the frontmatter (including fences) is returned separately from the body. This must run before goldmark sees the input so that "---" is not parsed as a thematic break or setext heading underline.
func updateExistingBlock¶
updateExistingBlock updates an existing block's content while preserving children, properties, and logbook.
type ChangedContents¶
ChangedContents is the result of a check function that modifies file contents directly without a transaction.
func RemoveUnnecessaryBracketsFromTags¶
RemoveUnnecessaryBracketsFromTags removes unnecessary brackets from hashtags. logseq-go rewrites tags correctly when saving the transaction, removing unnecessary brackets. But, when reading the file, the AST doesn't provide the information if a tag has brackets or not. So I would have to rewrite the file to fix them, and I don't want to do it every time there is a tag without spaces. Also, as of 2024-12-30, logseq-go has a bug when reading properties with spaces in values, which causes them to be partially removed from the file, destroying data. I will report it soon.
type ChangedPage¶
ChangedPage is the result of a check function that modifies Markdown through a Page and a transaction.
func CheckForbiddenReferences¶
CheckForbiddenReferences checks if a page has forbidden references to other pages or tags.
func CheckRunningTasks¶
CheckRunningTasks checks if a page has running tasks (DOING, etc.).
func RemoveDoubleSpaces¶
RemoveDoubleSpaces removes double spaces from text, page links, and tags, except for tables.
func RemoveEmptyBullets¶
type InsertMarkdownOptions¶
InsertMarkdownOptions contains options for inserting Markdown content.
type InsertMarkdownOptions struct {
Graph *logseq.Graph
Date time.Time
Page string // Page name to add content to (empty = journal)
Content string
ParentText string // Partial text to search for in parent blocks
Key string // Unique key to search for existing block (case-insensitive)
}
type OutlineOptions¶
OutlineOptions configures the flat-Markdown-to-outline conversion.
type OutlineOptions struct {
// KeepBreaks preserves blank lines between blocks as empty "- " bullet lines.
KeepBreaks bool
}
type converter¶
converter walks the goldmark AST and builds the outline string.
func (*converter) collectInline¶
collectInline recursively renders inline nodes into builder.
func (*converter) handleListItem¶
handleListItem emits the list item text and manages level for nested lists. isOrdered indicates whether the parent list is an ordered list; when true, the Logseq block property "logseq.order-list-type:: number" is written after the bullet as a plain indented line (no "- " prefix).
On re-parse the converted output uses unordered "-" bullets, so isOrdered is false. The property line appears as a soft-line-break continuation inside the TextBlock. We detect it directly in itemText to preserve it on re-parse (idempotency).
func (*converter) inlineText¶
inlineText collects the rendered text of all inline children of a node. It replicates the Python LogseqRenderer.render_inner / render_link behavior.
func (*converter) listItemText¶
listItemText extracts the text content from a list item's first paragraph or inline block.
func (*converter) walk¶
walk is the goldmark AST visitor function.
func (*converter) walkNestedList¶
walkNestedList recursively processes a nested list and its items.
lqd¶
Copyright © 2024 W Augusto Andreoli \<andreoliwa@sent.com>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Index¶
func main¶
api¶
Package api provides the Logseq HTTP API client and graph-opening utilities.
Index¶
- Variables
- func BuildRefLookup(tasks []TaskJSON) map[int]string
- func BuildTaskListQuery(tags []string, includeCanceled, includeDone bool) string
- func EnrichTasksWithAncestorTags(tasks []TaskJSON, refLookup map[int]string) map[string]string
- func FindBlockOnDisk(graph *logseq.Graph, api LogseqAPI, uuid string) (*content.Block, *logseq.Transaction, error)
- func OpenGraphFromPath(path string) *logseq.Graph
- func OpenPage(graph *logseq.Graph, pageTitle string) logseq.Page
- func OpenPageForBlock(transaction *logseq.Transaction, blockInfo *BlockQueryInfo) (logseq.Page, error)
- func SortTasksByDate(tasks []TaskJSON)
- func TaskDoing(t TaskJSON) bool
- func TaskFutureScheduled(t TaskJSON, currentTime func() time.Time) bool
- func TaskOverdue(t TaskJSON, currentTime func() time.Time) bool
- func buildDirectRefIDSet(task TaskJSON) map[int]bool
- func collectAncestorTags(task TaskJSON, directRefIDs map[int]bool, refLookup map[int]string) []string
- func collectRefCandidates(tasks []TaskJSON, refLookup map[int]string) map[int]map[string]int
- func openBlockFromInfo(graph *logseq.Graph, blockInfo *BlockQueryInfo, uuid string) (*content.Block, *logseq.Transaction, error)
- func populatePageRef(refLookup map[int]string, task TaskJSON)
- func resolveRefCandidates(refLookup map[int]string, candidates map[int]map[string]int)
- type BlockQueryInfo
- func FindBlockByUUID(api LogseqAPI, uuid string) (*BlockQueryInfo, error)
- func extractBlockInfo(page map[string]any) *BlockQueryInfo
- func parseBlockQueryResponse(jsonStr, uuid string) (*BlockQueryInfo, error)
- type CategorizedTasks
- func NewCategorizedTasks() CategorizedTasks
- type LogseqAPI
- func NewLogseqAPI(path, hostURL, apiToken string) LogseqAPI
- type PageJSON
- type RefJSON
- type TaskJSON
- func ExtractTasksFromJSON(jsonStr string) ([]TaskJSON, error)
- type TaskUUID
- type logseqAPIImpl
- func (l *logseqAPIImpl) PostDatascriptQuery(query string) (string, error)
- func (l *logseqAPIImpl) PostQuery(query string) (string, error)
- func (l *logseqAPIImpl) UpsertBlockProperty(uuid, key, value string) error
- func (l *logseqAPIImpl) postAPI(method, query string) (string, error)
Variables¶
ErrBlockNotFoundViaAPI is returned when a block UUID query returns no results from the Logseq API.
ErrBlockNotOnDiskAfterWriteback is returned when a block is still missing from disk after a write-back attempt.
var ErrBlockNotOnDiskAfterWriteback = errors.New("block still not found on disk after write-back")
ErrFailedOpenGraph is returned when the graph cannot be opened.
ErrInvalidResponseStatus is returned when the Logseq API returns a non-200 status code.
ErrMissingConfig is returned when the Logseq API token or host URL is not set.
func BuildRefLookup¶
BuildRefLookup builds a mapping from Logseq ref ID to human-readable name.
func BuildTaskListQuery¶
BuildTaskListQuery assembles the Logseq Datalog query for listing tasks. It matches the Python `lqdpy tasks` query format exactly.
func EnrichTasksWithAncestorTags¶
EnrichTasksWithAncestorTags adds inherited tags from pathRefs to each task's tag set.
func FindBlockOnDisk¶
func FindBlockOnDisk(graph *logseq.Graph, api LogseqAPI, uuid string) (*content.Block, *logseq.Transaction, error)
FindBlockOnDisk locates a task block in the graph by querying the Logseq API for its page, then opening that page and finding the block by its id:: property. If the block is not on disk yet and the API is available, it forces a UUID write-back. This is a future candidate to move into logseq-go once the library supports graph queries directly.
func OpenGraphFromPath¶
OpenGraphFromPath opens a Logseq graph from the given directory path. Delegates to logseqext.OpenGraphFromPath.
func OpenPage¶
OpenPage opens a page in the Logseq graph. Delegates to logseqext.OpenPage.
func OpenPageForBlock¶
func OpenPageForBlock(transaction *logseq.Transaction, blockInfo *BlockQueryInfo) (logseq.Page, error)
OpenPageForBlock opens the appropriate page (journal or regular) for a block described by blockInfo.
func SortTasksByDate¶
SortTasksByDate sorts tasks in place by (JournalDay, Content) ascending, matching Python's Block.sort_by_date behavior.
func TaskDoing¶
TaskDoing checks if the task has the DOING marker.
func TaskFutureScheduled¶
TaskFutureScheduled checks if the task is scheduled for the future (tomorrow onwards) and it's not overdue.
func TaskOverdue¶
TaskOverdue checks if the task is overdue based on deadline or scheduled date.
func buildDirectRefIDSet¶
buildDirectRefIDSet creates a set of ref IDs that are direct references (including page).
func collectAncestorTags¶
func collectAncestorTags(task TaskJSON, directRefIDs map[int]bool, refLookup map[int]string) []string
collectAncestorTags gathers tags from pathRefs that are not in the direct ref set.
func collectRefCandidates¶
collectRefCandidates gathers tag candidates for unresolved ref IDs.
func openBlockFromInfo¶
func openBlockFromInfo(graph *logseq.Graph, blockInfo *BlockQueryInfo, uuid string) (*content.Block, *logseq.Transaction, error)
openBlockFromInfo opens the page described by blockInfo and finds the block by its id:: property.
func populatePageRef¶
populatePageRef adds the page reference for a task to the lookup.
func resolveRefCandidates¶
resolveRefCandidates picks the best tag name for each unresolved ref ID.
type BlockQueryInfo¶
BlockQueryInfo holds the result of a UUID lookup via Logseq API.
func FindBlockByUUID¶
FindBlockByUUID queries the Logseq HTTP API to find a block by UUID. Uses PostDatascriptQuery (logseq.db.datascriptQuery) because the pull syntax required here is not supported by logseq.db.q (PostQuery). The nested {:block/page [*]} expands page attributes; without it, page is just {id: N}.
func extractBlockInfo¶
extractBlockInfo extracts page name and journal info from a page map. Logseq's datascript API returns hyphenated keys: "journal-day", "original-name".
func parseBlockQueryResponse¶
parseBlockQueryResponse parses the JSON response from a block UUID query.
type CategorizedTasks¶
CategorizedTasks holds sets of task UUIDs grouped by category.
type CategorizedTasks struct {
All *set.Set[TaskUUID]
Overdue *set.Set[TaskUUID]
Doing *set.Set[TaskUUID]
FutureScheduled *set.Set[TaskUUID]
TaskLookup map[TaskUUID]TaskJSON
}
func NewCategorizedTasks¶
NewCategorizedTasks creates a new CategorizedTasks with initialized sets.
type LogseqAPI¶
LogseqAPI is the interface for communicating with a running Logseq instance via its HTTP API.
type LogseqAPI interface {
PostQuery(query string) (string, error)
PostDatascriptQuery(query string) (string, error)
// UpsertBlockProperty sets a block property via the Logseq Editor API.
// This causes Logseq to write the property to the .md file immediately,
// which is useful to force the id:: property onto disk for blocks that
// Logseq has tracked internally but not yet written back.
UpsertBlockProperty(uuid, key, value string) error
}
func NewLogseqAPI¶
NewLogseqAPI creates a new LogseqAPI instance.
type PageJSON¶
PageJSON holds page-level metadata from the Logseq API.
type PageJSON struct {
ID int `json:"id"`
JournalDay int `json:"journalDay"`
Name string `json:"name"`
OriginalName string `json:"originalName"`
}
type RefJSON¶
RefJSON represents a reference entry from the Logseq API response.
type TaskJSON¶
TaskJSON represents a task block from the Logseq HTTP API.
type TaskJSON struct {
UUID TaskUUID `json:"uuid"`
Marker string `json:"marker"`
Content string `json:"content"`
Page PageJSON `json:"page"`
Deadline int `json:"deadline"`
Scheduled int `json:"scheduled"`
Refs []RefJSON `json:"refs"`
PathRefs []RefJSON `json:"pathRefs"`
PropertiesTextValues map[string]string `json:"propertiesTextValues"`
}
func ExtractTasksFromJSON¶
ExtractTasksFromJSON parses a JSON string into a slice of TaskJSON.
type TaskUUID¶
TaskUUID is a type alias for task block UUIDs, making it clear when a string represents a Logseq block UUID.
type logseqAPIImpl¶
func (*logseqAPIImpl) PostDatascriptQuery¶
PostDatascriptQuery sends a Datascript query ([:find ...]) to the Logseq API. Use this instead of PostQuery for queries that require pull syntax or complex patterns.
func (*logseqAPIImpl) PostQuery¶
PostQuery sends a query to the Logseq API and returns the result as JSON.
func (*logseqAPIImpl) UpsertBlockProperty¶
UpsertBlockProperty calls logseq.Editor.upsertBlockProperty to set a property on a block. Unlike PostQuery/PostDatascriptQuery which take a single query string, this method passes three separate args: uuid, key, value. Logseq then writes the property to the .md file, which forces the id:: property onto disk for blocks that haven't been persisted yet. Logseq lazy-writes block UUIDs: they exist in its DB but only hit .md files when something triggers a write (a backlink, an edit, or this Editor API call).
func (*logseqAPIImpl) postAPI¶
postAPI is the shared HTTP implementation for PostQuery and PostDatascriptQuery.
backlog¶
Index¶
- Constants
- Variables
- func AddBlockRefToFocusPage(transaction *logseq.Transaction, focusPageTitle, uuid string) error
- func BlockRefExistsUnder(parent *content.Block, uuid logseqapi.TaskUUID) bool
- func FindFirstSectionDivider(page logseq.Page) *content.Block
- func FormatCount(count int, singular, plural string) string
- func MoveBlockRefToTriagedSection(transaction *logseq.Transaction, backlogPage string, uuid logseqapi.TaskUUID, triagedText, scheduledText string) error
- func NormalizeHeaderText(page logseq.Page) bool
- func RemoveBlockRefFromRegularArea(page logseq.Page, uuid logseqapi.TaskUUID)
- func addTasksToCategories(jsonTasks []logseqapi.TaskJSON, tasks *logseqapi.CategorizedTasks, currentTime func() time.Time)
- func applyDirectiveGroup(graph *logseq.Graph, logseqAPI logseqapi.LogseqAPI, items []*blockDirective, currentTime func() time.Time) error
- func applyDirectiveGroupAndCleanup(graph *logseq.Graph, logseqAPI logseqapi.LogseqAPI, grp *directiveGroup, currentTime func() time.Time) bool
- func applyDirectiveToBlock(block *content.Block, directive *blockDirective, currentTime func() time.Time) error
- func applyDirectives(graph *logseq.Graph, logseqAPI logseqapi.LogseqAPI, directives []blockDirective, currentTime func() time.Time) bool
- func blockRefsFromPages(page logseq.Page) *set.Set[string]
- func categorizeBlockRef(node content.Node, blockRef *content.BlockRef, block *content.Block, state *pageState, underScheduled bool, obsoleteBlockRefs, overdueBlockRefs, futureScheduledBlockRefs *set.Set[string], underTriaged bool) bool
- func collectScheduledRefs(page logseq.Page, state *pageState)
- func collectTriagedRefs(page logseq.Page, state *pageState)
- func createTriagedSectionWithRef(page logseq.Page, uuid logseqapi.TaskUUID, triagedText, scheduledText string) error
- func defaultQuery(pageTitle string) string
- func handleBlockRefGuards(blockRef *content.BlockRef, state *pageState, underTriaged, underUnranked, underScheduled bool, obsoleteBlockRefs *set.Set[string]) bool
- func handleDefaultBlockRef(node content.Node, blockRef *content.BlockRef, state *pageState, underScheduled bool, futureScheduledBlockRefs *set.Set[string]) bool
- func insertNewTasks(page logseq.Page, state *pageState, newBlockRefs, overdueBlockRefs, futureScheduledBlockRefs *set.Set[string]) bool
- func insertOverdueTasks(page logseq.Page, state *pageState, overdueBlockRefs *set.Set[string])
- func insertScheduledTasks(page logseq.Page, state *pageState, futureScheduledBlockRefs *set.Set[string])
- func isDuplicateRef(uuid string, state *pageState, underTriaged, underScheduled bool) bool
- func kindName(kind directiveKind) string
- func nextChildHasPin(node content.Node) bool
- func printQuickCaptureURL(graph *logseq.Graph)
- func processAllBlocks(page logseq.Page, state *pageState, obsoleteBlockRefs, overdueBlockRefs, futureScheduledBlockRefs *set.Set[string])
- func processBlockRef(node content.Node, blockRef *content.BlockRef, block *content.Block, state *pageState, obsoleteBlockRefs, overdueBlockRefs, futureScheduledBlockRefs *set.Set[string])
- func queryTasksFromPages(graph *logseq.Graph, logseqAPI logseqapi.LogseqAPI, pageTitles []string, currentTime func() time.Time) (*logseqapi.CategorizedTasks, error)
- func queryTasksFromPagesConcurrent(logseqAPI logseqapi.LogseqAPI, pageTitles []string, tasks *logseqapi.CategorizedTasks, finder logseqext.LogseqFinder, currentTime func() time.Time) (*logseqapi.CategorizedTasks, error)
- func queryTasksFromPagesSequential(logseqAPI logseqapi.LogseqAPI, pageTitles []string, tasks *logseqapi.CategorizedTasks, finder logseqext.LogseqFinder, currentTime func() time.Time) (*logseqapi.CategorizedTasks, error)
- func queryTasksFromSinglePage(logseqAPI logseqapi.LogseqAPI, pageTitle string, finder logseqext.LogseqFinder) ([]logseqapi.TaskJSON, error)
- func recordSectionDivider(block *content.Block, textValue string, state *pageState)
- func reportCounts(state *pageState, save bool) bool
- func scanPageBlocks(page logseq.Page, state *pageState, obsoleteBlockRefs, overdueBlockRefs, futureScheduledBlockRefs *set.Set[string])
- func sortTriagedSection(state *pageState, taskLookup map[logseqapi.TaskUUID]logseqapi.TaskJSON)
- func taskSortKeyLess(left, right taskSortKey) bool
- type Backlog
- func NewBacklog(graph *logseq.Graph, logseqAPI logseqapi.LogseqAPI, reader ConfigReader, currentTime func() time.Time) Backlog
- type Config
- func (c *Config) FindBacklogPageTitle(backlogName string) string
- type ConfigReader
- func NewPageConfigReader(graph *logseq.Graph, configPage string) ConfigReader
- type Header
- func (h Header) Matches(blockText string) bool
- func (h Header) NewHeading() *content.Heading
- func (h Header) String() string
- type Result
- func insertAndRemoveRefs(graph *logseq.Graph, logseqAPI logseqapi.LogseqAPI, pageTitle string, newBlockRefs, obsoleteBlockRefs, overdueBlockRefs, futureScheduledBlockRefs *set.Set[string], taskLookup map[logseqapi.TaskUUID]logseqapi.TaskJSON, currentTime func() time.Time) (*Result, error)
- type SingleBacklogConfig
- type backlogImpl
- func (b *backlogImpl) Graph() *logseq.Graph
- func (b *backlogImpl) ProcessAll(partialNames []string) error
- func (b *backlogImpl) ProcessOne(pageTitle string, funcQueryRefs func() (*logseqapi.CategorizedTasks, error)) (*Result, error)
- type blockDirective
- func detectDirectives(blockRef *content.BlockRef) []blockDirective
- type directiveGroup
- func groupDirectivesByUUID(directives []blockDirective) []directiveGroup
- type directiveKind
- type pageConfigReader
- func (p *pageConfigReader) ReadConfig() (*Config, error)
- type pageState
- func newPageState() *pageState
- type taskSortKey
- func newSortKeys(children content.BlockList, taskLookup map[logseqapi.TaskUUID]logseqapi.TaskJSON) ([]taskSortKey, int)
Constants¶
Section values for the PocketBase `section` field. Ranked=1, Unranked=2, Orphan=3 so that (backlog_index, section, rank) sorts ranked tasks before unranked tasks before orphans within any backlog.
const (
SectionRanked = 1 // manually ordered, above the ⤵️ Unranked tasks divider
SectionUnranked = 2 // under ⤵️ Unranked tasks, 📅 Overdue tasks, ⏰ Scheduled tasks, ✨ New tasks, 🏷️ Triaged tasks
SectionOrphan = 3 // not referenced in any backlog page
)
quickCapturePageName is the Logseq page linked in newly created section dividers so the user can identify dividers inserted by lqd backlog.
Variables¶
Backlog section header definitions. Detection uses Header.Matches (case-insensitive label search). Creation uses Header.String() so the canonical emoji+label+tasks is always written.
var (
HeaderFocus = Header{"🎯", "Focus"}
HeaderOverdue = Header{"📅", "Overdue"}
HeaderNewTasks = Header{"✨", "New"}
HeaderTriaged = Header{"🏷️", "Triaged"}
HeaderScheduled = Header{"⏰", "Scheduled"}
HeaderUnranked = Header{"⤵️", "Unranked"}
)
allHeaders is the full list used to normalize section dividers on write-back.
var allHeaders = []Header{
HeaderFocus, HeaderOverdue, HeaderNewTasks,
HeaderTriaged, HeaderScheduled, HeaderUnranked,
}
focusSectionHeaders are the section dividers on the Focus page. Used to find the insertion point for new block refs.
regularAreaSectionHeaders are section headers that mark the boundary of the regular area. A top-level block matching any of these is a section divider, not part of the regular area.
var regularAreaSectionHeaders = []Header{
HeaderFocus, HeaderOverdue, HeaderNewTasks, HeaderTriaged, HeaderScheduled, HeaderUnranked,
}
func AddBlockRefToFocusPage¶
AddBlockRefToFocusPage adds a block ref ((uuid)) to the Focus page.
func BlockRefExistsUnder¶
BlockRefExistsUnder returns true if a block ref with the given UUID exists anywhere in the descendant tree of parent.
func FindFirstSectionDivider¶
FindFirstSectionDivider finds the first block whose text matches a known section header.
func FormatCount¶
FormatCount returns a string with the count and the singular or plural form of a word.
func MoveBlockRefToTriagedSection¶
func MoveBlockRefToTriagedSection(transaction *logseq.Transaction, backlogPage string, uuid logseqapi.TaskUUID, triagedText, scheduledText string) error
MoveBlockRefToTriagedSection moves a block ref to the Triaged section of a backlog page. If the ref exists in the regular area (not under Focus, New tasks, Overdue, or Scheduled), it is removed from there. If it's already in Triaged, no duplicate is added. Creates the Triaged section if it doesn't exist.
func NormalizeHeaderText¶
NormalizeHeaderText scans all top-level blocks on the page and normalizes any block whose text node contains a known header keyword:
- fixes the text to the canonical "emoji Label tasks" form
- upgrades a plain Paragraph container to a Heading level 1
Returns true if any block was changed.
func RemoveBlockRefFromRegularArea¶
RemoveBlockRefFromRegularArea removes the block ref with the given UUID from the regular area of the page. The regular area is any top-level block ref that is not itself a section divider. Section dividers (Focus, Overdue, New tasks, Triaged, Scheduled, Unranked) and their children are not part of the regular area. Since we walk only top-level blocks, child refs are never seen.
func addTasksToCategories¶
func addTasksToCategories(jsonTasks []logseqapi.TaskJSON, tasks *logseqapi.CategorizedTasks, currentTime func() time.Time)
addTasksToCategories adds tasks to the appropriate categories in CategorizedTasks.
func applyDirectiveGroup¶
func applyDirectiveGroup(graph *logseq.Graph, logseqAPI logseqapi.LogseqAPI, items []*blockDirective, currentTime func() time.Time) error
func applyDirectiveGroupAndCleanup¶
func applyDirectiveGroupAndCleanup(graph *logseq.Graph, logseqAPI logseqapi.LogseqAPI, grp *directiveGroup, currentTime func() time.Time) bool
applyDirectiveGroupAndCleanup applies all directives in a group and strips their nodes. Returns true if the group was successfully applied.
func applyDirectiveToBlock¶
func applyDirectiveToBlock(block *content.Block, directive *blockDirective, currentTime func() time.Time) error
func applyDirectives¶
func applyDirectives(graph *logseq.Graph, logseqAPI logseqapi.LogseqAPI, directives []blockDirective, currentTime func() time.Time) bool
applyDirectives processes all collected directives: modifies the real task block on disk, then strips the directive node from the backlog page.
Directives for the same UUID are grouped and applied in a single transaction so the task file is opened and saved only once (e.g. WAITING + [#B] on the same block ref).
If a block is not on disk and the Logseq API is available, it forces a UUID write-back. If the API is unavailable, it warns and skips. Returns true if any directive was successfully applied (meaning the backlog page AST was mutated and the caller must save the backlog transaction).
func blockRefsFromPages¶
func categorizeBlockRef¶
func categorizeBlockRef(node content.Node, blockRef *content.BlockRef, block *content.Block, state *pageState, underScheduled bool, obsoleteBlockRefs, overdueBlockRefs, futureScheduledBlockRefs *set.Set[string], underTriaged bool) bool
categorizeBlockRef determines whether a block ref should be deleted based on its category.
func collectScheduledRefs¶
collectScheduledRefs performs a first pass to find the Scheduled section divider and record all block-ref UUIDs that are descendants of it.
func collectTriagedRefs¶
collectTriagedRefs performs a first pass over the page to find the Triaged section divider and record all block-ref UUIDs that are descendants of it.
func createTriagedSectionWithRef¶
func createTriagedSectionWithRef(page logseq.Page, uuid logseqapi.TaskUUID, triagedText, scheduledText string) error
createTriagedSectionWithRef creates a new Triaged section with a block reference. It inserts the section before the Scheduled section if found, or appends to the end.
func defaultQuery¶
func handleBlockRefGuards¶
func handleBlockRefGuards(blockRef *content.BlockRef, state *pageState, underTriaged, underUnranked, underScheduled bool, obsoleteBlockRefs *set.Set[string]) bool
handleBlockRefGuards applies early-exit guards for block refs before categorization. Returns true if the caller should return immediately (the ref was handled or preserved as-is).
func handleDefaultBlockRef¶
func handleDefaultBlockRef(node content.Node, blockRef *content.BlockRef, state *pageState, underScheduled bool, futureScheduledBlockRefs *set.Set[string]) bool
handleDefaultBlockRef handles the default case in processBlockRef: moves stale Scheduled tasks back to new, and unpins regular tasks. Returns true if the block should be deleted.
func insertNewTasks¶
func insertNewTasks(page logseq.Page, state *pageState, newBlockRefs, overdueBlockRefs, futureScheduledBlockRefs *set.Set[string]) bool
insertNewTasks inserts new task refs under the new-tasks divider (creating it if needed). Returns updated save flag.
func insertOverdueTasks¶
insertOverdueTasks inserts overdue task refs under the overdue divider (creating it if needed).
Overdue tasks go after the focus section and before new ones so the user can manually decide which overdue tasks deserve focus.
func insertScheduledTasks¶
func insertScheduledTasks(page logseq.Page, state *pageState, futureScheduledBlockRefs *set.Set[string])
insertScheduledTasks moves future-scheduled tasks to the bottom of the page.
func isDuplicateRef¶
isDuplicateRef returns true if blockRef should be removed as a duplicate. Priority order: Scheduled section wins over all others; Triaged section wins over regular area; first-seen wins in the regular/Unranked area.
func kindName¶
func nextChildHasPin¶
func printQuickCaptureURL¶
func processAllBlocks¶
func processAllBlocks(page logseq.Page, state *pageState, obsoleteBlockRefs, overdueBlockRefs, futureScheduledBlockRefs *set.Set[string])
processAllBlocks iterates all blocks on the page, records section dividers, and removes or unpins block refs that are obsolete, overdue, or scheduled.
func processBlockRef¶
func processBlockRef(node content.Node, blockRef *content.BlockRef, block *content.Block, state *pageState, obsoleteBlockRefs, overdueBlockRefs, futureScheduledBlockRefs *set.Set[string])
processBlockRef decides whether to delete, pin, unpin, or keep a block ref. It also collects directive annotations (CANCELED, WAITING, priority) prepended to the ref.
func queryTasksFromPages¶
func queryTasksFromPages(graph *logseq.Graph, logseqAPI logseqapi.LogseqAPI, pageTitles []string, currentTime func() time.Time) (*logseqapi.CategorizedTasks, error)
queryTasksFromPages queries Logseq API for tasks from specified pages. It uses concurrent processing for multiple pages and sequential processing for a single page.
func queryTasksFromPagesConcurrent¶
func queryTasksFromPagesConcurrent(logseqAPI logseqapi.LogseqAPI, pageTitles []string, tasks *logseqapi.CategorizedTasks, finder logseqext.LogseqFinder, currentTime func() time.Time) (*logseqapi.CategorizedTasks, error)
queryTasksFromPagesConcurrent processes pages concurrently using goroutines.
func queryTasksFromPagesSequential¶
func queryTasksFromPagesSequential(logseqAPI logseqapi.LogseqAPI, pageTitles []string, tasks *logseqapi.CategorizedTasks, finder logseqext.LogseqFinder, currentTime func() time.Time) (*logseqapi.CategorizedTasks, error)
queryTasksFromPagesSequential processes pages sequentially (original implementation).
func queryTasksFromSinglePage¶
func queryTasksFromSinglePage(logseqAPI logseqapi.LogseqAPI, pageTitle string, finder logseqext.LogseqFinder) ([]logseqapi.TaskJSON, error)
queryTasksFromSinglePage queries tasks from a single page and returns the JSON tasks.
func recordSectionDivider¶
recordSectionDivider updates state with a block if its text matches a known section header.
func reportCounts¶
reportCounts prints colored summaries and returns updated save flag.
func scanPageBlocks¶
func scanPageBlocks(page logseq.Page, state *pageState, obsoleteBlockRefs, overdueBlockRefs, futureScheduledBlockRefs *set.Set[string])
scanPageBlocks is a two-pass coordinator: first it collects UUIDs already in the Triaged and Scheduled sections, then processes all blocks (which uses those UUIDs for deduplication in the regular area).
func sortTriagedSection¶
sortTriagedSection sorts children of the Triaged divider by priority, date, name, and ID.
func taskSortKeyLess¶
taskSortKeyLess compares two sort keys for ordering in the Triaged section.
type Backlog¶
type Backlog interface {
Graph() *logseq.Graph
ProcessAll(partialNames []string) error
ProcessOne(pageTitle string, funcQueryRefs func() (*logseqapi.CategorizedTasks, error)) (*Result, error)
}
func NewBacklog¶
func NewBacklog(graph *logseq.Graph, logseqAPI logseqapi.LogseqAPI, reader ConfigReader, currentTime func() time.Time) Backlog
type Config¶
func (*Config) FindBacklogPageTitle¶
FindBacklogPageTitle looks up the full backlog page path from config by backlog name. Returns empty string if no matching backlog is found.
type ConfigReader¶
func NewPageConfigReader¶
NewPageConfigReader creates a new ConfigReader that reads the backlog configuration from a Logseq page.
type Header¶
Header represents a backlog section divider. Label is the display word(s) without the "tasks" suffix (e.g. "Focus"). String() always returns "Emoji Label tasks".
func (Header) Matches¶
Matches reports whether blockText contains the label, case-insensitively.
func (Header) NewHeading¶
NewHeading returns a level-1 heading node with the canonical header text followed by a [[quick capture]] page link. Use this when creating a new section divider so the user can identify blocks inserted by lqd backlog.
func (Header) String¶
String returns the canonical display form: "emoji label tasks".
type Result¶
func insertAndRemoveRefs¶
func insertAndRemoveRefs(graph *logseq.Graph, logseqAPI logseqapi.LogseqAPI, pageTitle string, newBlockRefs, obsoleteBlockRefs, overdueBlockRefs, futureScheduledBlockRefs *set.Set[string], taskLookup map[logseqapi.TaskUUID]logseqapi.TaskJSON, currentTime func() time.Time) (*Result, error)
type SingleBacklogConfig¶
type backlogImpl¶
type backlogImpl struct {
graph *logseq.Graph
logseqAPI logseqapi.LogseqAPI
configReader ConfigReader
currentTime func() time.Time
}
func (*backlogImpl) Graph¶
func (*backlogImpl) ProcessAll¶
func (*backlogImpl) ProcessOne¶
func (b *backlogImpl) ProcessOne(pageTitle string, funcQueryRefs func() (*logseqapi.CategorizedTasks, error)) (*Result, error)
type blockDirective¶
blockDirective records a pending task modification detected on a backlog page.
type blockDirective struct {
UUID string
Kind directiveKind
Priority content.PriorityValue // only for directivePriority; PriorityNone for other kinds
DirectiveNode content.Node // the TaskMarker or Priority node to remove after apply
BacklogBlock *content.Block // the block on the backlog page containing the BlockRef
}
func detectDirectives¶
detectDirectives inspects all preceding siblings of the BlockRef in its parent Paragraph. It walks backwards from the BlockRef collecting TaskMarker and Priority nodes until it reaches a non-directive node. Returns all found directives (may be empty).
type directiveGroup¶
directiveGroup holds all directives targeting the same task UUID.
type directiveGroup struct {
items []*blockDirective
uuid string
backlog *content.Block
hasCancel bool
}
func groupDirectivesByUUID¶
groupDirectivesByUUID groups a flat slice of directives into per-UUID groups, preserving insertion order so the task file is opened and saved only once per UUID.
type directiveKind¶
type pageConfigReader¶
func (*pageConfigReader) ReadConfig¶
ReadConfig reads the backlog configuration from a Logseq page.
type pageState¶
pageState holds mutable state accumulated while scanning a backlog page.
type pageState struct {
firstBlock *content.Block
dividerNewTasks *content.Block
dividerOverdue *content.Block
dividerFocus *content.Block
dividerScheduled *content.Block
dividerTriaged *content.Block
dividerUnranked *content.Block
deletedCount int
movedCount int
movedScheduledCount int
movedFromScheduledCount int
unpinnedCount int
result *Result
pinnedBlockRefs *set.Set[string]
triagedBlockRefs *set.Set[string] // UUIDs already in the Triaged section
scheduledBlockRefs *set.Set[string] // UUIDs already in the Scheduled section
seenBlockRefs *set.Set[string] // UUIDs seen during the current scan (for deduplication)
unscheduledRefs *set.Set[string] // UUIDs removed from Scheduled because they lost their scheduled date
directives []blockDirective // pending task modifications found on the backlog page
}
func newPageState¶
type taskSortKey¶
taskSortKey holds the fields used to sort tasks in the Triaged section.
type taskSortKey struct {
priority content.PriorityValue // PriorityNone=0 sorts FIRST (unprioritized at top)
createdDate time.Time // oldest first
firstLine string // alphabetical tiebreaker
id logseqapi.TaskUUID // UUID, guaranteed unique final tiebreaker
block *content.Block // reference to the block for reordering
}
func newSortKeys¶
func newSortKeys(children content.BlockList, taskLookup map[logseqapi.TaskUUID]logseqapi.TaskJSON) ([]taskSortKey, int)
newSortKeys creates sort keys for all children of the Triaged section.
dashboard¶
Index¶
- func MoveToUnranked(graphPath, backlogPageName string, uuids []string) error
- func allSectionHeaders() []backlog.Header
- func collectBlocksToMove(page logseq.Page, uuidSet map[string]bool) []*content.Block
- func ensureUnrankedDivider(page logseq.Page, existing *content.Block) *content.Block
- func isSectionHeaderBlock(block *content.Block) bool
func MoveToUnranked¶
MoveToUnranked moves the given task UUIDs from the regular area of backlogPage to under the "🔢 Unranked tasks" section divider, creating the divider if absent.
graphPath is the path to the Logseq graph root directory. backlogPageName is the page name (e.g. "my-backlog", without .md extension). uuids is the list of task UUIDs to move.
func allSectionHeaders¶
func collectBlocksToMove¶
collectBlocksToMove returns all blocks (top-level or children of section headers) whose block-ref UUID is in uuidSet. Section header blocks themselves are skipped.
func ensureUnrankedDivider¶
ensureUnrankedDivider returns the existing divider block, or creates and inserts one.
func isSectionHeaderBlock¶
isSectionHeaderBlock reports whether block's text matches any known section header.
groom¶
Package groom implements the grooming business logic for Logseq tasks.
Index¶
- Constants
- Variables
- func ApplyGroomAction(graph *logseq.Graph, groomAPI logseqapi.LogseqAPI, action *Action, task map[string]any, opts *WriteOpts) error
- func BuildGroomFilter(now time.Time, thresholdDate time.Time) string
- func CalculateThresholdDate(base time.Time, olderThan string) (time.Time, error)
- func EnsureBlockOnDisk(graph *logseq.Graph, groomAPI logseqapi.LogseqAPI, task map[string]any) (bool, bool)
- func FormatGroomSummary(counts Counts, remaining int, olderThan string) string
- func FormatGroomTask(task map[string]any, index, total int, now time.Time) string
- func FormatTaskAge(isoDate string, now time.Time) string
- func HasRecentDate(task map[string]any, thresholdDate time.Time) bool
- func applyActionToBlock(transaction *logseq.Transaction, action *Action, block *content.Block, groomedDate, uuid string, opts *WriteOpts) error
- func applyCancelAction(block *content.Block, cancelTime time.Time) error
- func applyFocusAction(transaction *logseq.Transaction, block *content.Block, groomedDate, uuid, focusPageTitle string) error
- func applyPriorityAction(transaction *logseq.Transaction, block *content.Block, groomedDate, uuid string, priority content.PriorityValue, opts *WriteOpts) error
- type Action
- func ParseAction(input string, hasBacklog bool) *Action
- type Counts
- type WriteOpts
Constants¶
Groom action name constants. Use these instead of string literals whenever comparing or switching on GroomAction.Name to avoid typos and enable refactoring.
const (
GroomActionCancel = "cancel"
GroomActionFocus = "focus"
GroomActionPriorityHigh = "priority-high"
GroomActionPriorityMedium = "priority-medium"
GroomActionPriorityLow = "priority-low"
GroomActionSkip = "skip"
GroomActionQuit = "quit"
)
GroomPropertyGroomed is the Logseq block property key written when a task is groomed.
Variables¶
ErrBlockIDMissingInFile is returned when a block UUID exists in the Logseq API/DB but the id:: property is absent from the .md file. This happens for blocks that were assigned a UUID internally by Logseq but have not yet had it written to disk. The fix is to open the block in Logseq (which forces the id:: write-back), then re-run groom.
errEmptyDuration is returned when an empty duration string is provided.
groomActions maps single-letter inputs to their corresponding actions.
var groomActions = map[string]*Action{
"x": {Name: GroomActionCancel, SetsGroomed: true, RequiresFile: true},
"f": {Name: GroomActionFocus, SetsGroomed: true, RequiresFile: true},
"a": {Name: GroomActionPriorityHigh, SetsGroomed: true, RequiresFile: true, Priority: content.PriorityHigh},
"b": {Name: GroomActionPriorityMedium, SetsGroomed: true, RequiresFile: true, Priority: content.PriorityMedium},
"c": {Name: GroomActionPriorityLow, SetsGroomed: true, RequiresFile: true, Priority: content.PriorityLow},
"s": {Name: GroomActionSkip, SetsGroomed: false, RequiresFile: false},
"q": {Name: GroomActionQuit, SetsGroomed: false, RequiresFile: false},
}
func ApplyGroomAction¶
func ApplyGroomAction(graph *logseq.Graph, groomAPI logseqapi.LogseqAPI, action *Action, task map[string]any, opts *WriteOpts) error
ApplyGroomAction applies a groom action to a Logseq block.
func BuildGroomFilter¶
BuildGroomFilter builds the PocketBase filter for stale tasks. thresholdDate is the cutoff: tasks with journal before this date are considered stale.
NOTE: Do NOT add scheduled/deadline filters here. PocketBase date fields store null (not empty string) when unset, so `scheduled="` never matches and tasks slip through. Future-date filtering is done in Go via HasFutureDate after fetching. See CLAUDE.md.
func CalculateThresholdDate¶
CalculateThresholdDate subtracts a human-readable duration from a base time. Uses karrick/tparse for calendar-aware math (proper month/year handling). Accepts formats like: "5 years", "90 days", "6 months", "1 year", "2 weeks".
func EnsureBlockOnDisk¶
func EnsureBlockOnDisk(graph *logseq.Graph, groomAPI logseqapi.LogseqAPI, task map[string]any) (bool, bool)
EnsureBlockOnDisk checks whether a task's block UUID is present in the Logseq .md file. If the id:: property is missing from disk (Logseq lazy-writes UUIDs), it calls logseq.Editor.upsertBlockProperty via the HTTP API to force Logseq to write it immediately.
Background: Logseq assigns UUIDs to blocks internally but only writes them to .md files when triggered (e.g. a backlink is created, the block is edited, or the Editor API writes it). Without this, groom cannot locate or modify the block by its UUID and would skip the task.
The caller should print the log message to stdout so the user knows what happened.
Returns (exists bool, upserted bool):
- exists=true means the block is on disk (possibly after the upsert triggered a write).
- upserted=true means the API call was made (Logseq may need a moment to flush; a second groom run will reliably find it even if the file hasn't been updated within this process).
func FormatGroomSummary¶
FormatGroomSummary formats the end-of-session summary.
func FormatGroomTask¶
FormatGroomTask formats a PB task record for terminal display.
func FormatTaskAge¶
FormatTaskAge returns a human-readable age string like "9 years ago".
func HasRecentDate¶
HasRecentDate reports whether a task should be excluded from the groom queue because its scheduled or deadline date is newer than thresholdDate (i.e. not yet stale).
A task with scheduled/deadline older than the threshold is stale enough to review. A task with a date newer than the threshold is still active and should be skipped. A task with no date is unaffected by this check.
PocketBase date fields store null (not empty string) when unset, so these comparisons cannot be done reliably in PocketBase query strings. Filter in Go instead. See CLAUDE.md.
func applyActionToBlock¶
func applyActionToBlock(transaction *logseq.Transaction, action *Action, block *content.Block, groomedDate, uuid string, opts *WriteOpts) error
applyActionToBlock applies the specific groom action to a block.
func applyCancelAction¶
applyCancelAction sets the task as canceled. cancelled:: date property is set by SetTaskCanceled; groomed:: is intentionally omitted. Kept as a named function for symmetry with applyFocusAction and applyPriorityAction.
func applyFocusAction¶
func applyFocusAction(transaction *logseq.Transaction, block *content.Block, groomedDate, uuid, focusPageTitle string) error
applyFocusAction marks the block groomed and adds a reference to the Focus page.
func applyPriorityAction¶
func applyPriorityAction(transaction *logseq.Transaction, block *content.Block, groomedDate, uuid string, priority content.PriorityValue, opts *WriteOpts) error
applyPriorityAction sets priority on the block, marks it groomed, and adds a reference to the Triaged section of the backlog page (if the task has a backlog).
type Action¶
Action represents a user's grooming decision.
type Action struct {
Name string
SetsGroomed bool
RequiresFile bool
Priority content.PriorityValue
}
func ParseAction¶
ParseAction converts a single-letter input to an Action. Returns nil if the input is invalid or requires backlog when none exists.
type Counts¶
Counts tracks how many tasks received each action.
type Counts struct {
Cancelled int
Focused int
PriorityHigh int
PriorityMedium int
PriorityLow int
Skipped int
}
type WriteOpts¶
WriteOpts holds config-derived values needed for write-back operations. These are passed from cmd/ to avoid circular imports with internal/backlog.
type WriteOpts struct {
FocusPageTitle string
BacklogPageTitle string
TriagedSectionText string
ScheduledSectionText string
CurrentTime func() time.Time
}
logseqext¶
Package logseqext provides generic extensions to the logseq-go library. Functions here are candidates for upstreaming into logseq-go once that library is restructured.
This package must remain self-contained: it may only import logseq-go, the Go standard library, and golang.org/x packages. It must never import logseq-doctor internal packages (doing so would also create a cyclic import).
Index¶
- Constants
- Variables
- func AddSibling(page logseq.Page, newBlock, before *content.Block, after ...*content.Block)
- func AddTask(opts *AddTaskOptions) error
- func BlockContentText(block *content.Block) string
- func BlockProperties(block *content.Block) *content.Properties
- func CleanTaskName(taskContent, marker string) string
- func DateYYYYMMDD(time time.Time) int
- func ExtractBlockRefUUID(block *content.Block) string
- func ExtractBlockRefUUIDs(page logseq.Page) []string
- func ExtractDirectTags(contentText string) []string
- func ExtractFirstLine(taskContent string) string
- func FindBlockByIDProperty(page logseq.Page, uuid string) *content.Block
- func FindBlockByKey(page logseq.Page, parentBlock *content.Block, key string) *content.Block
- func FindBlockContainingText(page logseq.Page, searchText string) *content.Block
- func FindTaskMarkerByKey(page logseq.Page, parentBlock *content.Block, key string) *content.TaskMarker
- func FormatLogseqDate(t time.Time) string
- func JournalDayToTime(journalDay int) time.Time
- func NormalizeTagPrefixes(tags []string)
- func OpenGraphFromPath(path string) *logseq.Graph
- func OpenPage(graph *logseq.Graph, pageTitle string) logseq.Page
- func ParseLogseqDate(dateStr string) (time.Time, error)
- func ParsePriorityFromContent(contentStr string) content.PriorityValue
- func ReadJournalTitleFormat(graphPath string) string
- func RemoveEmptyBlocks(save bool, blocks ...*content.Block) bool
- func SetPriority(block *content.Block, priority content.PriorityValue) error
- func SetTaskCanceled(block *content.Block, date ...time.Time) error
- func SetTaskTodo(block *content.Block) error
- func SetTaskWaiting(block *content.Block) error
- func UniqueStrings(items []string) []string
- func blockHasIDProperty(block *content.Block, uuid string) bool
- func containsTextCaseInsensitive(node content.Node, searchTextLower string) bool
- func isDescendantBlock(block, ancestor *content.Block) bool
- func replaceCurrentPage(query, pageTitle string) string
- func slugifyTag(s string) string
- func updateExistingTask(existingTaskMarker *content.TaskMarker, opts *AddTaskOptions) error
- func updateTaskBackToTodo(taskMarker *content.TaskMarker, newName string) error
- type AddTaskOptions
- type LogseqFinder
- func NewLogseqFinder(graph *logseq.Graph) LogseqFinder
- type SectionedUUID
- func ExtractSectionedBlockRefUUIDs(page logseq.Page, unrankedSectionTexts []string) []SectionedUUID
- type logseqFinderImpl
- func (f logseqFinderImpl) FindFirstQuery(pageTitle string) string
Constants¶
JournalDayDivisorMonth is used to extract the month from a journalDay integer (YYYYMMDD).
JournalDayDivisorYear is used to extract the year from a journalDay integer (YYYYMMDD).
PropertyCancelled is the Logseq block property key written when a task is cancelled.
logseqDateFormat is the Go format string for the current graph's journal title format. Derived from config.edn `:journal/page-title-format` = "EEEE, dd.MM.yyyy" → "Monday, 02.01.2006".
Variables¶
Regex patterns for extracting tags from task content.
var (
markdownLinkPattern = regexp.MustCompile(`\[([^\]]+)\]\([^\)]+\)`)
pageRefPattern = regexp.MustCompile(`\[\[([^\]]+)\]\]`)
bracketHashtagPatter = regexp.MustCompile(`#\[\[([^\]]+)\]\]`)
simpleHashtagPattern = regexp.MustCompile(`#([\w\-/]+)`)
timePrefixPattern = regexp.MustCompile(`^\*?\*?(\d{1,2}:\d{2})\*?\*?\s+`)
)
ErrNoParagraph is returned when SetPriority is called on a block with no paragraph.
journalTitleFormatRe matches the :journal/page-title-format line in config.edn.
priorityPrefixRegex matches a priority marker at the start of a line for stripping.
priorityRegex matches Logseq priority markers like [#A], [#B], [#C] in content strings.
func AddSibling¶
AddSibling inserts newBlock into page relative to the given anchor blocks. It inserts after the last non-nil block in after, or before the before block, or appends to the page if neither is provided.
func AddTask¶
AddTask adds a task to Logseq. If Key is provided, it searches for an existing task containing that key (case-insensitive) and updates it. Otherwise, creates a new task. If Page is provided, adds to that page. Otherwise, adds to journal for Date. If BlockText is provided, adds as a child of the first block containing that text.
func BlockContentText¶
BlockContentText extracts the text content from a block's content nodes.
func BlockProperties¶
BlockProperties returns the Properties node for a task block. logseq-go's block.Properties() only checks the first child — task blocks have a Paragraph first, so it would prepend a new empty Properties node before the TODO line. The parser creates an empty Properties placeholder at position 0; the real properties (id::, collapsed::, etc.) appear after the first Paragraph. This helper finds the first Properties that comes after a Paragraph in the content.
func CleanTaskName¶
CleanTaskName extracts the task name from content: first line, marker stripped, time prefix stripped.
func DateYYYYMMDD¶
DateYYYYMMDD returns the current date in YYYYMMDD format.
func ExtractBlockRefUUID¶
ExtractBlockRefUUID extracts the UUID from a block ref inside a child block.
func ExtractBlockRefUUIDs¶
ExtractBlockRefUUIDs extracts all block ref UUIDs from a page (ordered).
func ExtractDirectTags¶
ExtractDirectTags extracts #hashtags and [[page refs]] from content text.
func ExtractFirstLine¶
ExtractFirstLine extracts the first line of task content, stripping the marker and priority.
func FindBlockByIDProperty¶
FindBlockByIDProperty finds a block in a page by its id:: property value. It searches Properties nodes within block content because logseq-go's block.Properties() only checks the first child — task blocks have a paragraph first, then properties.
func FindBlockByKey¶
FindBlockByKey searches for a block containing the specified key (case-insensitive). If parentBlock is provided, searches only among its children. Otherwise, searches in the entire page. Returns the Block if found, nil otherwise.
func FindBlockContainingText¶
FindBlockContainingText searches for the first block containing the specified text using FindDeep.
func FindTaskMarkerByKey¶
func FindTaskMarkerByKey(page logseq.Page, parentBlock *content.Block, key string) *content.TaskMarker
FindTaskMarkerByKey searches for a task marker containing the specified key (case-insensitive). If parentBlock is provided, searches only among its children. Otherwise, searches in the entire page. Returns the TaskMarker if found, nil otherwise.
func FormatLogseqDate¶
FormatLogseqDate formats a time.Time as a Logseq date string: "[[Saturday, 21.03.2026]]".
func JournalDayToTime¶
JournalDayToTime converts a Logseq journalDay integer (YYYYMMDD) to a time.Time. Returns zero time for zero input.
func NormalizeTagPrefixes¶
NormalizeTagPrefixes ensures all tags start with "#", are lowercase, and slugified (accents removed, non-alphanumeric chars stripped) to match Python's slugify(t, separator=").
func OpenGraphFromPath¶
OpenGraphFromPath opens a Logseq graph from the given directory path. Aborts the program if path is empty or the graph cannot be opened, to avoid error-handling boilerplate at every call site.
func OpenPage¶
OpenPage opens a page in the Logseq graph. Aborts the program in case of error to avoid boilerplate at every call site.
func ParseLogseqDate¶
ParseLogseqDate parses a Logseq date string like "[[Saturday, 21.03.2026]]" into a time.Time. Returns zero time (not error) for empty or unparseable strings.
func ParsePriorityFromContent¶
ParsePriorityFromContent extracts a priority value from a Logseq API content string. It looks for [#A], [#B], or [#C] patterns and returns the corresponding PriorityValue. Returns PriorityNone if no priority marker is found.
func ReadJournalTitleFormat¶
ReadJournalTitleFormat reads the JS-style date format string used for journal page titles from logseq/config.edn (e.g. "EEEE, dd.MM.yyyy"). Returns the Logseq default "EEE do, MMM yyyy" if the file cannot be read or the key is absent. This is a candidate for upstreaming to logseq-go.
func RemoveEmptyBlocks¶
RemoveEmptyBlocks removes blocks that have no child blocks and returns true if any were removed.
func SetPriority¶
SetPriority sets or replaces the priority marker ([#A]/[#B]/[#C]) on a block. If a Priority node exists, it is updated in place. Otherwise, a new Priority node is inserted after the TaskMarker (or at the start of the first paragraph for plain blocks).
func SetTaskCanceled¶
SetTaskCanceled changes the task marker to CANCELED and sets the cancelled:: date property. An optional date argument overrides the default (today). The cancelled:: property is always set.
func SetTaskTodo¶
SetTaskTodo changes the task marker to TODO using logseq-go's WithStatus API.
func SetTaskWaiting¶
SetTaskWaiting changes the task marker to WAITING using logseq-go's WithStatus API.
func UniqueStrings¶
UniqueStrings deduplicates a string slice preserving order.
func blockHasIDProperty¶
blockHasIDProperty checks whether a block's id:: property matches the given UUID. Searches inside content nodes to handle task blocks where properties follow the paragraph.
func containsTextCaseInsensitive¶
containsTextCaseInsensitive checks if a node contains the specified text (case-insensitive). It checks Text, PageLink, Hashtag, CodeSpan, and Link nodes (including link URLs and text content).
func isDescendantBlock¶
isDescendantBlock returns true if block is a descendant of ancestor by traversing the parent chain upward.
func replaceCurrentPage¶
replaceCurrentPage replaces the current page placeholder in the query with the actual page name.
func slugifyTag¶
slugifyTag removes accents and non-alphanumeric characters, then lowercases — matching Python's slugify(tag, separator=") behaviour.
func updateExistingTask¶
func updateTaskBackToTodo¶
type AddTaskOptions¶
AddTaskOptions contains options for adding a task to Logseq.
type AddTaskOptions struct {
Graph *logseq.Graph
Date time.Time
Page string // Page name to add the task to (empty = journal)
BlockText string // Partial text to search for in parent blocks
Key string // Unique key to search for existing task (case-insensitive)
Name string // Short name of the task
TimeNow func() time.Time // For testing
}
type LogseqFinder¶
LogseqFinder provides methods for searching within a Logseq graph.
func NewLogseqFinder¶
NewLogseqFinder creates a new LogseqFinder backed by the given graph.
type SectionedUUID¶
SectionedUUID pairs a block-ref UUID with whether it lives under a section header (ranked=false means it is under Overdue, New tasks, Triaged, Scheduled, or Unranked — any divider that is not the implicit "ranked" area at the top).
func ExtractSectionedBlockRefUUIDs¶
func ExtractSectionedBlockRefUUIDs(page logseq.Page, unrankedSectionTexts []string) []SectionedUUID
ExtractSectionedBlockRefUUIDs scans a backlog page and returns every block-ref UUID together with whether it is in the ranked area (above all section dividers) or the unranked area (under any divider whose text matches one of unrankedSectionTexts, case-insensitive substring).
The ranked area is defined as top-level blocks that are NOT section-header blocks and NOT descendants of any section-header block.
type logseqFinderImpl¶
func (logseqFinderImpl) FindFirstQuery¶
FindFirstQuery returns the first query found on the given page, with the current page placeholder replaced by the actual page name. Returns empty string if no query is found or the page cannot be opened.
pocketbase¶
Index¶
- Constants
- Variables
- func FormatDateLocal(utcStr string) string
- func IsReady(healthURL string) bool
- func LqdTasksSchema() map[string]any
- func StartPocketBase(workDir string) (*exec.Cmd, error)
- func WaitForReady(healthURL string, timeout time.Duration) error
- func findPocketBase() (string, error)
- func lqdTasksDataFields() []map[string]any
- func lqdTasksFields() []map[string]any
- func lqdTasksIdentityFields() []map[string]any
- type Client
- func NewClient(baseURL, username, password string) (*Client, error)
- func NewClientWithToken(baseURL, token string) *Client
- func (c *Client) CollectionExists(name string) (bool, error)
- func (c *Client) CreateCollection(schema map[string]any) error
- func (c *Client) CreateRecord(collection string, data map[string]any) error
- func (c *Client) DeleteCollection(name string) error
- func (c *Client) DeleteRecord(collection, recordID string) error
- func (c *Client) FetchRecords(collection, filter, sort string, limit ...int) ([]map[string]any, error)
- func (c *Client) Token() string
- func (c *Client) UpdateRecord(collection, recordID string, data map[string]any) error
- func (c *Client) authenticate(username, password string) error
- func (c *Client) doRequest(method, path string, body io.Reader) (*http.Response, error)
Constants¶
DateFormat is the ISO date format used for PocketBase date/datetime record fields.
idMaxLength is UUID (36) + underscore (1) + backlog name (up to 50) = 87.
Variables¶
Sentinel errors for PocketBase client operations.
var (
ErrCannotConnect = errors.New("cannot connect to PocketBase")
ErrAuthFailed = errors.New("PocketBase authentication failed")
ErrUnexpectedStatus = errors.New("unexpected status from PocketBase")
)
ErrPocketBaseNotFound is returned when the pocketbase binary cannot be located.
var ErrPocketBaseNotFound = errors.New("pocketbase executable not found in $PATH or ~/.local/bin")
ErrWaitTimeout is returned when WaitForReady times out.
taskStatusValues are the task status values stored in the PocketBase select field.
var taskStatusValues = []string{
content.TaskStringTodo, content.TaskStringDoing, content.TaskStringDone,
content.TaskStringWaiting, content.TaskStringCanceled,
}
func FormatDateLocal¶
FormatDateLocal parses a PocketBase UTC datetime string and returns it in local time as "YYYY-MM-DD HH:MM". Returns the raw string if parsing fails.
func IsReady¶
IsReady returns true if healthURL responds with 200 OK within one poll timeout.
func LqdTasksSchema¶
LqdTasksSchema returns the PocketBase collection schema for lqd_tasks. Go code is the source of truth — not PB migrations.
func StartPocketBase¶
StartPocketBase starts pocketbase serve as a managed subprocess from workDir. Stdout and stderr are inherited so PocketBase logs appear in the same terminal with color. The caller is responsible for calling cmd.Process.Kill() on shutdown.
func WaitForReady¶
WaitForReady polls healthURL until it returns 200 OK or timeout elapses.
func findPocketBase¶
findPocketBase resolves the pocketbase executable path. It checks $PATH first, then falls back to \~/.local/bin/pocketbase.
func lqdTasksDataFields¶
func lqdTasksFields¶
func lqdTasksIdentityFields¶
type Client¶
Client is a minimal PocketBase HTTP client.
func NewClient¶
NewClient authenticates with PocketBase and returns a ready-to-use client.
func NewClientWithToken¶
NewClientWithToken returns a client pre-loaded with an existing auth token. Use when a token has already been obtained (e.g. at dashboard startup) to avoid a redundant authentication round-trip.
func (*Client) CollectionExists¶
CollectionExists checks if a collection exists in PocketBase.
func (*Client) CreateCollection¶
CreateCollection creates a new collection with the given schema.
func (*Client) CreateRecord¶
CreateRecord creates a record in the given collection.
func (*Client) DeleteCollection¶
DeleteCollection deletes a collection by name. It first fetches the collection to get its ID.
func (*Client) DeleteRecord¶
DeleteRecord deletes a record by ID from the given collection.
func (*Client) FetchRecords¶
func (c *Client) FetchRecords(collection, filter, sort string, limit ...int) ([]map[string]any, error)
FetchRecords fetches all records from a collection, handling pagination. Optional filter and sort parameters are passed as PB query params. If limit > 0, fetches at most that many records (single page).
func (*Client) Token¶
Token returns the current authentication token.
func (*Client) UpdateRecord¶
UpdateRecord updates a record by ID in the given collection.
func (*Client) authenticate¶
func (*Client) doRequest¶
doRequest sends an authenticated HTTP request to PocketBase.
serve¶
Index¶
func NewProxy¶
NewProxy returns an http.Handler that reverse-proxies to pbURL, injecting the given Bearer token into every proxied request. Any Authorization header supplied by the browser client is overwritten.
lqdsync¶
Index¶
- Constants
- func CalculateRanks(backlogs map[string][]string, backlogOrder []string) map[string][]RankInfo
- func DiffRecords(existing, desired []map[string]any) ([]map[string]any, []map[string]any, []string)
- func TaskToRecord(task logseqapi.TaskJSON, rank *RankInfo, enrichedTags string, currentTime func() time.Time) map[string]any
- func determineSortDate(scheduledISO, deadlineISO, today string) string
- func extractRankFields(rank *RankInfo) (string, int, int, int)
- func indexRecordsByID(records []map[string]any) map[string]map[string]any
- func isDateBeforeToday(dateISO string, today time.Time) bool
- func isOverdue(scheduledISO, deadlineISO string, currentTime func() time.Time) bool
- func parseGroomedDate(task logseqapi.TaskJSON) string
- func recordChanged(existing, desired map[string]any) bool
- func syncUpdateFields() []string
- func yyyymmddToDateOnly(dateInt int) string
- func yyyymmddToLocalISO(dateInt int) string
- type RankInfo
Constants¶
const (
hoursPerDay = 24
rankSeedFactor = 1000 // rank is seeded as position × rankSeedFactor on first sync
)
func CalculateRanks¶
CalculateRanks assigns ranks for all (uuid, backlog) pairs. A task that appears in multiple backlogs gets one RankInfo per backlog.
func DiffRecords¶
func DiffRecords(existing, desired []map[string]any) ([]map[string]any, []map[string]any, []string)
DiffRecords compares existing PB records with desired records. Returns slices of records to create, update, and IDs to delete.
func TaskToRecord¶
func TaskToRecord(task logseqapi.TaskJSON, rank *RankInfo, enrichedTags string, currentTime func() time.Time) map[string]any
TaskToRecord converts a TaskJSON + optional RankInfo to a PocketBase record map.
func determineSortDate¶
determineSortDate picks the best sort date: scheduled > deadline > today (fallback).
func extractRankFields¶
extractRankFields returns backlog name, index, section, and rank from a RankInfo pointer.
func indexRecordsByID¶
indexRecordsByID builds a map from record "id" field to the full record.
func isDateBeforeToday¶
isDateBeforeToday returns true if the RFC3339 date string is non-empty and before today.
func isOverdue¶
isOverdue checks if a task is past its scheduled or deadline date. Parses RFC3339 date strings (produced by yyyymmddLocalISO) and compares as dates.
func parseGroomedDate¶
parseGroomedDate extracts the groomed date from task properties.
func recordChanged¶
recordChanged checks if any sync-relevant fields differ between two records.
func syncUpdateFields¶
syncUpdateFields returns the fields checked by recordChanged to detect updates. rank is intentionally excluded — the UI owns rank after record creation.
func yyyymmddToDateOnly¶
yyyymmddToDateOnly converts a YYYYMMDD integer to a plain date string (YYYY-MM-DD). Returns empty string for zero values. Used for journal dates where Python sends date.isoformat() without timezone.
func yyyymmddToLocalISO¶
yyyymmddToLocalISO converts a YYYYMMDD integer to an RFC3339 datetime string with local timezone offset. Returns empty string for zero values. Used for scheduled/deadline dates where Python uses datetime.strptime().astimezone().isoformat() which includes the local timezone offset, causing PocketBase to shift to UTC on storage.
type RankInfo¶
RankInfo holds backlog rank data for a task.
type RankInfo struct {
BacklogName string
BacklogIndex int
Section int // backlog.SectionRanked, SectionUnranked, or SectionOrphan
Rank int
}
testutils¶
Index¶
- Constants
- Variables
- func AssertGoldenJournals(t *testing.T, graph *logseq.Graph, caseDirName string, pages []string)
- func AssertGoldenPages(t *testing.T, graph *logseq.Graph, caseDirName string, pages []string)
- func AssertPagesDontExist(t *testing.T, graph *logseq.Graph, pages []string)
- func CaptureOutput(function func()) string
- func ExportFixtureUUID(f *TaskFixture, slug string) string
- func NewStubGraph(t *testing.T, subDir string) *logseq.Graph
- func RelativeTime() time.Time
- func assertGoldenContent(t *testing.T, graph *logseq.Graph, journals bool, caseDirName string, pages []string)
- func buildAPIResponse(blocks []Block, slugToUUIDMap map[string]string, now time.Time) string
- func buildBlockContent(block Block, uuid string) string
- func buildBlockProperties(block Block, uuid string, now time.Time) (map[string]string, []string)
- func buildBlockRefs(block Block, tagIDs map[string]int, pageID int) ([]refJSON, []refJSON)
- func buildSlugMap(blocks []Block) (map[string]string, map[string]string)
- func buildTagIDs(blocks []Block) map[string]int
- func collapseSlugs(content string, uuidToSlugMap map[string]string) string
- func copyDirTree(src, dst string) error
- func expandSlugs(content string, slugToUUIDMap map[string]string) string
- func resolveJournalDayInt(rel string, now time.Time) int
- func resolveRelativeDate(rel string, now time.Time) (time.Time, error)
- func slugToUUID(slug string) string
- func toJournalDayInt(t time.Time) int
- type Block
- func Task(slug, status, text string, opts ...BlockOpt) Block
- type BlockOpt
- func WithDeadline(rel string) BlockOpt
- func WithExtraProps(props map[string]string) BlockOpt
- func WithGroomed(rel string) BlockOpt
- func WithJournalDay(day string) BlockOpt
- func WithPriority(p string) BlockOpt
- func WithScheduled(rel string) BlockOpt
- func WithTags(tags ...string) BlockOpt
- type TaskFixture
- func NewFixture(t *testing.T, blocks ...Block) *TaskFixture
- func (f *TaskFixture) Add(blocks ...Block) *TaskFixture
- func (f *TaskFixture) AssertGoldenJournals(t *testing.T, graph *logseq.Graph, caseDirName string, pages []string)
- func (f *TaskFixture) AssertGoldenPages(t *testing.T, graph *logseq.Graph, caseDirName string, pages []string)
- func (f *TaskFixture) FakeBacklog(t *testing.T, configPage, caseDirName string) backlog.Backlog
- func (f *TaskFixture) FakeBacklogWithUUIDPages(t *testing.T, configPage, caseDirName string, uuidPageNames map[string]string) backlog.Backlog
- func (f *TaskFixture) assertGoldenFiles(t *testing.T, graph *logseq.Graph, journals bool, caseDirName string, pages []string)
- func (f *TaskFixture) copyExpandedMDs(t *testing.T, src, dst string)
- func (f *TaskFixture) fakeAPI(t *testing.T) *mockLogseqAPI
- func (f *TaskFixture) fakeGraph(t *testing.T, caseDirName string) *logseq.Graph
- type blockJSON
- type mockLogseqAPI
- func newMockLogseqAPIFromMap(t *testing.T, responses map[string]string) *mockLogseqAPI
- func (m *mockLogseqAPI) PostDatascriptQuery(query string) (string, error)
- func (m *mockLogseqAPI) PostQuery(query string) (string, error)
- func (m *mockLogseqAPI) UpsertBlockProperty(_ string, _ string, _ string) error
- func (m *mockLogseqAPI) WithUUIDPageResponse(uuid, pageJSON string) *mockLogseqAPI
- type pageJSON
- func buildJournalPage(block Block, now time.Time, pageID int) pageJSON
- type refJSON
Constants¶
blockIDBase is the starting numeric ID for fixture blocks.
dirPerm is the permission used for directories created by the fixture framework.
filePerm is the permission used for files created by the fixture framework.
pageIDBase is the starting numeric ID for fixture pages.
Variables¶
ExportBuildAPIResponse exposes buildAPIResponse for white-box testing.
ExportBuildSlugMap exposes buildSlugMap for white-box testing.
ExportCollapseSlugs exposes collapseSlugs for white-box testing.
ExportExpandSlugs exposes expandSlugs for white-box testing.
ExportResolveRelativeDate exposes resolveRelativeDate for white-box testing.
slugRefPattern matches (( any slug with spaces )) in committed .md files.
func AssertGoldenJournals¶
func AssertGoldenPages¶
func AssertPagesDontExist¶
func CaptureOutput¶
CaptureOutput captures both stdout and stderr. It also works with the "color" package.
func ExportFixtureUUID¶
ExportFixtureUUID returns the UUID for the given slug in the fixture (for white-box testing).
func NewStubGraph¶
NewStubGraph creates a test graph using the new directory structure. It uses graph-template as the base and loads test data from testdata/{subDir}/journals and testdata/{subDir}/pages.
func RelativeTime¶
func assertGoldenContent¶
func assertGoldenContent(t *testing.T, graph *logseq.Graph, journals bool, caseDirName string, pages []string)
func buildAPIResponse¶
buildAPIResponse generates a JSON array string from blocks, matching the Logseq API response format. now is used to resolve relative Scheduled/Deadline/Groomed date strings.
func buildBlockContent¶
buildBlockContent constructs the Logseq block content string.
func buildBlockProperties¶
buildBlockProperties constructs the properties map and order slice for a block.
func buildBlockRefs¶
buildBlockRefs builds the refs and pathRefs slices for a block.
func buildSlugMap¶
buildSlugMap builds slug→UUID and UUID→slug maps from a slice of blocks. Panics if two different slugs produce the same UUID (collision detection).
func buildTagIDs¶
buildTagIDs assigns stable numeric IDs to all distinct tags across blocks.
func collapseSlugs¶
collapseSlugs replaces UUIDs with (( slug )) in content. Handles both ((uuid)) block refs and bare UUIDs (e.g. in id:: property lines). UUIDs not present in uuidToSlugMap are left unchanged.
func copyDirTree¶
copyDirTree copies a directory tree recursively from src to dst.
func expandSlugs¶
expandSlugs replaces (( slug )) with ((uuid)) in content. Slugs not present in slugToUUIDMap are left unchanged.
func resolveJournalDayInt¶
resolveJournalDayInt resolves a relative date string to a Logseq journalDay int. Returns 0 if rel is empty or invalid.
func resolveRelativeDate¶
resolveRelativeDate parses a relative date string like "+3d", "-1w", "+2m", "+1y" and returns the resolved time. Empty string returns the zero time. "0" returns now. Supported units: d (days), w (weeks), m (months), y (years).
func slugToUUID¶
slugToUUID derives a deterministic UUID from a slug using FNV-32a. Format: {h8}-0000-0000-0000-{h8}0000 where h8 = zero-padded 8-char hex of fnv32a(slug).
func toJournalDayInt¶
toJournalDayInt converts a time.Time to the Logseq journalDay integer format (YYYYMMDD).
type Block¶
Block is a generic Logseq block fixture. Most callers use the Task() constructor which sets Marker. The framework is built on Block internally so non-task blocks (plain content, property blocks) can be added in the future without architectural changes — just add a Block constructor alongside Task().
type Block struct {
Slug string
Marker string // content.TaskString* from logseq-go (e.g. TaskStringTodo), or "" for non-task blocks
Text string
Tags []string
Scheduled string // relative date: "+3d", "-1w", "+2m", "+1y", "" for none
Deadline string // same format as Scheduled
Priority string // "A", "B", "C", or ""
Groomed string // relative date for groomed:: property, "" means no property
JournalDay string // "2025-03-02" format; defaults to baseline date (2025-04-13)
ExtraProps map[string]string // arbitrary Logseq block properties
}
func Task¶
Task constructs a Block with Marker set. Use BlockOpts for optional fields.
type BlockOpt¶
BlockOpt is a functional option for configuring a Block.
func WithDeadline¶
WithDeadline sets the Deadline relative date.
func WithExtraProps¶
WithExtraProps sets arbitrary Logseq block properties.
func WithGroomed¶
WithGroomed sets the Groomed relative date (e.g. "-7d").
func WithJournalDay¶
WithJournalDay sets the journal page date ("2025-03-02").
func WithPriority¶
WithPriority sets the Priority ("A", "B", or "C").
func WithScheduled¶
WithScheduled sets the Scheduled relative date (e.g. "+3d", "-1w").
func WithTags¶
WithTags sets the Tags field.
type TaskFixture¶
TaskFixture holds block definitions and generates fake API responses for a test.
type TaskFixture struct {
t *testing.T
blocks []Block
slugToUUID map[string]string
uuidToSlug map[string]string
}
func NewFixture¶
NewFixture creates a TaskFixture from the given blocks.
func (*TaskFixture) Add¶
Add appends blocks to the fixture and returns it for chaining. Panics if any new slug collides with an existing one.
func (*TaskFixture) AssertGoldenJournals¶
func (f *TaskFixture) AssertGoldenJournals(t *testing.T, graph *logseq.Graph, caseDirName string, pages []string)
AssertGoldenJournals is like AssertGoldenPages but for journals/.
func (*TaskFixture) AssertGoldenPages¶
func (f *TaskFixture) AssertGoldenPages(t *testing.T, graph *logseq.Graph, caseDirName string, pages []string)
AssertGoldenPages collapses UUIDs back to slugs in each output page, then compares against golden files in testdata/{caseDirName}/pages/.
func (*TaskFixture) FakeBacklog¶
FakeBacklog creates a backlog.Backlog backed by: - a temp graph populated from testdata/{caseDirName}/ with slugs expanded to UUIDs - a fake Logseq API returning generated JSON grouped by tag
configPage is the backlog config page name (e.g. "bk", "ov"). caseDirName is the subdirectory under testdata/ for this test case (may be empty).
func (*TaskFixture) FakeBacklogWithUUIDPages¶
func (f *TaskFixture) FakeBacklogWithUUIDPages(t *testing.T, configPage, caseDirName string, uuidPageNames map[string]string) backlog.Backlog
FakeBacklogWithUUIDPages creates a backlog.Backlog like FakeBacklog, but also registers UUID-to-page-name mappings in the mock API for FindBlockByUUID calls (used by directives). uuidPageNames maps slug -> page name (e.g. "home-clean-windows" -> "home").
func (*TaskFixture) assertGoldenFiles¶
func (f *TaskFixture) assertGoldenFiles(t *testing.T, graph *logseq.Graph, journals bool, caseDirName string, pages []string)
func (*TaskFixture) copyExpandedMDs¶
copyExpandedMDs copies .md files from src to dst, expanding slugs to UUIDs in each file. Silently skips if src does not exist.
func (*TaskFixture) fakeAPI¶
fakeAPI creates a mockLogseqAPI returning generated JSON grouped by tag.
func (*TaskFixture) fakeGraph¶
fakeGraph builds a temp graph with slug-expanded .md files from testdata/{caseDirName}/. Uses the graph-template as the base and expands slugs in the page files.
type blockJSON¶
blockJSON is the JSON structure matching testdata/stub-api/*.jsonl entries.
type blockJSON struct {
UUID string `json:"uuid"`
Marker string `json:"marker,omitempty"`
Content string `json:"content"`
Deadline int `json:"deadline,omitempty"`
Scheduled int `json:"scheduled,omitempty"`
Page pageJSON `json:"page"`
Properties map[string]string `json:"properties"`
PropertiesOrder []string `json:"propertiesOrder"`
PropertiesTextValues map[string]string `json:"propertiesTextValues"`
Refs []refJSON `json:"refs"`
PathRefs []refJSON `json:"pathRefs"`
Format string `json:"format"`
ID int `json:"id"`
Parent refJSON `json:"parent"`
Left refJSON `json:"left"`
}
type mockLogseqAPI¶
type mockLogseqAPI struct {
mock.Mock
tagResponses map[string]string
uuidResponses map[string]string // uuid -> page JSON response for FindBlockByUUID
}
func newMockLogseqAPIFromMap¶
newMockLogseqAPIFromMap creates a mockLogseqAPI that returns pre-built JSON responses keyed by tag.
func (*mockLogseqAPI) PostDatascriptQuery¶
func (*mockLogseqAPI) PostQuery¶
func (*mockLogseqAPI) UpsertBlockProperty¶
func (*mockLogseqAPI) WithUUIDPageResponse¶
WithUUIDPageResponse registers a page-info JSON response for a given UUID. When FindBlockByUUID queries for this UUID, the mock returns the provided response. The response format matches Logseq's datascript pull result, e.g.:
type pageJSON¶
type pageJSON struct {
JournalDay int `json:"journalDay"`
Name string `json:"name"`
OriginalName string `json:"originalName"`
ID int `json:"id"`
}
func buildJournalPage¶
buildJournalPage derives the page JSON for a block.
type refJSON¶
set¶
Index¶
- type Set
- func NewSet[T cmp.Ordered]() *Set[T]
- func (s *Set[T]) Add(value T)
- func (s *Set[T]) Clear()
- func (s *Set[T]) Contains(value T) bool
- func (s *Set[T]) Diff(sets ...*Set[T]) *Set[T]
- func (s *Set[T]) Remove(value T)
- func (s *Set[T]) Size() int
- func (s *Set[T]) Update(sets ...*Set[T])
- func (s *Set[T]) Values() []T
- func (s *Set[T]) ValuesSorted() []T
type Set¶
Set is a simple implementation of a set using a map.
func NewSet¶
NewSet creates and returns a new set.
func (*Set[T]) Add¶
Add inserts an element into the set.
func (*Set[T]) Clear¶
Clear removes all elements from the set.
func (*Set[T]) Contains¶
Contains checks if an element exists in the set.
func (*Set[T]) Diff¶
Diff returns a new set containing elements that are in the current set but not in the provided sets.
func (*Set[T]) Remove¶
Remove deletes an element from the set.
func (*Set[T]) Size¶
Size returns the number of elements in the set.
func (*Set[T]) Update¶
Update adds all elements from the given sets into the current set.
func (*Set[T]) Values¶
Values returns all elements in the set as a slice.
func (*Set[T]) ValuesSorted¶
ValuesSorted returns all elements in the set as a sorted slice.
Generated by gomarkdoc