Skip to content

Commit

Permalink
feat(tags): include & layout template tags
Browse files Browse the repository at this point in the history
  • Loading branch information
stdword committed Feb 12, 2024
1 parent b5c2503 commit ad452d9
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 62 deletions.
2 changes: 2 additions & 0 deletions src/context.ts
Expand Up @@ -4,6 +4,7 @@ import { BlockEntity, PageEntity } from '@logseq/libs/dist/LSPlugin.user'
import {
cleanMacroArg, LogseqReference, p, Properties, PropertiesRefs, PropertiesUtils
} from './utils'
import { ITemplate } from './template'

// dayjs: for tests
// import { Dayjs } from 'dayjs'
Expand Down Expand Up @@ -79,6 +80,7 @@ export interface ILogseqContext extends ILogseqCallContext, ILogseqCurrentContex
tags?: Context
self?: BlockContext
template?: {
_obj: ITemplate,
name: string,
includingParent: boolean,
block: BlockContext,
Expand Down
87 changes: 56 additions & 31 deletions src/logic.ts
Expand Up @@ -45,17 +45,30 @@ async function getCurrentContext(
}
}

/**
* @raises StateError: Arg `:page` doesn't exist or improperly specified
* @raises StateError: Arg `:block` doesn't exist or improperly specified
*/
async function getCallContext(
slot: string,
export async function getArgsContext(
template: ITemplate,
argsContext: ArgsContext,
): Promise<ILogseqCallContext> {
args: string[],
precedingTemplate?: ITemplate,
): Promise<ArgsContext> {
const argsContext = ArgsContext.create(template.name, args)

// fulfill args with template arg-props
const argsProps = template.getArgProperties()

if (!precedingTemplate) {
const blockID = argsContext['transcluded-from']
if (blockID) {
const block = await logseq.Editor.getBlock(Number(blockID))
if (block) {
const precedingArgsProps = Template.getArgProperties(block)
Object.assign(argsProps, precedingArgsProps)
}
}
} else { // for handling template layouts
const precedingArgsProps = precedingTemplate.getArgProperties()
Object.assign(argsProps, precedingArgsProps)
}

for (const [ key, value ] of Object.entries(argsProps))
if (key.startsWith(ArgsContext.propertyPrefix)) {
const name = key.slice(ArgsContext.propertyPrefix.length)
Expand All @@ -69,6 +82,17 @@ async function getCallContext(
}
}

return argsContext
}

/**
* @raises StateError: Arg `:page` doesn't exist or improperly specified
* @raises StateError: Arg `:block` doesn't exist or improperly specified
*/
async function getCallContext(
slot: string,
argsContext: ArgsContext,
): Promise<ILogseqCallContext> {
// @ts-expect-error
const contextPageRef = parseReference(argsContext.page as string ?? '')
let contextPage: PageEntity | null = null
Expand Down Expand Up @@ -105,20 +129,19 @@ async function getCallContext(
contextBlock = blockExists
}

argsContext._hideUndefinedMode = true
return {
identity: new Context({ slot, key: slot.split('__', 2)[1].trim() }),
config: await ConfigContext.get(),

page: contextPage ? PageContext.createFromEntity(contextPage) : null,
block: contextBlock ? BlockContext.createFromEntity(contextBlock) : null,
args: argsContext,
}
}

async function getContext(
async function assembleContext(
callContext: ILogseqCallContext,
currentContext: ILogseqCurrentContext,
argsContext: ArgsContext,
): Promise<ILogseqContext> {
return {
mode: currentContext.mode,
Expand All @@ -131,7 +154,7 @@ async function getContext(
block: callContext.block || currentContext.currentBlock,
currentBlock: currentContext.currentBlock,

args: callContext.args,
args: argsContext,
}
}

Expand Down Expand Up @@ -230,7 +253,7 @@ function showInsideMacroNotification() {

async function handleNestedRendering(
templateBlock: BlockEntity,
argsContext: ArgsContext,
args: string[],
uuid: string,
slot: string,
rawCode: RendererMacro,
Expand All @@ -251,7 +274,7 @@ async function handleNestedRendering(

// case: template rendering occurs via standard Logseq way
// https://github.com/stdword/logseq13-full-house-plugin/discussions/18
if (argsContext['delay-until-rendered'] && state === 'nested') {
if (args.includes(':delay-until-rendered') && state === 'nested') {
const code = html`
<i title="Rendering of this ${rawCode.name} was delayed"
>${rawCode.toString()}</i>
Expand Down Expand Up @@ -294,20 +317,20 @@ async (
rawCode: RendererMacro,
args: string[],
) => {
const argsContext = ArgsContext.create(templateRef.original, args)
const template = await getTemplate(templateRef)

const handled = await handleNestedRendering(template.block, argsContext, uuid, slot, rawCode)
const handled = await handleNestedRendering(template.block, args, uuid, slot, rawCode)
if (handled)
return

const currentContext = await getCurrentContext(uuid, 'template')
if (!currentContext)
return

const context = await getContext(
await getCallContext(slot, template, argsContext),
const argsContext = await getArgsContext(template, args)
const context = await assembleContext(
await getCallContext(slot, argsContext),
currentContext,
argsContext,
)

let rendered: IBlockNode
Expand Down Expand Up @@ -376,12 +399,17 @@ async (
export async function compileTemplateView(
slot: string,
template: ITemplate,
argsContext: ArgsContext,
args: string[],
currentContext: ILogseqCurrentContext,
argsContext?: ArgsContext,
): Promise<string> {
const context = await getContext(
await getCallContext(slot, template, argsContext),
if (!argsContext)
argsContext = await getArgsContext(template, args)

const context = await assembleContext(
await getCallContext(slot, argsContext),
currentContext,
argsContext,
)

let rendered: IBlockNode
Expand Down Expand Up @@ -409,7 +437,7 @@ export async function compileTemplateView(
})
console.debug(p`Markup compiled:`, {data: compiled})

if (compiled.content === '' && compiled.children.length === 1)
if (!template.includingParent && compiled.children.length === 1)
compiled = compiled.children[0]

const htmlFold = (node: IBlockNode, level = 0): string => {
Expand Down Expand Up @@ -457,13 +485,13 @@ async function _renderTemplateView(
slot: string,
blockUUID: string,
template: ITemplate,
argsContext: ArgsContext,
args: string[],
) {
const currentContext = await getCurrentContext(blockUUID, 'view')
if (!currentContext)
return

const view = await compileTemplateView(slot, template, argsContext, currentContext)
const view = await compileTemplateView(slot, template, args, currentContext)
provideHTML(blockUUID, view, slot)
}

Expand All @@ -475,13 +503,11 @@ export async function renderTemplateView(
args: string[] = [],
) {
const template = await getTemplate(templateRef)
const argsContext = ArgsContext.create(template.name, args)

const handled = await handleNestedRendering(template.block, argsContext, blockUUID, slot, rawCode)
const handled = await handleNestedRendering(template.block, args, blockUUID, slot, rawCode)
if (handled)
return

await _renderTemplateView(slot, blockUUID, template, argsContext)
await _renderTemplateView(slot, blockUUID, template, args)
}

export async function renderView(
Expand All @@ -491,8 +517,7 @@ export async function renderView(
args: string[] = [],
) {
const template = getView(viewBody)
const argsContext = ArgsContext.create(template.name, args)
await _renderTemplateView(slot, blockUUID, template, argsContext)
await _renderTemplateView(slot, blockUUID, template, args)
}

export async function templateMacroStringForBlock(uuid: string, isView: boolean = false): Promise<string> {
Expand Down
64 changes: 39 additions & 25 deletions src/tags.ts
Expand Up @@ -12,7 +12,7 @@ import {
getBlock, getPage, IBlockNode, isEmptyString, isObject, isUUID,
LogseqReference, p, parseReference, RendererMacro, splitMacroArgs, unquote, walkBlockTree
} from './utils'
import { compileTemplateView, getTemplate, getTemplateBlock, templateMacroStringForBlock } from './logic'
import { compileTemplateView, getArgsContext, getTemplate, getTemplateBlock, templateMacroStringForBlock } from './logic'
import { StateError } from './errors'
import { ITemplate, Template } from './template'

Expand Down Expand Up @@ -160,7 +160,7 @@ function embed(item: string | BlockContext | PageContext | Dayjs): string {
return `{{embed ${r}}}`
}

async function _template(ref: LogseqReference, args: string[]): Promise<string> {
async function _include__template(context: ILogseqContext, layoutMode: boolean, ref: LogseqReference, args?: string[]): Promise<string> {
let block: BlockEntity
try {
[block, ] = await getTemplateBlock(ref)
Expand All @@ -170,52 +170,64 @@ async function _template(ref: LogseqReference, args: string[]): Promise<string>

let command = RendererMacro.command('template').arg(ref.value as string)

if (args.length !== 0)
command = command.args(args)
else {
const templateUsage = Template.getUsageString(block, {cleanMarkers: true})
if (templateUsage)
command = command.arg(templateUsage, {raw: true})
}
if (!args)
args = Template.getUsageArgs(block)
if (layoutMode)
args.push(`:transcluded-from ${context.template!.block.id}`)
command = command.args(args)

return command.toString()
}
async function _view(context: ILogseqContext, ref: LogseqReference, args: string[]): Promise<string> {
async function _include__view(context: ILogseqContext, layoutMode: boolean, ref: LogseqReference, args?: string[]): Promise<string> {
let template: Template
try {
template = await getTemplate(ref)
} catch (error) { // StateMessage
return ''
}

if (args.length === 0) {
const templateUsage = Template.getUsageString(template.block, {cleanMarkers: true})
if (templateUsage)
args = splitMacroArgs(templateUsage)
}
const argsContext = ArgsContext.create(template.name, args)
if (!args)
args = Template.getUsageArgs(template.block)

const argsContext = layoutMode
? await getArgsContext(template, args, context.template!._obj)
: undefined

return compileTemplateView(
return await compileTemplateView(
// @ts-expect-error
context.identity.slot,
template,
argsContext,
args,
context as ILogseqCurrentContext,
argsContext,
)
}
async function include(context: ILogseqContext, name: string, ...args: string[]) {
async function _include(context: ILogseqContext, layoutMode: boolean, name: string, args?: string[] | string) {
const ref = parseReference(name ?? '')
if (!ref)
return ''

args = args.map((arg) => arg.toString())
if (args !== undefined && !Array.isArray(args))
args = splitMacroArgs(args.toString())
args = args as string[] | undefined

if (args) // runtime protection
args = args.map((arg) => arg.toString())

if (context.mode === 'template')
return _template(ref, args)
return await _include__template(context, layoutMode, ref, args)
else if (context.mode === 'view')
return _view(context, ref, args)
return await _include__view(context, layoutMode, ref, args)

console.debug(p`Unknown rendering mode: ${context.mode}`)
return ''
}
async function include(context: ILogseqContext, name: string, args?: string[] | string) {
return await _include(context, false, name, args)
}
async function layout(context: ILogseqContext, name: string, args?: string[] | string) {
return await _include(context, true, name, args)
}

function empty(obj: any, fallback: any = ''): any {
if (obj === null)
Expand Down Expand Up @@ -496,14 +508,14 @@ export function getTemplateTagsDatesContext() {
return {
yesterday, today, tomorrow, time,

date: {
date: new Context({
yesterday: yesterdayObj,
today: todayObj.startOf('day'),
now: todayObj,
tomorrow: tomorrowObj,

from: dayjs,
},
}),
}
}
export function getTemplateTagsContext(context: ILogseqContext) {
Expand All @@ -518,6 +530,9 @@ export function getTemplateTagsContext(context: ILogseqContext) {
tomorrow: datesContext.tomorrow,
time: datesContext.time,

include: bindContext(include, context),
layout: bindContext(layout, context),

query: new Context({
refs: new Context({
count: bindContext(query_refsCount, context),
Expand All @@ -537,7 +552,6 @@ export function getTemplateTagsContext(context: ILogseqContext) {
page: function (entity) { return PageContext.createFromEntity(entity) },
block: function (entity) { return BlockContext.createFromEntity(entity) },
}),
include: bindContext(include, context),
}),
date: Object.assign(datesContext.date, {
nlp: bindContext(date_nlp, context),
Expand Down

0 comments on commit ad452d9

Please sign in to comment.