template_content_type

Content-Type из шаблона

Концепция

Jet-шаблон может задавать Content-Type ответа через переменную response. Это позволяет отдавать из шаблона не только HTML, но и RSS, JSON, XML, CSV и любой другой текстовый формат.

{{ response.SetContentType("application/rss+xml") }}
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  ...
</rss>

Зачем

Сейчас Content-Type всегда text/html — задаётся хардкодом в rendernotepage/endpoint.go. Кастомные layouts уже позволяют писать произвольный вывод (рендер идёт напрямую в ctx, HTML-обёртка не применяется), но Content-Type поменять нельзя.

Примеры использования:

  • Кастомный RSS: пользователь сам определяет какие заметки попадают в фид и как они форматируются
  • JSON API endpoint из заметки
  • XML-экспорт
  • CSV-дамп через шаблон

Существующий RSS (handleRSSFeed middleware) остаётся — он автоматически создаёт .rss.xml для любой заметки по её ссылкам. Шаблонный подход — дополнение для случаев когда нужен кастомный фид.

Как будет работать

Template variable response

В renderLayout() в переменные шаблона добавляется response:

vars["response"] = reflect.ValueOf(&templateviews.ResponseWriter{Ctx: ctx})

Структура ResponseWriter

// internal/templateviews/response_writer.go

type ResponseWriter struct {
    Ctx *fasthttp.RequestCtx
}

func (rw *ResponseWriter) SetContentType(ct string) string {
    rw.Ctx.SetContentType(ct)
    return ""
}

Возвращает пустую строку чтобы вызов не выводил ничего в шаблоне.

Использование в шаблоне

{{ response.SetContentType("application/json") }}
{
  "title": "{{ note.Title() }}",
  "url": "{{ note.Permalink() }}"
}
{{ response.SetContentType("application/rss+xml") }}
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
  <title>{{ note.Title() }}</title>
  <link>{{ nvs.ResolveURL(note) }}</link>
  {{ range i, item := nvs.ByGlob("blog/*.md").SortBy("CreatedAt").Desc().Limit(20).All() }}
  <item>
    <title>{{ item.Title() }}</title>
    <link>{{ item.Permalink() }}</link>
    <pubDate>{{ item.CreatedAt().Format("Mon, 02 Jan 2006 15:04:05 -0700") }}</pubDate>
  </item>
  {{ end }}
</channel>
</rss>

Escape-хелперы

Для безопасной вставки в JSON/XML зарегистрировать глобальные функции в layoutloader/loader.go:

views.AddGlobalFunc("json_escape", func(a jet.Arguments) reflect.Value {
    var s string
    a.ParseInto(&s)
    b, _ := json.Marshal(s)
    return reflect.ValueOf(string(b[1 : len(b)-1])) // без кавычек
})

views.AddGlobalFunc("xml_escape", func(a jet.Arguments) reflect.Value {
    var s string
    a.ParseInto(&s)
    // html.EscapeString работает для XML тоже
    return reflect.ValueOf(html.EscapeString(s))
})

Использование:

<title>{{ note.Title() | xml_escape }}</title>

Что нужно изменить

Файл Изменение
internal/templateviews/response_writer.go Новый файл: ResponseWriter struct
internal/case/rendernotepage/endpoint.go Передавать vars["response"] в renderLayout()
internal/layoutloader/loader.go Зарегистрировать json_escape, xml_escape

Rendering pipeline уже правильный: когда layout используется, вывод идёт напрямую в ctx без HTML-обёртки (processed == truereturn nil, nil). Достаточно добавить response переменную.

Статус

  • Не реализовано