Implement nested lists
All checks were successful
CI Pipeline / build (pull_request) Successful in 13s
All checks were successful
CI Pipeline / build (pull_request) Successful in 13s
This commit is contained in:
@@ -93,12 +93,10 @@ module Notare
|
||||
end
|
||||
|
||||
def li(text = nil, &block)
|
||||
item = Nodes::ListItem.new([], list_type: @current_list.type, num_id: @current_list.num_id)
|
||||
if block
|
||||
with_target(item, &block)
|
||||
elsif text
|
||||
item.add_run(Nodes::Run.new(text, **current_formatting))
|
||||
end
|
||||
current_type = @list_type_stack.last
|
||||
item = Nodes::ListItem.new([], list_type: current_type, num_id: @current_list.num_id, level: @list_level)
|
||||
item.add_run(Nodes::Run.new(text, **current_formatting)) if text
|
||||
with_target(item, &block) if block
|
||||
@current_list.add_item(item)
|
||||
end
|
||||
|
||||
@@ -134,15 +132,31 @@ module Notare
|
||||
|
||||
def list(type, &block)
|
||||
@num_id_counter ||= 0
|
||||
@num_id_counter += 1
|
||||
mark_has_lists!
|
||||
@list_level ||= 0
|
||||
@list_type_stack ||= []
|
||||
|
||||
list_node = Nodes::List.new(type: type, num_id: @num_id_counter)
|
||||
previous_list = @current_list
|
||||
@current_list = list_node
|
||||
block.call
|
||||
@current_list = previous_list
|
||||
@nodes << list_node
|
||||
nested = !previous_list.nil?
|
||||
|
||||
if nested
|
||||
# Nested list: reuse parent list, push new type, increment level
|
||||
@list_level += 1
|
||||
@list_type_stack.push(type)
|
||||
block.call
|
||||
@list_type_stack.pop
|
||||
@list_level -= 1
|
||||
else
|
||||
# Top-level list: new List node
|
||||
@num_id_counter += 1
|
||||
mark_has_lists!
|
||||
list_node = Nodes::List.new(type: type, num_id: @num_id_counter)
|
||||
@list_type_stack.push(type)
|
||||
@current_list = list_node
|
||||
block.call
|
||||
@current_list = previous_list
|
||||
@list_type_stack.pop
|
||||
@nodes << list_node
|
||||
end
|
||||
end
|
||||
|
||||
def with_format(format, &block)
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
module Notare
|
||||
module Nodes
|
||||
class ListItem < Base
|
||||
attr_reader :runs, :list_type, :num_id
|
||||
attr_reader :runs, :list_type, :num_id, :level
|
||||
|
||||
def initialize(runs = [], list_type:, num_id:)
|
||||
def initialize(runs = [], list_type:, num_id:, level: 0)
|
||||
super()
|
||||
@runs = runs
|
||||
@list_type = list_type
|
||||
@num_id = num_id
|
||||
@level = level
|
||||
end
|
||||
|
||||
def add_run(run)
|
||||
|
||||
@@ -69,7 +69,7 @@ module Notare
|
||||
xml["w"].p do
|
||||
xml["w"].pPr do
|
||||
xml["w"].numPr do
|
||||
xml["w"].ilvl("w:val" => "0")
|
||||
xml["w"].ilvl("w:val" => item.level.to_s)
|
||||
xml["w"].numId("w:val" => item.num_id.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,6 +4,8 @@ module Notare
|
||||
module Xml
|
||||
class Numbering
|
||||
NAMESPACE = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
||||
BULLET_CHARS = ["•", "○", "■"].freeze
|
||||
NUMBER_FORMATS = %w[decimal lowerLetter lowerRoman].freeze
|
||||
|
||||
def initialize(lists)
|
||||
@lists = lists
|
||||
@@ -28,13 +30,16 @@ module Notare
|
||||
|
||||
def render_abstract_num(xml, list)
|
||||
xml["w"].abstractNum("w:abstractNumId" => list.num_id.to_s) do
|
||||
xml["w"].lvl("w:ilvl" => "0") do
|
||||
xml["w"].start("w:val" => "1")
|
||||
xml["w"].numFmt("w:val" => num_format(list.type))
|
||||
xml["w"].lvlText("w:val" => lvl_text(list.type))
|
||||
xml["w"].lvlJc("w:val" => "left")
|
||||
xml["w"].pPr do
|
||||
xml["w"].ind("w:left" => "720", "w:hanging" => "360")
|
||||
9.times do |level|
|
||||
xml["w"].lvl("w:ilvl" => level.to_s) do
|
||||
xml["w"].start("w:val" => "1")
|
||||
xml["w"].numFmt("w:val" => num_format_for_level(list.type, level))
|
||||
xml["w"].lvlText("w:val" => lvl_text_for_level(list.type, level))
|
||||
xml["w"].lvlJc("w:val" => "left")
|
||||
xml["w"].pPr do
|
||||
left = 720 * (level + 1)
|
||||
xml["w"].ind("w:left" => left.to_s, "w:hanging" => "360")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -46,12 +51,20 @@ module Notare
|
||||
end
|
||||
end
|
||||
|
||||
def num_format(type)
|
||||
type == :bullet ? "bullet" : "decimal"
|
||||
def num_format_for_level(type, level)
|
||||
if type == :bullet
|
||||
"bullet"
|
||||
else
|
||||
NUMBER_FORMATS[level % NUMBER_FORMATS.length]
|
||||
end
|
||||
end
|
||||
|
||||
def lvl_text(type)
|
||||
type == :bullet ? "•" : "%1."
|
||||
def lvl_text_for_level(type, level)
|
||||
if type == :bullet
|
||||
BULLET_CHARS[level % BULLET_CHARS.length]
|
||||
else
|
||||
"%#{level + 1}."
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user