manaba-enhanced-for-tsukuba/manaba-enhanced

View on GitHub
src/methods/ReportTemplateGenerator.tsx

Summary

Maintainability
A
0 mins
Test Coverage
import { ReactElement } from "react"
import ReactDOM from "react-dom"

class ReportTemplateGenerator {
  constructor(
    rawFilename: string,
    rawTemplate: string,
    public readonly reportInfo: Pick<
      ReportInfo,
      "courseName" | "deadline" | "description" | "reportTitle" | "studentName"
    > = new ReportInfo(document)
  ) {
    this.filename = this.parseFilename(
      rawFilename || ReportTemplateGenerator.defaultFilename
    )
    this.template = ReportTemplateGenerator.addSignature(
      this.parseTemplate(rawTemplate || ReportTemplateGenerator.defaultTemplate)
    )
  }

  public readonly filename: string
  public readonly template: string

  private GenerateButton = ({
    template,
    filename,
  }: {
    template: string
    filename: string
  }) => (
    <button
      type="button"
      onClick={() => this.downloadReportTemplate(template, filename)}
      className="manaba-original-button"
    >
      {chrome.i18n.getMessage("generate_report_template")}
    </button>
  )

  private downloadReportTemplate = (template: string, filename: string) => {
    const blob = new Blob([template], {
      type: "application/x-tex",
    })
    const url = URL.createObjectURL(blob)
    chrome.runtime.sendMessage({
      url,
      filename,
    })
  }

  public appendReportGeneratorRow = () => {
    const tbodyQueryString = ".stdlist-reportV2 tbody"
    const tbody = document.querySelector<HTMLElement>(tbodyQueryString)
    if (!tbody) return

    const containerRow = document.createElement("tr")
    tbody.appendChild(containerRow)

    ReactDOM.render(
      <RowContent
        header={chrome.i18n.getMessage("report_template")}
        data={
          <this.GenerateButton
            template={this.template}
            filename={this.filename}
          />
        }
      />,
      containerRow
    )
  }

  private injectReportInfoIntoRawText = (
    rawText: string,
    reportInfo: Pick<
      ReportInfo,
      "courseName" | "deadline" | "description" | "reportTitle" | "studentName"
    >
  ) =>
    Object.entries(reportInfo).reduce(
      (acc, [key, value]) =>
        acc.replaceAll(
          `{{${this.camelcaseToKebabcase(key)}}}`,
          typeof value === "string" ? value : value.toLocaleString()
        ),
      rawText
    )

  private parseFilename = (rawFilename: string) =>
    this.injectReportInfoIntoRawText(rawFilename, this.reportInfo)
      .replaceAll(/[\\/:*?"<>|]/g, "_")
      .trim()

  private parseTemplate = (rawTemplate: string) =>
    this.injectReportInfoIntoRawText(rawTemplate, this.reportInfo)

  private camelcaseToKebabcase = (camelcase: string) =>
    camelcase.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, "$1-$2").toLowerCase()

  static addSignature = (template: string) =>
    `% This file is generated by manaba Enhanced.
${template}`

  static defaultFilename = `{{student-name}}_{{course-name}}_{{report-title}}.tex`

  static defaultTemplate = `% The deadline is {{deadline}}.

\\documentclass{ltjsarticle}
\\usepackage{listings}

\\begin{document}
\\title{{{course-name}}\\\\{{report-title}}}
\\author{{{student-name}}\\\\${chrome.i18n.getMessage(
    "report_template_student_code"
  )}}
\\lstset{
numbers=left,
frame=single,
breaklines=true,
}

\\maketitle

% {{description}}

% \\section{${chrome.i18n.getMessage(
    "report_template_introduction_section_title"
  )}}

\\end{document}
`
}

class ReportInfo {
  constructor(private document: Document) {
    this.courseName =
      this.courseNameElement?.title ?? chrome.i18n.getMessage("course_name")
    this.reportTitle =
      this.reportTitleElement?.innerText ??
      chrome.i18n.getMessage("report_title")
    this.description =
      this.descriptionElement?.innerText ??
      chrome.i18n.getMessage("description")
    this.studentName =
      this.screennameElement?.innerText ??
      chrome.i18n.getMessage("student_name")
    this.deadline = new Date(
      this.deadlineElement?.innerText.substring(0, 16) ??
        chrome.i18n.getMessage("deadline")
    )
  }

  public courseName
  public reportTitle
  public studentName
  public deadline
  public description

  private courseNameElement = this.document.getElementById("coursename")
  private screennameElement = this.document.getElementById("screenname")
  private reportTitleElement =
    this.document.querySelector<HTMLTableCellElement>(
      ".stdlist-reportV2 .title th"
    )
  private tdElements = this.document.querySelectorAll<HTMLTableCellElement>(
    ".stdlist-reportV2 td"
  )
  private descriptionElement = this.tdElements[0]
  private deadlineElement = this.tdElements[2]
}

const RowContent = ({
  header,
  data,
}: {
  header: string
  data: ReactElement | string
}) => (
  <>
    <th>{header}</th>
    <td className="left">{data}</td>
  </>
)

export { ReportTemplateGenerator, RowContent }