diff --git a/.github/workflows/check-format-and-test-python-markdown-extension.yml b/.github/workflows/check-format-and-test-python-markdown-extension.yml new file mode 100644 index 00000000..df6737de --- /dev/null +++ b/.github/workflows/check-format-and-test-python-markdown-extension.yml @@ -0,0 +1,46 @@ +name: Check PR Format and Test for python-markdown-extension + +on: + pull_request: + branches: + - master + paths: + - python-markdown-extension/** + +jobs: + check-format: + name: Check PR Format + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./python-markdown-extension + steps: + - uses: actions/checkout@v4 + name: Checkout Repo + - uses: eifinger/setup-rye@v3 + name: Setup Rye + with: + enable-cache: true + working-directory: python-markdown-extension + - run: rye sync + name: Install Dependencies + - run: rye fmt --check + name: Check Format + test: + name: Test PR + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./python-markdown-extension + steps: + - uses: actions/checkout@v4 + name: Checkout Repo + - uses: eifinger/setup-rye@v3 + name: Setup Rye + with: + enable-cache: true + working-directory: python-markdown-extension + - run: rye sync + name: Install Dependencies + - run: rye run test + name: Run Tests diff --git a/.gitignore b/.gitignore deleted file mode 100755 index 826b026a..00000000 --- a/.gitignore +++ /dev/null @@ -1,36 +0,0 @@ -public -.cache -node_modules -*DS_Store -*.env - -.idea/ - -yarn-error.log -.vscode - -__generated__/ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? diff --git a/index.html b/index.html deleted file mode 100644 index 44a93350..00000000 --- a/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - Vite + TS - - -
- - - diff --git a/package.json b/package.json deleted file mode 100644 index 07c6a13a..00000000 --- a/package.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "feedback-sys", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview" - }, - "devDependencies": { - "typescript": "^5.2.2", - "vite": "^5.2.0" - } -} diff --git a/public/vite.svg b/public/vite.svg deleted file mode 100644 index e7b8dfb1..00000000 --- a/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/python-markdown-extension/.gitignore b/python-markdown-extension/.gitignore new file mode 100644 index 00000000..bf07c5cb --- /dev/null +++ b/python-markdown-extension/.gitignore @@ -0,0 +1,12 @@ +# python generated files +__pycache__/ +*.py[oc] +build/ +dist/ +wheels/ +*.egg-info + +# venv +.venv + +.idea/ \ No newline at end of file diff --git a/python-markdown-extension/.python-version b/python-markdown-extension/.python-version new file mode 100644 index 00000000..871f80a3 --- /dev/null +++ b/python-markdown-extension/.python-version @@ -0,0 +1 @@ +3.12.3 diff --git a/python-markdown-extension/pyproject.toml b/python-markdown-extension/pyproject.toml new file mode 100644 index 00000000..b641660a --- /dev/null +++ b/python-markdown-extension/pyproject.toml @@ -0,0 +1,33 @@ +[project] +name = "python_markdown_document_offsets_injection_extension" +version = "0.0.1" +description = "A Python-Markdown compiler plugin that put markdown words offset to the output HTML." +authors = [{ name = "HikariLan", email = "hikarilan@minecraft.kim" }] +license = { text = "Apache-2.0" } +dependencies = [ + "markdown>=3.6", +] +requires-python = ">= 3.8" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.rye] +managed = true +dev-dependencies = [ + "pygments>=2.18.0", + "pymdown-extensions>=10.8.1", +] + +[tool.hatch.metadata] +allow-direct-references = true + +[tool.hatch.build.targets.wheel] +packages = ["src/python_markdown_document_offsets_injection_extension"] + +[tool.rye.scripts] +test = "python ./test" + +[project.entry-points."markdown.extensions"] +document-offsets-injection = "python_markdown_document_offsets_injection_extension.extension:MainExtension" diff --git a/python-markdown-extension/requirements-dev.lock b/python-markdown-extension/requirements-dev.lock new file mode 100644 index 00000000..a33eba1b --- /dev/null +++ b/python-markdown-extension/requirements-dev.lock @@ -0,0 +1,18 @@ +# generated by rye +# use `rye lock` or `rye sync` to update this lockfile +# +# last locked with the following flags: +# pre: false +# features: [] +# all-features: false +# with-sources: false +# generate-hashes: false + +-e file:. +markdown==3.6 + # via pymdown-extensions + # via python-markdown-document-offsets-injection-extension +pygments==2.18.0 +pymdown-extensions==10.8.1 +pyyaml==6.0.1 + # via pymdown-extensions diff --git a/python-markdown-extension/requirements.lock b/python-markdown-extension/requirements.lock new file mode 100644 index 00000000..2e8b89cd --- /dev/null +++ b/python-markdown-extension/requirements.lock @@ -0,0 +1,13 @@ +# generated by rye +# use `rye lock` or `rye sync` to update this lockfile +# +# last locked with the following flags: +# pre: false +# features: [] +# all-features: false +# with-sources: false +# generate-hashes: false + +-e file:. +markdown==3.6 + # via python-markdown-document-offsets-injection-extension diff --git a/python-markdown-extension/src/python_markdown_document_offsets_injection_extension/__init__.py b/python-markdown-extension/src/python_markdown_document_offsets_injection_extension/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/python-markdown-extension/src/python_markdown_document_offsets_injection_extension/extension.py b/python-markdown-extension/src/python_markdown_document_offsets_injection_extension/extension.py new file mode 100644 index 00000000..a2aa3b5b --- /dev/null +++ b/python-markdown-extension/src/python_markdown_document_offsets_injection_extension/extension.py @@ -0,0 +1,247 @@ +import re +from markdown import Extension, Markdown +from markdown.preprocessors import Preprocessor +from markdown.blockprocessors import BlockProcessor +from markdown.blockparser import BlockParser +import xml.etree.ElementTree as etree + +MARK_PREVENT_RECURSION: str = "\t\t\t\r\r\rMARK_PREVENT_RECURSION\r\r\r\t\t\t" + +MARK_CONTINUE: str = "\t\t\t\r\r\rMARK_CONTINUE\r\r\r\t\t\t" + +# @see: markdown.util.HTML_PLACEHOLDER_RE +# PYTHON_MARKDOWN_HTML_PLACEHOLDER_RE: re.Pattern[str] = re.compile( +# "\u0002wzxhzdk:%s\u0003" % r"([0-9]+)" +# ) + + +class MainExtension(Extension): + def extendMarkdown(self, md: Markdown): + meta: dict = { + "document_offsets": [], + "used_document_offsets": {}, + "last_parent": None, + } + md.preprocessors.register( + CalculateDocumentOffsetPreprocessor(md, meta), "capture_document", 1000 + ) # Highest priority is required because we need to calc words offset from original document + md.preprocessors.register( + FixDocumentOffsetPreprocessor(md, meta), "fix_document", 0 + ) # Lowest priority is required because we need to fix the offset after all other block processors + md.parser.blockprocessors.register( + OffsetsInjectionBlockProcessor(md.parser, meta), "mark_words", 200 + ) # high priority, usually larger than every other block processor + + +class CalculateDocumentOffsetPreprocessor(Preprocessor): + """ + A preprocessor to calculate the offset of each line in the document + """ + + def __init__(self, md: Markdown, meta: dict): + super(CalculateDocumentOffsetPreprocessor, self).__init__(md) + self.meta = meta + + def run(self, lines: list[str]) -> list[str]: + offset: int = 0 + for line in lines: + # Skip empty lines + if len(line) == 0: + store: tuple[str, int, int] = (line, offset, offset + 1) + self.meta["document_offsets"].append(store) + self.meta["used_document_offsets"][store] = False + offset += 1 + continue + # store the line and offset + store: tuple[str, int, int] = (line, offset, offset + len(line)) + self.meta["document_offsets"].append(store) + self.meta["used_document_offsets"][store] = False + # plus 1 is for the newline character (\n), use the CRLF file is unknown behavior + offset += len(line) + 1 + return lines + + +class FixDocumentOffsetPreprocessor(Preprocessor): + """ + A preprocessor to fix the offset of each line after the 3rd party extension processed the document + """ + + def __init__(self, md: Markdown, meta: dict): + super(FixDocumentOffsetPreprocessor, self).__init__(md) + self.meta = meta + + def run(self, lines: list[str]) -> list[str]: + document_offsets: list[tuple[str, int, int]] = self.meta["document_offsets"] + + # 最后一次成功匹配的文档偏移量字典索引末,开区间 + last_success_match_end: int = 0 + num_lines: int = 0 + num_document_offsets: int = 0 + while num_document_offsets < len(document_offsets) and num_lines < len(lines): + line = lines[num_lines] + document_offset: tuple[str, int, int] = document_offsets[ + num_document_offsets + ] + + # 如果精准匹配 + if document_offset[0] == line: + # 匹配该行 + self.match(line, num_document_offsets, num_document_offsets + 1) + # 如果上次成功匹配的原文档偏移量未连续,匹配当前行到这部分未连续的原文档偏移量 + if num_document_offsets > last_success_match_end and num_lines > 0: + self.match( + lines[num_lines - 1], + last_success_match_end, + num_document_offsets, + ) + last_success_match_end = num_document_offsets + 1 + num_lines += 1 + num_document_offsets += 1 + # 如果未能精准匹配,查找该行在原文档偏移量字典中的位置 + else: + remain: list[str] = [ + line for line, _, _ in document_offsets[num_document_offsets:] + ] + # 如果存在这样的行 + if line in remain: + # 找到第一次匹配的位置,匹配该行到此处 + idx = remain.index(line) + num_document_offsets + self.match(line, idx, idx + 1) + # 如果上次成功匹配的原文档偏移量未连续,匹配当前行到这部分未连续的原文档偏移量 + if idx > last_success_match_end and num_lines > 0: + self.match(lines[num_lines - 1], last_success_match_end, idx) + last_success_match_end = idx + 1 + num_lines += 1 + num_document_offsets = idx + 1 + # 如果未找到匹配的位置,继续查找下一行 + else: + num_lines += 1 + + # 如果行匹配完成,但原文档偏移量未匹配完成,匹配剩余的原文档偏移量 + if last_success_match_end < len(document_offsets): + self.match( + lines[num_lines - 1], last_success_match_end, len(document_offsets) + ) + + return lines + + def match( + self, + matched_line: str, + num_document_offsets_start: int, + num_document_offsets_end: int, + ): + """ + 将单个匹配行设置到多个原文档偏移量字典,索引范围为[num_document_offsets_start, num_document_offsets_end) + """ + document_offsets: list[tuple[str, int, int]] = self.meta["document_offsets"] + used_document_offsets: dict[tuple[str, int, int], bool] = self.meta[ + "used_document_offsets" + ] + for i in range(num_document_offsets_start, num_document_offsets_end): + document_offset = document_offsets[i] + # 如果是第一个匹配的原文档偏移量,设置为匹配行,否则设置为 MARK_CONTINUE + if i == num_document_offsets_start: + document_offsets[i] = ( + matched_line, + document_offset[1], + document_offset[2], + ) + else: + document_offsets[i] = ( + MARK_CONTINUE, + document_offset[1], + document_offset[2], + ) + del used_document_offsets[document_offset] + used_document_offsets[document_offsets[i]] = False + + +class OffsetsInjectionBlockProcessor(BlockProcessor): + """ + A block processor to mark the words in the document and inject the offset of the block to the HTML element + """ + + def __init__(self, parser: BlockParser, meta: dict): + super(OffsetsInjectionBlockProcessor, self).__init__(parser) + self.meta = meta + + def test(self, _, block) -> bool: + # Test if there is any line in the block + for line in [line for (line, _, _) in self.meta["document_offsets"]]: + if line in block: + return True + return False + + def run(self, parent: etree.Element, blocks: list[str]) -> bool: + """ + 注入文档中的偏移量到HTML元素中,以便在后续的处理中可以使用这些偏移量来定位文档中的位置。目前的算法如下: + 1. 从文档中查找第一个包含文本的块 + 2. 查找这个块在文档中的位置,这通过遍历文档中的每一行,以找到所有被包含在该块中的行,通过获取这些行的起始和结束位置,来确定这个块在文档中的位置 + 3. 注入这个块的起始和结束位置到HTML元素中,这会先递归的解析这个块,然后再注入这个块的起始和结束位置注入到最后一个被生成的HTML元素中 + 由于递归解析块时该块仍会被本处理器捕获,为了避免循环递归,我们在块的末尾添加了MARK_PREVENT_RECURSION标记,当本处理器再次捕获到这个块时,会直接跳过这个块,并清除这个标记。 + """ + + block: str = blocks[0] + + # If the first block is handled, remove the marker and return, so that other block processors can process it + if MARK_PREVENT_RECURSION in blocks[0]: + blocks[0] = blocks[0].replace(MARK_PREVENT_RECURSION, "") + return False + + start: int | None = None + end: int | None = None + used: dict[tuple[str, int, int], bool] = {} + # Search for the block fragment in the document_offsets + for store in self.meta["document_offsets"]: + # Skip empty lines + if len(store[0]) == 0: + continue + # If already used, skip + if self.meta["used_document_offsets"][store]: + continue + (line, offset, end_offset) = store + # 如果收到 MARK_CONTINUE 标记,直接认为该标记之前的行是连续的 + if line == MARK_CONTINUE: + end = end_offset + used[store] = True + continue + # If found one + if line in block: + # If the line already scanned (usually some lines with same content in different place), skip + if line in [line for (line, _, _) in used.keys()]: + continue + # If none yet set, set the start offset + if start is None: + start = offset + end = end_offset + # Or, continuing searching for the end offset until the end of the block + else: + end = end_offset + # Mark the fragment as used + used[store] = True + # If end is not found but new line not in block, reset the search and restart from the next line + elif end is None: + start = None + # Clear the used list + used = {} + continue + # If both start and end are both set and no continuously block found, break the loop + else: + break + # If both start and end are found, store the result + if start is not None and end is not None: + blocks.pop(0) + self.meta["used_document_offsets"].update(used) + # append MARK_PREVENT_RECURSION to tail of the block to prevent recursion, we don't use a handled + # flaglist because we don't know if there's some same block in the document + self.parser.parseBlocks(parent, [block + MARK_PREVENT_RECURSION]) + # fix multi blocks in same parents + if self.meta["last_parent"] == parent[-1]: + parent[-1].set("data-original-document-end", str(end)) + return True + parent[-1].set("data-original-document-start", str(start)) + parent[-1].set("data-original-document-end", str(end)) + self.meta["last_parent"] = parent[-1] + return True + return False diff --git a/python-markdown-extension/test/__main__.py b/python-markdown-extension/test/__main__.py new file mode 100644 index 00000000..93f2eb00 --- /dev/null +++ b/python-markdown-extension/test/__main__.py @@ -0,0 +1,375 @@ +import textwrap +import unittest +import markdown +from html.parser import HTMLParser + +from pymdownx.emoji import to_svg +from pymdownx.slugs import uslugify +from pymdownx.arithmatex import fence_mathjax_format + + +class Tester: + def __init__(self, case, test_case: unittest.TestCase): + self.case = case + """ + @see: https://github.com/OI-wiki/OI-wiki/blob/65983038c40716dd0644778fe7875e91c9043618/mkdocs.yml#L586 + + # Extensions + markdown_extensions: + - admonition + - def_list + - footnotes + - meta + - toc: + permalink: "" + slugify: !!python/name:pymdownx.slugs.uslugify + - pymdownx.arithmatex: + generic: true + - pymdownx.caret + - pymdownx.critic + - pymdownx.details + - pymdownx.emoji: + emoji_generator: !!python/name:pymdownx.emoji.to_svg + - pymdownx.highlight: + linenums: true + - pymdownx.inlinehilite + - pymdownx.keys + - pymdownx.magiclink + - pymdownx.mark + - pymdownx.snippets: + check_paths: true + - pymdownx.progressbar + - pymdownx.smartsymbols + - pymdownx.superfences: + custom_fences: + - name: math + class: arithmatex + format: !!python/name:pymdownx.arithmatex.fence_mathjax_format + - pymdownx.tasklist: + custom_checkbox: true + - pymdownx.tilde + - pymdownx.tabbed: + alternate_style: true + """ + self.result = markdown.markdown( + self.case["document"], + extensions=[ + "document-offsets-injection", + "admonition", + "def_list", + "footnotes", + "meta", + "toc", + "pymdownx.arithmatex", + "pymdownx.caret", + "pymdownx.critic", + "pymdownx.details", + "pymdownx.emoji", + "pymdownx.highlight", + "pymdownx.inlinehilite", + "pymdownx.keys", + "pymdownx.magiclink", + "pymdownx.mark", + "pymdownx.snippets", + "pymdownx.progressbar", + "pymdownx.smartsymbols", + "pymdownx.superfences", + "pymdownx.tasklist", + "pymdownx.tilde", + "pymdownx.tabbed", + ], + extension_configs={ + "toc": { + "permalink": "", + "slugify": uslugify, + }, + "pymdownx.arithmatex": { + "generic": True, + }, + "pymdownx.emoji": { + "emoji_generator": to_svg, + }, + "pymdownx.highlight": { + "linenums": True, + }, + "pymdownx.snippets": { + "check_paths": True, + }, + "pymdownx.superfences": { + "custom_fences": [ + { + "name": "math", + "class": "arithmatex", + "format": fence_mathjax_format, + }, + ], + }, + "pymdownx.tasklist": { + "custom_checkbox": True, + }, + "pymdownx.tabbed": { + "alternate_style": True, + }, + }, + ) + self.test_case = test_case + + def test(self): + tester = ParserTester(self.case, self.test_case) + tester.feed(self.result) + tester.check_integrity() + + +class ParserTester(HTMLParser): + tag = None + offset_start = None + offset_end = None + + def __init__(self, case, test_case: unittest.TestCase): + super().__init__() + self.test_case = test_case + self.case = case + self.idx = 0 + + def handle_starttag(self, tag, attrs): + start = None + end = None + for attr in attrs: + if attr[0] == "data-original-document-start": + start = int(attr[1]) + if attr[0] == "data-original-document-end": + end = int(attr[1]) + if start is not None and end is not None: + self.tag = tag + self.offset_start = start + self.offset_end = end + + def handle_endtag(self, tag): + if self.tag != tag: + return # ignore nested tags + if self.idx == len(self.case["expected"]): + return # ignore extra tags + self._test() + self._reset() + + def _test(self): + self.test_case.assertEqual( + self.tag, + self.case["expected"][self.idx]["tag"], + msg="Tag mismatch in index " + str(self.idx), + ) + self.test_case.assertEqual( + self.offset_start, + self.case["expected"][self.idx]["offset"][0], + msg="Offset start mismatch in index " + str(self.idx), + ) + self.test_case.assertEqual( + self.offset_end, + self.case["expected"][self.idx]["offset"][1], + msg="Offset end mismatch in index " + str(self.idx), + ) + self.idx += 1 + + def _reset(self): + self.tag = None + self.offset_start = None + self.offset_end = None + + def check_integrity(self): + self.test_case.assertEqual( + self.idx, + len(self.case["expected"]), + msg="Not all tags were found", + ) + + +class TestParser(unittest.TestCase): + def test_normal(self): + case = { + "document": textwrap.dedent("""\ + # Lorem ipsum + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin sed lacus vitae neque vestibulum porttitor id et urna. + + ## Morbi neque lectus + + Morbi neque lectus, faucibus a mattis at, aliquam quis est. Maecenas sed luctus elit."""), + "expected": [ + {"tag": "h1", "offset": (0, 13)}, + { + "tag": "p", + "offset": (15, 132), + }, + {"tag": "h2", "offset": (134, 155)}, + { + "tag": "p", + "offset": (157, 242), + }, + ], + } + Tester(case, self).test() + + def test_empty(self): + case = { + "document": "", + "expected": [], + } + Tester(case, self).test() + + def test_single(self): + case = { + "document": "Lorem ipsum", + "expected": [ + {"tag": "p", "offset": (0, 11)}, + ], + } + Tester(case, self).test() + + def test_oi_wiki_index(self): + case = { + "document": textwrap.dedent("""\ + disqus: + pagetime: + title: OI Wiki + + ## 欢迎来到 **OI Wiki**![![GitHub watchers](https://img.shields.io/github/watchers/OI-wiki/OI-wiki.svg?style=social&label=Watch)](https://github.com/OI-wiki/OI-wiki) [![GitHub stars](https://img.shields.io/github/stars/OI-wiki/OI-wiki.svg?style=social&label=Stars)](https://github.com/OI-wiki/OI-wiki) + + [![Word Art](images/wordArt.webp)](https://github.com/OI-wiki/OI-wiki) + + **OI**(Olympiad in Informatics,信息学奥林匹克竞赛)在中国起源于 1984 年,是五大高中学科竞赛之一。 + + **ICPC**(International Collegiate Programming Contest,国际大学生程序设计竞赛)由 ICPC 基金会(ICPC Foundation)举办,是最具影响力的大学生计算机竞赛。由于以前 ACM 赞助这个竞赛,也有很多人习惯叫它 ACM 竞赛。 + + **OI Wiki** 致力于成为一个免费开放且持续更新的 **编程竞赛(competitive programming)** 知识整合站点,大家可以在这里获取与竞赛相关的、有趣又实用的知识。我们为大家准备了竞赛中的基础知识、常见题型、解题思路以及常用工具等内容,帮助大家更快速深入地学习编程竞赛中涉及到的知识。 + + 本项目受 [CTF Wiki](https://ctf-wiki.org/) 的启发,在编写过程中参考了诸多资料,在此一并致谢。 + +
+ +
+ + """), + "expected": [ + { + "tag": "h2", + "offset": (34, 332), + }, + { + "tag": "p", + "offset": (334, 404), + }, + { + "tag": "p", + "offset": (406, 473), + }, + { + "tag": "p", + "offset": (475, 620), + }, + { + "tag": "p", + "offset": (622, 778), + }, + { + "tag": "p", + "offset": (780, 1101), # FIXME: Correct one is (780, 1101) + }, + ], + } + Tester(case, self).test() + + def test_oi_wiki_search_dfs(self): + case = { + "document": textwrap.dedent("""\ + ## 引入 + + DFS 为图论中的概念,详见 [DFS(图论)](../graph/dfs.md) 页面。在 **搜索算法** 中,该词常常指利用递归函数方便地实现暴力枚举的算法,与图论中的 DFS 算法有一定相似之处,但并不完全相同。 + + ## 解释 + + 考虑这个例子: + + ???+ note "例题" + 把正整数 $n$ 分解为 $3$ 个不同的正整数,如 $6=1+2+3$,排在后面的数必须大于等于前面的数,输出所有方案。 + + 对于这个问题,如果不知道搜索,应该怎么办呢? + + 当然是三重循环,参考代码如下: + + ???+ note "实现" + === "C++" + ```cpp + for (int i = 1; i <= n; ++i) + for (int j = i; j <= n; ++j) + for (int k = j; k <= n; ++k) + if (i + j + k == n) printf("%d = %d + %d + %d\\n", n, i, j, k); + ``` + + === "Python" + ```python + for i in range(1, n + 1): + for j in range(i, n + 1): + for k in range(j, n + 1): + if i + j + k == n: + print("%d = %d + %d + %d" % (n, i, j, k)) + ``` + + === "Java" + ```Java + for (int i = 1; i < n + 1; i++) { + for (int j = i; j < n + 1; j++) { + for (int k = j; k < n + 1; k++) { + if (i + j + k == n) System.out.printf("%d = %d + %d + %d%n", n, i, j, k); + } + } + } + ``` + + 那如果是分解成四个整数呢?再加一重循环?"""), + "expected": [ + { + "tag": "h2", + "offset": (0, 5), + }, + { + "tag": "p", + "offset": (7, 117), + }, + { + "tag": "h2", + "offset": (119, 124), + }, + { + "tag": "p", + "offset": (126, 133), + }, + { + "tag": "details", + "offset": (135, 215), + }, + { + "tag": "p", + "offset": (217, 239), + }, + { + "tag": "p", + "offset": (241, 256), + }, + { + "tag": "details", + "offset": (258, 1092), + }, + { + "tag": "p", + "offset": (1094, 1114), + }, + ], + } + Tester(case, self).test() + + +if __name__ == "__main__": + unittest.main() diff --git a/src/counter.ts b/src/counter.ts deleted file mode 100644 index 09e5afd2..00000000 --- a/src/counter.ts +++ /dev/null @@ -1,9 +0,0 @@ -export function setupCounter(element: HTMLButtonElement) { - let counter = 0 - const setCounter = (count: number) => { - counter = count - element.innerHTML = `count is ${counter}` - } - element.addEventListener('click', () => setCounter(counter + 1)) - setCounter(0) -} diff --git a/src/main.ts b/src/main.ts deleted file mode 100644 index 791547b0..00000000 --- a/src/main.ts +++ /dev/null @@ -1,24 +0,0 @@ -import './style.css' -import typescriptLogo from './typescript.svg' -import viteLogo from '/vite.svg' -import { setupCounter } from './counter.ts' - -document.querySelector('#app')!.innerHTML = ` -
- - - - - - -

Vite + TypeScript

-
- -
-

- Click on the Vite and TypeScript logos to learn more -

-
-` - -setupCounter(document.querySelector('#counter')!) diff --git a/src/style.css b/src/style.css deleted file mode 100644 index f9c73502..00000000 --- a/src/style.css +++ /dev/null @@ -1,96 +0,0 @@ -:root { - font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -#app { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.vanilla:hover { - filter: drop-shadow(0 0 2em #3178c6aa); -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } -} diff --git a/src/typescript.svg b/src/typescript.svg deleted file mode 100644 index d91c910c..00000000 --- a/src/typescript.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts deleted file mode 100644 index 11f02fe2..00000000 --- a/src/vite-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index 75abdef2..00000000 --- a/tsconfig.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "useDefineForClassFields": true, - "module": "ESNext", - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true - }, - "include": ["src"] -}