2016-02-15 20:58:12 +09:00
|
|
|
|
|
|
|
|
|
2016-02-19 05:53:48 +09:00
|
|
|
|
|
|
|
|
using Textamina.Markdig.Helpers;
|
2016-02-15 20:58:12 +09:00
|
|
|
using Textamina.Markdig.Parsing;
|
|
|
|
|
|
|
|
|
|
namespace Textamina.Markdig.Syntax
|
|
|
|
|
{
|
|
|
|
|
public class ListBlock : ContainerBlock
|
|
|
|
|
{
|
2016-02-23 20:09:37 +09:00
|
|
|
public new static readonly BlockParser Parser = new ParserInternal();
|
|
|
|
|
|
|
|
|
|
public ListBlock(BlockParser parser) : base(parser)
|
|
|
|
|
{
|
|
|
|
|
}
|
2016-02-15 20:58:12 +09:00
|
|
|
|
2016-02-23 00:01:43 +09:00
|
|
|
public bool IsOrdered { get; set; }
|
|
|
|
|
|
2016-02-15 20:58:12 +09:00
|
|
|
public char BulletChar { get; set; }
|
|
|
|
|
|
2016-02-23 00:01:43 +09:00
|
|
|
public int OrderedStart { get; set; }
|
|
|
|
|
|
|
|
|
|
public char OrderedDelimiter { get; set; }
|
|
|
|
|
|
|
|
|
|
public bool IsLoose { get; set; }
|
|
|
|
|
|
2016-02-24 01:01:40 +09:00
|
|
|
private int CountAllBlankLines { get; set; }
|
2016-02-15 23:56:45 +09:00
|
|
|
|
2016-02-24 01:01:40 +09:00
|
|
|
private int CountBlankLinesReset { get; set; }
|
2016-02-23 23:24:57 +09:00
|
|
|
|
2016-02-15 20:58:12 +09:00
|
|
|
private class ParserInternal : BlockParser
|
|
|
|
|
{
|
2016-02-23 20:27:43 +09:00
|
|
|
public override MatchLineResult Match(BlockParserState state)
|
2016-02-15 20:58:12 +09:00
|
|
|
{
|
2016-02-23 23:24:57 +09:00
|
|
|
if (state.Pending is ListBlock && state.NextPending is ListItemBlock)
|
|
|
|
|
{
|
|
|
|
|
// We try to match only on item block if the ListBlock
|
|
|
|
|
return MatchLineResult.Skip;
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-24 01:01:40 +09:00
|
|
|
// When both a thematic break and a list item are possible
|
|
|
|
|
// interpretations of a line, the thematic break takes precedence
|
2016-02-25 20:30:37 +09:00
|
|
|
var save = state.Line;
|
|
|
|
|
if (ThematicBreakBlock.Parser.Match(state) == MatchLineResult.Last)
|
2016-02-24 01:01:40 +09:00
|
|
|
{
|
2016-02-25 20:30:37 +09:00
|
|
|
// Remove the ThematicBreakBlock as we will let the ThematicBreakBlock to catch it later
|
2016-02-24 01:01:40 +09:00
|
|
|
state.NewBlocks.Pop();
|
|
|
|
|
return MatchLineResult.None;
|
|
|
|
|
}
|
2016-02-25 20:30:37 +09:00
|
|
|
state.SetCurrentLine(ref save);
|
2016-02-24 01:01:40 +09:00
|
|
|
|
2016-02-15 20:58:12 +09:00
|
|
|
// 5.2 List items
|
|
|
|
|
// TODO: Check with specs, it is not clear that list marker or bullet marker must be followed by at least 1 space
|
|
|
|
|
|
2016-02-23 00:01:43 +09:00
|
|
|
int preIndent = 0;
|
2016-02-25 20:30:37 +09:00
|
|
|
for (int i = state.Line.Start - 1; i >= 0; i--)
|
2016-02-23 00:01:43 +09:00
|
|
|
{
|
2016-02-25 20:30:37 +09:00
|
|
|
if (state.Line[i].IsSpaceOrTab())
|
2016-02-23 00:01:43 +09:00
|
|
|
{
|
|
|
|
|
preIndent++;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-25 20:30:37 +09:00
|
|
|
var saveLiner = state.Line;
|
2016-02-15 20:58:12 +09:00
|
|
|
|
|
|
|
|
// If we have already a ListItemBlock, we are going to try to append to it
|
2016-02-23 20:26:45 +09:00
|
|
|
var listItem = state.Pending as ListItemBlock;
|
2016-02-23 00:01:43 +09:00
|
|
|
if (listItem != null)
|
2016-02-15 20:58:12 +09:00
|
|
|
{
|
2016-02-15 23:56:45 +09:00
|
|
|
var list = (ListBlock) listItem.Parent;
|
|
|
|
|
|
|
|
|
|
// Allow all blanks lines if the last block is a fenced code block
|
|
|
|
|
// Allow 1 blank line inside a list
|
|
|
|
|
// If > 1 blank line, terminate this list
|
2016-02-25 20:30:37 +09:00
|
|
|
var isBlankLine = state.IsBlankLine;
|
2016-02-23 00:01:43 +09:00
|
|
|
//if (isBlankLine && !(state.LastBlock is FencedCodeBlock)) // TODO: Handle this case
|
2016-02-23 23:24:57 +09:00
|
|
|
var isInFencedBlock = state.LastBlock is FencedCodeBlock;
|
2016-02-23 00:01:43 +09:00
|
|
|
if (isBlankLine)
|
2016-02-15 23:56:45 +09:00
|
|
|
{
|
2016-02-23 20:27:43 +09:00
|
|
|
// TODO: Check with a generic way (allow a block to have multiple empty lines)
|
2016-02-23 23:24:57 +09:00
|
|
|
if (!isInFencedBlock)
|
2016-02-23 00:01:43 +09:00
|
|
|
{
|
2016-02-23 23:24:57 +09:00
|
|
|
if (!(state.NextPending is ListBlock))
|
|
|
|
|
{
|
2016-02-24 01:01:40 +09:00
|
|
|
list.CountAllBlankLines++;
|
2016-02-23 23:24:57 +09:00
|
|
|
listItem.Children.Add(BlankLineBlock.Instance);
|
|
|
|
|
}
|
2016-02-24 01:01:40 +09:00
|
|
|
list.CountBlankLinesReset++;
|
2016-02-23 00:01:43 +09:00
|
|
|
}
|
|
|
|
|
|
2016-02-24 01:01:40 +09:00
|
|
|
if (list.CountBlankLinesReset > 1)
|
2016-02-15 23:56:45 +09:00
|
|
|
{
|
2016-02-23 00:01:43 +09:00
|
|
|
// TODO: Close all lists and not only this one
|
2016-02-15 23:56:45 +09:00
|
|
|
return MatchLineResult.LastDiscard;
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-24 01:01:40 +09:00
|
|
|
if (list.CountBlankLinesReset == 1 && listItem.NumberOfSpaces < 0)
|
|
|
|
|
{
|
|
|
|
|
state.Close(listItem);
|
|
|
|
|
|
|
|
|
|
// Leave the list open
|
|
|
|
|
list.IsOpen = true;
|
|
|
|
|
return MatchLineResult.Continue;
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-15 23:56:45 +09:00
|
|
|
return MatchLineResult.Continue;
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-24 01:01:40 +09:00
|
|
|
list.CountBlankLinesReset = 0;
|
2016-02-23 23:24:57 +09:00
|
|
|
|
2016-02-25 20:30:37 +09:00
|
|
|
var c = state.Line.CurrentChar;
|
|
|
|
|
var startPosition = state.Line.Start;
|
2016-02-23 00:01:43 +09:00
|
|
|
|
|
|
|
|
// List Item starting with a blank line (-1)
|
|
|
|
|
if (listItem.NumberOfSpaces < 0)
|
2016-02-15 20:58:12 +09:00
|
|
|
{
|
2016-02-23 00:01:43 +09:00
|
|
|
int expectedCount = -listItem.NumberOfSpaces;
|
|
|
|
|
int countSpaces = 0;
|
2016-02-25 20:30:37 +09:00
|
|
|
var saved = new StringSlice();
|
2016-02-23 00:01:43 +09:00
|
|
|
while (c.IsSpaceOrTab())
|
|
|
|
|
{
|
2016-02-25 20:30:37 +09:00
|
|
|
c = state.Line.NextChar();
|
|
|
|
|
countSpaces = preIndent + state.Line.Column - startPosition;
|
2016-02-23 00:01:43 +09:00
|
|
|
if (countSpaces == expectedCount)
|
|
|
|
|
{
|
2016-02-25 20:30:37 +09:00
|
|
|
saved = state.Line;
|
2016-02-23 00:01:43 +09:00
|
|
|
}
|
|
|
|
|
else if (countSpaces >= 4)
|
|
|
|
|
{
|
2016-02-25 20:30:37 +09:00
|
|
|
state.SetCurrentLine(ref saved);
|
2016-02-23 00:01:43 +09:00
|
|
|
countSpaces = expectedCount;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (countSpaces == expectedCount)
|
2016-02-15 20:58:12 +09:00
|
|
|
{
|
2016-02-23 00:01:43 +09:00
|
|
|
listItem.NumberOfSpaces = countSpaces;
|
2016-02-15 20:58:12 +09:00
|
|
|
return MatchLineResult.Continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-02-23 00:01:43 +09:00
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
while (c.IsSpaceOrTab())
|
|
|
|
|
{
|
2016-02-25 20:30:37 +09:00
|
|
|
c = state.Line.NextChar();
|
|
|
|
|
var countSpaces = preIndent + state.Line.Column - startPosition;
|
2016-02-23 00:01:43 +09:00
|
|
|
if (countSpaces >= listItem.NumberOfSpaces)
|
|
|
|
|
{
|
|
|
|
|
return MatchLineResult.Continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-02-25 20:30:37 +09:00
|
|
|
state.SetCurrentLine(ref saveLiner);
|
2016-02-15 20:58:12 +09:00
|
|
|
}
|
|
|
|
|
|
2016-02-23 00:01:43 +09:00
|
|
|
return TryParseListItem(ref state, preIndent);
|
2016-02-15 20:58:12 +09:00
|
|
|
}
|
|
|
|
|
|
2016-02-23 20:27:43 +09:00
|
|
|
private MatchLineResult TryParseListItem(ref BlockParserState state, int preIndent)
|
2016-02-15 20:58:12 +09:00
|
|
|
{
|
2016-02-23 20:27:43 +09:00
|
|
|
var isInList = state.Pending is ListItemBlock;
|
|
|
|
|
|
2016-02-25 20:30:37 +09:00
|
|
|
var preStartPosition = state.Line.Start;
|
2016-02-23 20:27:43 +09:00
|
|
|
|
2016-02-25 20:30:37 +09:00
|
|
|
var c = state.Line.CurrentChar;
|
2016-02-23 20:27:43 +09:00
|
|
|
if (isInList)
|
|
|
|
|
{
|
|
|
|
|
while (c.IsSpaceOrTab())
|
|
|
|
|
{
|
2016-02-25 20:30:37 +09:00
|
|
|
c = state.Line.NextChar();
|
2016-02-23 20:27:43 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2016-02-25 20:30:37 +09:00
|
|
|
// TODO
|
|
|
|
|
//state.Line.SkipLeadingSpaces3();
|
|
|
|
|
c = state.Line.CurrentChar;
|
2016-02-23 20:27:43 +09:00
|
|
|
}
|
2016-02-25 20:30:37 +09:00
|
|
|
preIndent = preIndent + state.Line.Start - preStartPosition;
|
2016-02-23 00:01:43 +09:00
|
|
|
|
|
|
|
|
var isOrdered = false;
|
|
|
|
|
var bulletChar = (char) 0;
|
|
|
|
|
int orderedStart = 0;
|
|
|
|
|
var orderedDelimiter = (char) 0;
|
|
|
|
|
|
2016-02-25 20:30:37 +09:00
|
|
|
var column = state.Line.Start;
|
2016-02-23 23:24:57 +09:00
|
|
|
|
2016-02-23 00:01:43 +09:00
|
|
|
if (c.IsBulletListMarker())
|
|
|
|
|
{
|
|
|
|
|
bulletChar = c;
|
|
|
|
|
preIndent++;
|
|
|
|
|
}
|
|
|
|
|
else if (c.IsDigit())
|
|
|
|
|
{
|
|
|
|
|
int countDigit = 0;
|
|
|
|
|
while (c.IsDigit())
|
|
|
|
|
{
|
|
|
|
|
orderedStart = orderedStart*10 + c - '0';
|
2016-02-25 20:30:37 +09:00
|
|
|
c = state.Line.NextChar();
|
2016-02-23 00:01:43 +09:00
|
|
|
preIndent++;
|
|
|
|
|
countDigit++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Note that ordered list start numbers must be nine digits or less:
|
|
|
|
|
if (countDigit > 9)
|
|
|
|
|
{
|
|
|
|
|
return MatchLineResult.None;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We don't have an ordered list
|
|
|
|
|
if (c != '.' && c != ')')
|
|
|
|
|
{
|
|
|
|
|
return MatchLineResult.None;
|
|
|
|
|
}
|
|
|
|
|
preIndent++;
|
|
|
|
|
isOrdered = true;
|
|
|
|
|
orderedDelimiter = c;
|
|
|
|
|
}
|
|
|
|
|
else
|
2016-02-15 20:58:12 +09:00
|
|
|
{
|
2016-02-23 00:01:43 +09:00
|
|
|
return MatchLineResult.None;
|
|
|
|
|
}
|
2016-02-15 20:58:12 +09:00
|
|
|
|
2016-02-23 00:01:43 +09:00
|
|
|
// Skip Bullet or '.'
|
2016-02-25 20:30:37 +09:00
|
|
|
state.Line.NextChar();
|
2016-02-23 00:01:43 +09:00
|
|
|
|
|
|
|
|
// Item starting with a blank line
|
|
|
|
|
int numberOfSpaces;
|
2016-02-25 20:30:37 +09:00
|
|
|
if (state.IsBlankLine)
|
2016-02-23 00:01:43 +09:00
|
|
|
{
|
|
|
|
|
// Use a negative number to store the number of expected chars
|
|
|
|
|
numberOfSpaces = -(preIndent + 1);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2016-02-16 20:35:42 +09:00
|
|
|
var startPosition = -1;
|
2016-02-23 00:01:43 +09:00
|
|
|
int countSpaceAfterBullet = 0;
|
2016-02-25 20:30:37 +09:00
|
|
|
var saved = new StringSlice();
|
2016-02-23 00:01:43 +09:00
|
|
|
for (int i = 0; i <= 4; i++)
|
2016-02-15 20:58:12 +09:00
|
|
|
{
|
2016-02-25 20:30:37 +09:00
|
|
|
c = state.Line.CurrentChar;
|
2016-02-23 00:01:43 +09:00
|
|
|
if (!c.IsSpaceOrTab())
|
2016-02-15 20:58:12 +09:00
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
2016-02-15 23:56:45 +09:00
|
|
|
if (i == 0)
|
|
|
|
|
{
|
2016-02-25 20:30:37 +09:00
|
|
|
startPosition = state.Line.Column;
|
2016-02-15 23:56:45 +09:00
|
|
|
}
|
2016-02-16 20:35:42 +09:00
|
|
|
|
2016-02-25 20:30:37 +09:00
|
|
|
var endPosition = state.Line.Column;
|
2016-02-23 00:01:43 +09:00
|
|
|
countSpaceAfterBullet = endPosition - startPosition;
|
|
|
|
|
|
|
|
|
|
if (countSpaceAfterBullet == 1)
|
|
|
|
|
{
|
2016-02-25 20:30:37 +09:00
|
|
|
saved = state.Line;
|
2016-02-23 00:01:43 +09:00
|
|
|
}
|
|
|
|
|
else if (countSpaceAfterBullet >= 4)
|
2016-02-16 20:35:42 +09:00
|
|
|
{
|
2016-02-25 20:30:37 +09:00
|
|
|
//state.Line.SpaceHeaderCount = countSpaceAfterBullet - 4;
|
2016-02-23 00:01:43 +09:00
|
|
|
countSpaceAfterBullet = 0;
|
2016-02-25 20:30:37 +09:00
|
|
|
state.SetCurrentLine(ref saved);
|
2016-02-16 20:35:42 +09:00
|
|
|
break;
|
|
|
|
|
}
|
2016-02-25 20:30:37 +09:00
|
|
|
state.Line.NextChar();
|
2016-02-16 20:35:42 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If we haven't matched any spaces, early exit
|
|
|
|
|
if (startPosition < 0)
|
|
|
|
|
{
|
|
|
|
|
return MatchLineResult.None;
|
2016-02-15 20:58:12 +09:00
|
|
|
}
|
|
|
|
|
// Number of spaces required for the following content to be part of this list item
|
2016-02-23 00:01:43 +09:00
|
|
|
numberOfSpaces = preIndent + countSpaceAfterBullet + 1;
|
|
|
|
|
}
|
2016-02-15 20:58:12 +09:00
|
|
|
|
2016-02-23 20:09:37 +09:00
|
|
|
var newListItem = new ListItemBlock(this)
|
2016-02-23 00:01:43 +09:00
|
|
|
{
|
2016-02-23 23:24:57 +09:00
|
|
|
Column = column,
|
2016-02-23 00:01:43 +09:00
|
|
|
NumberOfSpaces = numberOfSpaces
|
|
|
|
|
};
|
2016-02-23 20:27:43 +09:00
|
|
|
state.NewBlocks.Push(newListItem);
|
2016-02-23 07:50:13 +09:00
|
|
|
|
2016-02-23 20:27:43 +09:00
|
|
|
var currentListItem = state.Pending as ListItemBlock;
|
|
|
|
|
var currentParent = state.Pending as ListBlock ?? (ListBlock)currentListItem?.Parent;
|
2016-02-23 20:26:45 +09:00
|
|
|
|
2016-02-23 23:24:57 +09:00
|
|
|
if (currentParent != null)
|
2016-02-23 20:26:45 +09:00
|
|
|
{
|
2016-02-23 20:27:43 +09:00
|
|
|
// If we have a new list item, close the previous one
|
|
|
|
|
if (currentListItem != null)
|
|
|
|
|
{
|
|
|
|
|
state.Close(currentListItem);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Reset the list if it is a new list or a new type of bullet
|
|
|
|
|
if (currentParent.IsOrdered != isOrdered ||
|
|
|
|
|
(isOrdered && currentParent.OrderedDelimiter != orderedDelimiter) ||
|
|
|
|
|
(!isOrdered && currentParent.BulletChar != bulletChar)
|
|
|
|
|
//(numberOfSpaces < ((ListItemBlock) currentParent.LastChild).NumberOfSpaces)
|
|
|
|
|
)
|
|
|
|
|
{
|
|
|
|
|
state.Close(currentParent);
|
|
|
|
|
currentParent = null;
|
|
|
|
|
}
|
2016-02-23 20:26:45 +09:00
|
|
|
}
|
2016-02-15 20:58:12 +09:00
|
|
|
|
2016-02-23 20:27:43 +09:00
|
|
|
if (currentParent == null)
|
2016-02-23 00:01:43 +09:00
|
|
|
{
|
2016-02-23 20:09:37 +09:00
|
|
|
var newList = new ListBlock(this)
|
2016-02-15 20:58:12 +09:00
|
|
|
{
|
2016-02-23 23:24:57 +09:00
|
|
|
Column = column,
|
2016-02-23 20:27:43 +09:00
|
|
|
IsOrdered = isOrdered,
|
2016-02-23 00:01:43 +09:00
|
|
|
BulletChar = bulletChar,
|
|
|
|
|
OrderedDelimiter = orderedDelimiter,
|
|
|
|
|
OrderedStart = orderedStart,
|
|
|
|
|
};
|
2016-02-23 20:27:43 +09:00
|
|
|
state.NewBlocks.Push(newList);
|
2016-02-23 07:50:13 +09:00
|
|
|
}
|
|
|
|
|
|
2016-02-23 00:01:43 +09:00
|
|
|
return MatchLineResult.Continue;
|
2016-02-15 20:58:12 +09:00
|
|
|
}
|
2016-02-23 07:50:13 +09:00
|
|
|
|
2016-02-23 20:27:43 +09:00
|
|
|
public override void Close(BlockParserState state)
|
2016-02-23 07:50:13 +09:00
|
|
|
{
|
2016-02-23 20:26:45 +09:00
|
|
|
var listBlock = state.Pending as ListBlock;
|
2016-02-23 23:24:57 +09:00
|
|
|
|
2016-02-23 20:27:43 +09:00
|
|
|
// Process only if we have blank lines
|
2016-02-24 01:01:40 +09:00
|
|
|
if (listBlock != null && listBlock.CountAllBlankLines > 0)
|
2016-02-23 07:50:13 +09:00
|
|
|
{
|
2016-02-24 01:01:40 +09:00
|
|
|
bool isLastListItem = true;
|
2016-02-23 20:27:43 +09:00
|
|
|
for (int listIndex = listBlock.Children.Count - 1; listIndex >= 0; listIndex--)
|
|
|
|
|
{
|
|
|
|
|
var block = listBlock.Children[listIndex];
|
|
|
|
|
var listItem = (ListItemBlock) block;
|
|
|
|
|
var children = listItem.Children;
|
2016-02-24 01:01:40 +09:00
|
|
|
bool isLastElement = true;
|
2016-02-23 20:27:43 +09:00
|
|
|
for (int i = children.Count - 1; i >= 0; i--)
|
|
|
|
|
{
|
|
|
|
|
var item = children[i];
|
|
|
|
|
if (item is BlankLineBlock)
|
|
|
|
|
{
|
2016-02-24 01:01:40 +09:00
|
|
|
if ((isLastElement && listIndex < listBlock.Children.Count - 1) || (children.Count > 2 && (i > 0 && i < (children.Count - 1))))
|
2016-02-23 20:27:43 +09:00
|
|
|
{
|
|
|
|
|
listBlock.IsLoose = true;
|
|
|
|
|
}
|
2016-02-24 01:01:40 +09:00
|
|
|
|
|
|
|
|
if (isLastElement && isLastListItem)
|
|
|
|
|
{
|
|
|
|
|
// Inform the outer list that we have a blank line
|
|
|
|
|
var parentListItemBlock = listBlock.Parent as ListItemBlock;
|
|
|
|
|
if (parentListItemBlock != null)
|
|
|
|
|
{
|
|
|
|
|
var parentList = (ListBlock) parentListItemBlock.Parent;
|
|
|
|
|
|
|
|
|
|
parentList.CountAllBlankLines++;
|
|
|
|
|
parentListItemBlock.Children.Add(BlankLineBlock.Instance);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-23 20:27:43 +09:00
|
|
|
children.RemoveAt(i);
|
2016-02-24 01:01:40 +09:00
|
|
|
|
|
|
|
|
// If we have remove all blank lines, we can exit
|
|
|
|
|
listBlock.CountAllBlankLines--;
|
|
|
|
|
if (listBlock.CountAllBlankLines == 0)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
2016-02-23 20:27:43 +09:00
|
|
|
}
|
2016-02-24 01:01:40 +09:00
|
|
|
isLastElement = false;
|
2016-02-23 20:27:43 +09:00
|
|
|
}
|
2016-02-24 01:01:40 +09:00
|
|
|
isLastListItem = false;
|
2016-02-23 20:27:43 +09:00
|
|
|
}
|
2016-02-23 07:50:13 +09:00
|
|
|
}
|
|
|
|
|
}
|
2016-02-15 20:58:12 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|