feat(coursework-v2): refine abstract, committee lines, and bibliography

This commit is contained in:
ilyaoc
2026-05-26 20:46:42 +02:00
parent 6bcc345717
commit d6037872dd
6 changed files with 508 additions and 39 deletions
+420
View File
@@ -0,0 +1,420 @@
<?xml version="1.0" encoding="UTF-8"?>
<style xmlns="http://purl.org/net/xbiblio/csl"
class="in-text"
version="1.0"
default-locale="uk-UA"
demote-non-dropping-particle="never">
<info>
<title>ДСТУ 8302:2015 — бібліографічні посилання</title>
<title-short>ДСТУ 8302:2015</title-short>
<id>dstu-8302-2015-typst</id>
<link href="https://online.budstandart.com/ua/catalog/doc-page.html?id_doc=64411" rel="documentation"/>
<author>
<name>Custom Typst CSL</name>
</author>
<category citation-format="numeric"/>
<category field="generic-base"/>
<summary>CSL для оформлення переліку джерел за логікою ДСТУ 8302:2015 у Typst/Pandoc: числові посилання, українські скорочення, DOI/URL, дата звернення, коректні вебджерела, статті та нормативні акти.</summary>
<updated>2026-05-25T00:00:00+00:00</updated>
<rights license="http://creativecommons.org/licenses/by-sa/4.0/">This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 License</rights>
</info>
<locale xml:lang="uk-UA">
<terms>
<term name="accessed">дата звернення</term>
<term name="page" form="short">С.</term>
<term name="page" form="symbol">с.</term>
<term name="volume" form="short">Т.</term>
<term name="issue" form="short">№</term>
<term name="edition" form="short">вид.</term>
<term name="editor" form="short">ред.</term>
<term name="translator" form="short">пер.</term>
<term name="et-al">та ін.</term>
<term name="and">,</term>
</terms>
</locale>
<!-- Ініціали після прізвища: Vergara L. M., Park M. -->
<macro name="contributors">
<names variable="author">
<name name-as-sort-order="all"
sort-separator=" "
initialize-with=". "
delimiter=", "
delimiter-precedes-last="never"/>
<substitute>
<names variable="editor">
<name name-as-sort-order="all"
sort-separator=" "
initialize-with=". "
delimiter=", "
delimiter-precedes-last="never"/>
<label form="short" prefix=" (" suffix=")"/>
</names>
<names variable="translator">
<name name-as-sort-order="all"
sort-separator=" "
initialize-with=". "
delimiter=", "
delimiter-precedes-last="never"/>
<label form="short" prefix=" (" suffix=")"/>
</names>
</substitute>
</names>
</macro>
<macro name="title">
<text variable="title"/>
</macro>
<macro name="container">
<text variable="container-title" font-style="italic"/>
</macro>
<macro name="site-or-publisher">
<choose>
<if variable="container-title">
<text variable="container-title"/>
</if>
<else>
<text variable="publisher"/>
</else>
</choose>
</macro>
<macro name="issued-year">
<date variable="issued">
<date-part name="year"/>
</date>
</macro>
<macro name="issued-date">
<date variable="issued">
<date-part name="day" form="numeric-leading-zeros" suffix="."/>
<date-part name="month" form="numeric-leading-zeros" suffix="."/>
<date-part name="year"/>
</date>
</macro>
<macro name="accessed-date">
<date variable="accessed">
<date-part name="day" form="numeric-leading-zeros" suffix="."/>
<date-part name="month" form="numeric-leading-zeros" suffix="."/>
<date-part name="year"/>
</date>
</macro>
<macro name="publisher-place">
<group delimiter=" : ">
<text variable="publisher-place"/>
<text variable="publisher"/>
</group>
</macro>
<macro name="edition">
<group delimiter=" ">
<text variable="edition"/>
<label variable="edition" form="short"/>
</group>
</macro>
<macro name="book-pages">
<choose>
<if variable="number-of-pages">
<group delimiter=" ">
<text variable="number-of-pages"/>
<text term="page" form="symbol"/>
</group>
</if>
<else-if variable="page">
<group delimiter=" ">
<text variable="page"/>
<text term="page" form="symbol"/>
</group>
</else-if>
</choose>
</macro>
<macro name="pages">
<choose>
<if variable="page">
<group delimiter=" ">
<text term="page" form="short"/>
<text variable="page"/>
</group>
</if>
</choose>
</macro>
<macro name="volume-issue">
<group delimiter=", ">
<choose>
<if variable="volume">
<group delimiter=" ">
<text value="Т."/>
<text variable="volume"/>
</group>
</if>
</choose>
<choose>
<if variable="issue">
<group delimiter=" ">
<text value="№"/>
<text variable="issue"/>
</group>
</if>
</choose>
</group>
</macro>
<macro name="url-access">
<choose>
<if variable="URL">
<group delimiter=" ">
<text variable="URL" prefix="URL: "/>
<choose>
<if variable="accessed">
<group delimiter=": " prefix="(" suffix=")">
<text term="accessed"/>
<text macro="accessed-date"/>
</group>
</if>
</choose>
</group>
</if>
</choose>
</macro>
<macro name="doi-or-url">
<choose>
<if variable="DOI">
<text variable="DOI" prefix="DOI: "/>
</if>
<else>
<text macro="url-access"/>
</else>
</choose>
</macro>
<macro name="legal-details">
<choose>
<if variable="genre">
<group delimiter=" : ">
<text macro="title"/>
<group delimiter=" ">
<text variable="genre"/>
<choose>
<if variable="issued">
<group delimiter=" ">
<text value="від"/>
<text macro="issued-date"/>
</group>
</if>
</choose>
<choose>
<if variable="number">
<group delimiter=" ">
<text value="№"/>
<text variable="number"/>
</group>
</if>
</choose>
</group>
</group>
</if>
<else>
<group delimiter=". ">
<text macro="title"/>
<group delimiter=" ">
<choose>
<if variable="issued">
<group delimiter=" ">
<text value="від"/>
<text macro="issued-date"/>
</group>
</if>
</choose>
<choose>
<if variable="number">
<group delimiter=" ">
<text value="№"/>
<text variable="number"/>
</group>
</if>
</choose>
</group>
</group>
</else>
</choose>
</macro>
<citation collapse="citation-number">
<sort>
<key variable="citation-number"/>
</sort>
<layout delimiter=", " prefix="[" suffix="]">
<text variable="citation-number"/>
</layout>
</citation>
<bibliography hanging-indent="false" entry-spacing="0" line-spacing="1">
<sort>
<key variable="citation-number"/>
</sort>
<layout>
<group display="block">
<text display="left-margin" variable="citation-number" suffix=". "/>
<choose>
<!-- Нормативні акти: Цивільний кодекс України : Закон України від 16.01.2003 № 435-IV. URL: ... -->
<if type="legislation bill legal_case treaty" match="any">
<group delimiter=". " suffix=".">
<text macro="legal-details"/>
<text macro="url-access"/>
</group>
</if>
<!-- Наукова стаття -->
<else-if type="article-journal">
<group delimiter=". " suffix=".">
<text macro="contributors"/>
<text macro="title"/>
<text macro="container"/>
<text macro="issued-year"/>
<text macro="volume-issue"/>
<text macro="pages"/>
<text macro="doi-or-url"/>
</group>
</else-if>
<!-- Газетна / журнальна стаття без DOI -->
<else-if type="article-newspaper article-magazine" match="any">
<group delimiter=". " suffix=".">
<text macro="contributors"/>
<text macro="title"/>
<text variable="container-title" font-style="italic"/>
<text macro="issued-date"/>
<text macro="pages"/>
<text macro="url-access"/>
</group>
</else-if>
<!-- Вебсторінки: без дублювання назви замість автора -->
<else-if type="webpage post post-weblog" match="any">
<group delimiter=". " suffix=".">
<text macro="contributors"/>
<text macro="title"/>
<text macro="site-or-publisher"/>
<text macro="issued-date"/>
<text macro="url-access"/>
</group>
</else-if>
<!-- Книга -->
<else-if type="book">
<group delimiter=". " suffix=".">
<text macro="contributors"/>
<text macro="title"/>
<text macro="edition"/>
<group delimiter=", ">
<text macro="publisher-place"/>
<text macro="issued-year"/>
</group>
<text macro="book-pages"/>
<text macro="doi-or-url"/>
</group>
</else-if>
<!-- Розділ у книзі -->
<else-if type="chapter">
<group delimiter=". " suffix=".">
<text macro="contributors"/>
<text macro="title"/>
<group delimiter=" ">
<text value="In:"/>
<text variable="container-title" font-style="italic"/>
</group>
<names variable="editor">
<name name-as-sort-order="all"
sort-separator=" "
initialize-with=". "
delimiter=", "
delimiter-precedes-last="never"/>
<label form="short" prefix=" (" suffix=")"/>
</names>
<group delimiter=", ">
<text macro="publisher-place"/>
<text macro="issued-year"/>
</group>
<text macro="pages"/>
<text macro="doi-or-url"/>
</group>
</else-if>
<!-- Матеріали конференції -->
<else-if type="paper-conference">
<group delimiter=". " suffix=".">
<text macro="contributors"/>
<text macro="title"/>
<group delimiter=" ">
<text value="In:"/>
<text variable="container-title" font-style="italic"/>
</group>
<group delimiter=", ">
<text macro="publisher-place"/>
<text macro="issued-year"/>
</group>
<text macro="pages"/>
<text macro="doi-or-url"/>
</group>
</else-if>
<!-- Дисертації, звіти -->
<else-if type="thesis report" match="any">
<group delimiter=". " suffix=".">
<text macro="contributors"/>
<text macro="title"/>
<text variable="genre"/>
<group delimiter=", ">
<text macro="publisher-place"/>
<text macro="issued-year"/>
</group>
<text macro="book-pages"/>
<text macro="doi-or-url"/>
</group>
</else-if>
<!-- Патенти -->
<else-if type="patent">
<group delimiter=". " suffix=".">
<text macro="contributors"/>
<text macro="title"/>
<group delimiter=" ">
<text value="№"/>
<text variable="number"/>
</group>
<text macro="issued-date"/>
<text macro="url-access"/>
</group>
</else-if>
<!-- Універсальний fallback -->
<else>
<group delimiter=". " suffix=".">
<text macro="contributors"/>
<text macro="title"/>
<text variable="container-title" font-style="italic"/>
<group delimiter=", ">
<text macro="publisher-place"/>
<text macro="issued-year"/>
</group>
<text macro="pages"/>
<text macro="doi-or-url"/>
</group>
</else>
</choose>
</group>
</layout>
</bibliography>
</style>
+7 -1
View File
@@ -65,6 +65,7 @@
/// - title (str): Work title /// - title (str): Work title
/// - authors (array): List of author dictionaries /// - authors (array): List of author dictionaries
/// - mentors (array): List of mentor dictionaries /// - mentors (array): List of mentor dictionaries
/// - committee-members (array): Optional list of commission member dictionaries for the title page
/// - task-list (dict): Task metadata /// - task-list (dict): Task metadata
/// - calendar-plan (dict): Calendar plan table /// - calendar-plan (dict): Calendar plan table
/// - abstract (dict): Keywords and abstract text /// - abstract (dict): Keywords and abstract text
@@ -77,6 +78,7 @@
title: none, title: none,
authors: (), authors: (),
mentors: (), mentors: (),
committee-members: none,
task-list: (), task-list: (),
calendar-plan: (), calendar-plan: (),
abstract: (), abstract: (),
@@ -107,12 +109,14 @@
} else { } else {
abstract abstract
} }
let committee_members = committee-members
tp.cw-v2.nure( tp.cw-v2.nure(
university, university,
title, title,
authors, authors,
mentors, mentors,
committee_members,
task-list, task-list,
calendar-plan, calendar-plan,
abstract, abstract,
@@ -125,10 +129,12 @@
doc doc
{ {
show regex("^\\d+\\."): it => [#it#h(0.5cm)] show regex("^\\d+\\."): it => [#it#h(0.5cm)]
show block: it => [#it.body#parbreak()] show block: it => [#it.body#parbreak()]
bibliography(bib-path, title: [Перелік джерел посилання], style: "csl/dstu-3008-2015.csl", full: true) bibliography(bib-path, title: [Перелік джерел посилання], style: "csl/dstu-8302-2015.csl", full: true)
} }
style.appendices(appendices) style.appendices(appendices)
+10
View File
@@ -186,6 +186,7 @@
counter(figure.where(kind: raw)).update(0) counter(figure.where(kind: raw)).update(0)
counter(figure.where(kind: image)).update(0) counter(figure.where(kind: image)).update(0)
counter(figure.where(kind: table)).update(0) counter(figure.where(kind: table)).update(0)
dstu-table-counter.update(0)
it it
} }
set figure(numbering: i => context numbering("1.1", counter(heading).get().at(0), i)) set figure(numbering: i => context numbering("1.1", counter(heading).get().at(0), i))
@@ -218,6 +219,15 @@
v(double-half-spacing, weak: true) v(double-half-spacing, weak: true)
} }
// blocks `like this` aren't welcome, so ` is replaced with "
show raw.where(block: false): it => text(
lang: "uk",
size: 14pt,
hyphenate: false,
weight: "regular",
font: ("Times New Roman", "Liberation Serif"),
)["#it.text"]
it it
} }
+57 -24
View File
@@ -1,7 +1,7 @@
#import "../../shared.typ": universities #import "../../shared.typ": universities
#import "../../helpers.typ": * #import "../../helpers.typ": *
#import "../../style.typ": spacing #import "../../style.typ": spacing
#import "../../utils.typ": bold, uline, filled-lines #import "../../utils.typ": bold, uline, filled-lines, hfill
#let note(content) = block(width: 100%, above: 5pt, below: 0pt)[ #let note(content) = block(width: 100%, above: 5pt, below: 0pt)[
#set text(size: 10pt) #set text(size: 10pt)
@@ -69,16 +69,37 @@
#let task-num(n) = box(str(n) + ".") #let task-num(n) = box(str(n) + ".")
#let title-field(value) = { #let title-field(value) = {
uline(align: center, filled-lines(value)) uline(align: center, value.join())
uline(align: center, []) uline(align: center, [])
note[(тема)] note[(тема)]
} }
#let commission-lines(members) = {
if members.len() > 0 {
for (i, member) in members.enumerate() {
if i > 0 {
linebreak()
}
let member-display-name = member.at("display-name", default: member.name)
let member-degree = member.at("degree", default: "")
uline(align: left, [#member-degree #member-display-name])
}
} else {
v(0.55em)
line(length: 100%, stroke: 0.5pt)
v(0.55em)
line(length: 100%, stroke: 0.5pt)
v(0.55em)
line(length: 100%, stroke: 0.5pt)
}
}
#let nure( #let nure(
university, university,
title, title,
authors, authors,
mentors, mentors,
committee_members,
task-list, task-list,
calendar-plan, calendar-plan,
abstract, abstract,
@@ -90,6 +111,11 @@
) = { ) = {
let author = authors.first() let author = authors.first()
let head-mentor = mentors.first() let head-mentor = mentors.first()
let commission-members = if committee_members == none {
mentors.slice(1)
} else {
committee_members
}
let uni = universities.at(university) let uni = universities.at(university)
let edu-prog = uni.edu-programs.at(author.edu-program) let edu-prog = uni.edu-programs.at(author.edu-program)
@@ -177,12 +203,8 @@
#pad(left: 75pt)[ #pad(left: 75pt)[
#set par(first-line-indent: 0pt) #set par(first-line-indent: 0pt)
Члени комісії (#text(size: 10pt)[Власне ім'я, ПРІЗВИЩЕ, підпис]) Члени комісії (#text(size: 10pt)[Власне ім'я, ПРІЗВИЩЕ, підпис])
#v(0.55em) #v(0.15em)
#line(length: 100%, stroke: 0.5pt) #commission-lines(commission-members)
#v(0.55em)
#line(length: 100%, stroke: 0.5pt)
#v(0.55em)
#line(length: 100%, stroke: 0.5pt)
] ]
], ],
) )
@@ -266,12 +288,12 @@
#v(5.0em) #v(5.0em)
Дата видачі завдання “#underline(task-list.initial-date.display("[day]"))” #underline(month-gen(task-list.initial-date.month())) #task-list.initial-date.display("[year]")р. Дата видачі завдання “#underline(task-list.initial-date.display("[day]"))” #underline(month-gen(task-list.initial-date.month())) #task-list.initial-date.display("[year]") р.
#v(1.4em) #v(1.4em)
Здобувач #uline(align: center, []) Здобувач #underline([#hfill(6cm)])
#note[(підпис)] #note[(підпис) #h(8cm)]
#v(1.4em) #v(1.4em)
@@ -292,7 +314,8 @@
#context [ #context [
#let pages = counter(page).final().at(0) #let pages = counter(page).final().at(0)
#let images = query(figure.where(kind: image)).len() #let images = query(figure.where(kind: image)).len()
#let tables = query(figure.where(kind: table)).len() #let dstu-tables = query(metadata).filter(it => type(it.value) == str and it.value.starts-with("start-dstu-table-")).len()
#let tables = query(figure.where(kind: table)).len() + dstu-tables
#let bibs = bib-count.final().dedup().len() #let bibs = bib-count.final().dedup().len()
#let counters = () #let counters = ()
@@ -306,23 +329,33 @@
\ \
#( #let keyword-pairs = if abstract.keywords.len() > 0 and type(abstract.keywords.first()) == array {
abstract abstract.keywords.map(pair => (
.keywords uk: pair.at(0),
.map(upper) en: pair.at(1),
.sorted(by: (a, b) => { ))
if is-cyr(a) != is-cyr(b) { is-cyr(a) } else { a < b } } else if abstract.at("en", default: none) != none and abstract.en.at("keywords", default: none) != none {
}) abstract.keywords.enumerate().map(((i, uk)) => (
.join(", ") uk: uk,
) en: abstract.en.keywords.at(i),
))
} else {
abstract.keywords.map(uk => (uk: uk))
}
#let sorted-keyword-pairs = keyword-pairs.sorted(by: (a, b) => {
if is-cyr(a.uk) != is-cyr(b.uk) { is-cyr(a.uk) } else { a.uk < b.uk }
})
#(sorted-keyword-pairs.map(pair => upper(pair.uk)).join(", "))
\ \
#abstract.text #abstract.text
#if abstract.at("en", default: none) != none [ #if abstract.at("en", default: none) != none or (keyword-pairs.len() > 0 and keyword-pairs.first().at("en", default: none) != none) [
\ \
#(abstract.en.keywords.map(upper).join(", "))
#(sorted-keyword-pairs.map(pair => upper(pair.en)).join(", "))
\ \
@@ -340,7 +373,7 @@
} else { } else {
block(width: 100%)[ block(width: 100%)[
#link(el.location())[ #link(el.location())[
ДОДАТОК #it.prefix()#h(0.5em)#it.inner() Додаток #it.prefix()#h(0.5em)#it.inner()
] ]
] ]
} }
+2 -1
View File
@@ -210,7 +210,8 @@
#context [ #context [
#let pages = counter(page).final().at(0) #let pages = counter(page).final().at(0)
#let images = query(figure.where(kind: image)).len() #let images = query(figure.where(kind: image)).len()
#let tables = query(figure.where(kind: table)).len() #let dstu-tables = query(metadata).filter(it => type(it.value) == str and it.value.starts-with("start-dstu-table-")).len()
#let tables = query(figure.where(kind: table)).len() + dstu-tables
#let bibs = bib-count.final().dedup().len() #let bibs = bib-count.final().dedup().len()
/* TODO: why this stopped working? /* TODO: why this stopped working?
#let tables = counter(figure.where(kind: table)).final().at(0) #let tables = counter(figure.where(kind: table)).final().at(0)
+12 -13
View File
@@ -20,6 +20,12 @@
(name: "Сокорчук І. П.", display-name: "Ігор СОКОРЧУК", degree: "ст.викл. кафедри ПІ"), (name: "Сокорчук І. П.", display-name: "Ігор СОКОРЧУК", degree: "ст.викл. кафедри ПІ"),
) )
#let committee_members = (
(name: "Груздо І.В.", degree: "Доц."),
(name: "Зибіна К.В.", degree: "Ст. викл."),
(name: "Гребенюк В.О.", degree: "Ст. викл."),
)
#let task-list = ( #let task-list = (
done-date: datetime(year: 2026, month: 12, day: 27), done-date: datetime(year: 2026, month: 12, day: 27),
initial-date: datetime(year: 2026, month: 9, day: 15), initial-date: datetime(year: 2026, month: 9, day: 15),
@@ -53,11 +59,11 @@
#let abstract = ( #let abstract = (
keywords: ( keywords: (
"веб-застосунок", ("веб-застосунок", "WEB APPLICATION"),
"інформаційна система", ("інформаційна система", "INFORMATION SYSTEM"),
"курсова робота", ("курсова робота", "COURSEWORK"),
"програмна інженерія", ("програмна інженерія", "SOFTWARE ENGINEERING"),
"тестовий приклад", ("тестовий приклад", "DEMO SAMPLE"),
), ),
text: [ text: [
Мета даної роботи -- продемонструвати оформлення пояснювальної записки Мета даної роботи -- продемонструвати оформлення пояснювальної записки
@@ -74,15 +80,7 @@
шаблону без прив'язки до реального студента, викладача або завершеної роботи. шаблону без прив'язки до реального студента, викладача або завершеної роботи.
], ],
) )
#let abstract_en = ( #let abstract_en = (
keywords: (
"WEB APPLICATION",
"INFORMATION SYSTEM",
"COURSEWORK",
"SOFTWARE ENGINEERING",
"DEMO SAMPLE",
),
text: [ text: [
The purpose of this work is to demonstrate the formatting of a coursework The purpose of this work is to demonstrate the formatting of a coursework
explanatory note using the new template variant. explanatory note using the new template variant.
@@ -122,6 +120,7 @@
), ),
authors: authors, authors: authors,
mentors: mentors, mentors: mentors,
committee-members: committee_members,
task-list: task-list, task-list: task-list,
calendar-plan: calendar-plan, calendar-plan: calendar-plan,
abstract: abstract, abstract: abstract,