From 37fb51becffde292b067ab1322fc671dbb99a42d Mon Sep 17 00:00:00 2001 From: andyeisenberg Date: Tue, 13 Jan 2026 22:26:28 -0500 Subject: [PATCH] Latest build deployed. --- course/.mapping.json | 2 +- course/_static/_runestone-services.xml | 2 +- course/_static/webpack_static_imports.xml | 2 +- course/ch-notes.html | 194 + course/course-documents.html | 15 +- course/frontmatter.html | 15 +- .../latex-image/fig-Venn-diagram-2.svg | 27 + .../fig-Venn-diagram-complement-2.svg | 12 + .../fig-Venn-diagram-difference-2.svg | 12 + .../fig-Venn-diagram-intersection-2.svg | 23 + .../latex-image/fig-Venn-diagram-union-2.svg | 12 + course/index.html | 2 +- course/lunr-pretext-search-index.js | 191 +- course/notes-week-01.html | 501 ++ .../recitation-calculus-review-printable.html | 19 +- course/recitation-calculus-review.html | 19 +- course/recitations.html | 19 +- course/root-1-2.html | 16 +- course/syllabus.html | 19 +- slides-annotated/_static/pretext/js/answer.js | 552 ++ .../_static/pretext/js/diagcess/diagcess.js | 1 + slides-annotated/_static/pretext/js/edit.js | 4988 +++++++++++++++++ .../_static/pretext/js/highlight.js | 480 ++ .../_static/pretext/js/instructor.js | 131 + .../_static/pretext/js/lib/jquery.espy.min.js | 195 + .../_static/pretext/js/lib/jquery.min.js | 2 + .../_static/pretext/js/lib/jquery.sticky.js | 161 + .../_static/pretext/js/lib/knowl.js | 338 ++ .../_static/pretext/js/lib/mathjaxknowl.js | 109 + .../_static/pretext/js/lib/mathjaxknowl3.js | 48 + slides-annotated/_static/pretext/js/login.js | 322 ++ .../_static/pretext/js/lti_iframe_resizer.js | 48 + .../pretext/js/pretext-stack/stackapicalls.js | 515 ++ .../pretext/js/pretext-stack/stackjsvle.js | 652 +++ .../pretext-webwork/2.16/pretext-webwork.js | 753 +++ .../pretext-webwork/2.17/pretext-webwork.js | 1058 ++++ .../pretext-webwork/2.18/pretext-webwork.js | 1058 ++++ .../pretext-webwork/2.19/pretext-webwork.js | 677 +++ .../pretext-webwork/2.20/pretext-webwork.js | 686 +++ .../_static/pretext/js/pretext.js | 257 + .../_static/pretext/js/pretext_add_on.js | 1264 +++++ .../_static/pretext/js/pretext_search.js | 315 ++ .../_static/pretext/js/ptx_search.js | 112 + .../_static/pretext/js/user_preferences.js | 510 ++ slides-annotated/index.html | 206 + slides/_static/pretext/js/answer.js | 552 ++ .../_static/pretext/js/diagcess/diagcess.js | 1 + slides/_static/pretext/js/edit.js | 4988 +++++++++++++++++ slides/_static/pretext/js/highlight.js | 480 ++ slides/_static/pretext/js/instructor.js | 131 + .../_static/pretext/js/lib/jquery.espy.min.js | 195 + slides/_static/pretext/js/lib/jquery.min.js | 2 + .../_static/pretext/js/lib/jquery.sticky.js | 161 + slides/_static/pretext/js/lib/knowl.js | 338 ++ slides/_static/pretext/js/lib/mathjaxknowl.js | 109 + .../_static/pretext/js/lib/mathjaxknowl3.js | 48 + slides/_static/pretext/js/login.js | 322 ++ .../_static/pretext/js/lti_iframe_resizer.js | 48 + .../pretext/js/pretext-stack/stackapicalls.js | 515 ++ .../pretext/js/pretext-stack/stackjsvle.js | 652 +++ .../pretext-webwork/2.16/pretext-webwork.js | 753 +++ .../pretext-webwork/2.17/pretext-webwork.js | 1058 ++++ .../pretext-webwork/2.18/pretext-webwork.js | 1058 ++++ .../pretext-webwork/2.19/pretext-webwork.js | 677 +++ .../pretext-webwork/2.20/pretext-webwork.js | 686 +++ slides/_static/pretext/js/pretext.js | 257 + slides/_static/pretext/js/pretext_add_on.js | 1264 +++++ slides/_static/pretext/js/pretext_search.js | 315 ++ slides/_static/pretext/js/ptx_search.js | 112 + slides/_static/pretext/js/user_preferences.js | 510 ++ slides/index.html | 249 + 71 files changed, 32001 insertions(+), 20 deletions(-) create mode 100755 course/ch-notes.html create mode 100755 course/generated/latex-image/fig-Venn-diagram-2.svg create mode 100755 course/generated/latex-image/fig-Venn-diagram-complement-2.svg create mode 100755 course/generated/latex-image/fig-Venn-diagram-difference-2.svg create mode 100755 course/generated/latex-image/fig-Venn-diagram-intersection-2.svg create mode 100755 course/generated/latex-image/fig-Venn-diagram-union-2.svg create mode 100755 course/notes-week-01.html create mode 100755 slides-annotated/_static/pretext/js/answer.js create mode 100755 slides-annotated/_static/pretext/js/diagcess/diagcess.js create mode 100755 slides-annotated/_static/pretext/js/edit.js create mode 100755 slides-annotated/_static/pretext/js/highlight.js create mode 100755 slides-annotated/_static/pretext/js/instructor.js create mode 100755 slides-annotated/_static/pretext/js/lib/jquery.espy.min.js create mode 100755 slides-annotated/_static/pretext/js/lib/jquery.min.js create mode 100755 slides-annotated/_static/pretext/js/lib/jquery.sticky.js create mode 100755 slides-annotated/_static/pretext/js/lib/knowl.js create mode 100755 slides-annotated/_static/pretext/js/lib/mathjaxknowl.js create mode 100755 slides-annotated/_static/pretext/js/lib/mathjaxknowl3.js create mode 100755 slides-annotated/_static/pretext/js/login.js create mode 100755 slides-annotated/_static/pretext/js/lti_iframe_resizer.js create mode 100755 slides-annotated/_static/pretext/js/pretext-stack/stackapicalls.js create mode 100755 slides-annotated/_static/pretext/js/pretext-stack/stackjsvle.js create mode 100755 slides-annotated/_static/pretext/js/pretext-webwork/2.16/pretext-webwork.js create mode 100755 slides-annotated/_static/pretext/js/pretext-webwork/2.17/pretext-webwork.js create mode 100755 slides-annotated/_static/pretext/js/pretext-webwork/2.18/pretext-webwork.js create mode 100755 slides-annotated/_static/pretext/js/pretext-webwork/2.19/pretext-webwork.js create mode 100755 slides-annotated/_static/pretext/js/pretext-webwork/2.20/pretext-webwork.js create mode 100755 slides-annotated/_static/pretext/js/pretext.js create mode 100755 slides-annotated/_static/pretext/js/pretext_add_on.js create mode 100755 slides-annotated/_static/pretext/js/pretext_search.js create mode 100755 slides-annotated/_static/pretext/js/ptx_search.js create mode 100755 slides-annotated/_static/pretext/js/user_preferences.js create mode 100755 slides-annotated/index.html create mode 100755 slides/_static/pretext/js/answer.js create mode 100755 slides/_static/pretext/js/diagcess/diagcess.js create mode 100755 slides/_static/pretext/js/edit.js create mode 100755 slides/_static/pretext/js/highlight.js create mode 100755 slides/_static/pretext/js/instructor.js create mode 100755 slides/_static/pretext/js/lib/jquery.espy.min.js create mode 100755 slides/_static/pretext/js/lib/jquery.min.js create mode 100755 slides/_static/pretext/js/lib/jquery.sticky.js create mode 100755 slides/_static/pretext/js/lib/knowl.js create mode 100755 slides/_static/pretext/js/lib/mathjaxknowl.js create mode 100755 slides/_static/pretext/js/lib/mathjaxknowl3.js create mode 100755 slides/_static/pretext/js/login.js create mode 100755 slides/_static/pretext/js/lti_iframe_resizer.js create mode 100755 slides/_static/pretext/js/pretext-stack/stackapicalls.js create mode 100755 slides/_static/pretext/js/pretext-stack/stackjsvle.js create mode 100755 slides/_static/pretext/js/pretext-webwork/2.16/pretext-webwork.js create mode 100755 slides/_static/pretext/js/pretext-webwork/2.17/pretext-webwork.js create mode 100755 slides/_static/pretext/js/pretext-webwork/2.18/pretext-webwork.js create mode 100755 slides/_static/pretext/js/pretext-webwork/2.19/pretext-webwork.js create mode 100755 slides/_static/pretext/js/pretext-webwork/2.20/pretext-webwork.js create mode 100755 slides/_static/pretext/js/pretext.js create mode 100755 slides/_static/pretext/js/pretext_add_on.js create mode 100755 slides/_static/pretext/js/pretext_search.js create mode 100755 slides/_static/pretext/js/ptx_search.js create mode 100755 slides/_static/pretext/js/user_preferences.js create mode 100755 slides/index.html diff --git a/course/.mapping.json b/course/.mapping.json index 9780df0..5e210fc 100755 --- a/course/.mapping.json +++ b/course/.mapping.json @@ -1 +1 @@ -{"source/frontmatter.ptx": ["frontmatter"], "source/main.ptx": ["course-documents", "recitations"], "source/syllabus/syllabus.ptx": ["syllabus"], "source/recitations/calculus-review.ptx": ["recitation-calculus-review"]} \ No newline at end of file +{"source/frontmatter.ptx": ["frontmatter"], "source/main.ptx": ["course-documents", "ch-notes", "recitations"], "source/syllabus/syllabus.ptx": ["syllabus"], "source/notes/week01.ptx": ["notes-week-01"], "source/recitations/calculus-review.ptx": ["recitation-calculus-review"]} \ No newline at end of file diff --git a/course/_static/_runestone-services.xml b/course/_static/_runestone-services.xml index b219abc..ef4065c 100755 --- a/course/_static/_runestone-services.xml +++ b/course/_static/_runestone-services.xml @@ -10,5 +10,5 @@ prefix-runestone.677a75a9120449ff.css https://runestone.academy/cdn/runestone/ - 7.11.8 + 7.11.9 diff --git a/course/_static/webpack_static_imports.xml b/course/_static/webpack_static_imports.xml index 3fc346a..185474d 100755 --- a/course/_static/webpack_static_imports.xml +++ b/course/_static/webpack_static_imports.xml @@ -10,5 +10,5 @@ prefix-runestone.677a75a9120449ff.css https://runestone.academy/cdn/runestone/ - 7.11.8 + 7.11.9 diff --git a/course/ch-notes.html b/course/ch-notes.html new file mode 100755 index 0000000..f4cc5a9 --- /dev/null +++ b/course/ch-notes.html @@ -0,0 +1,194 @@ + + + + + + + + + + + + + + + + +Class Notes + + + + + + + + + + + + + + + + + + + + +Skip to main content
+
+ +

+Chapter Class Notes +

+
+
+
+ + + diff --git a/course/course-documents.html b/course/course-documents.html index 522b320..b1c3072 100755 --- a/course/course-documents.html +++ b/course/course-documents.html @@ -101,7 +101,7 @@ eBookConfig.allow_pairs = false; eBookConfig.enableScratchAC = false; eBookConfig.build_info = ""; eBookConfig.python3 = null; -eBookConfig.runestone_version = '7.11.8'; +eBookConfig.runestone_version = '7.11.9'; eBookConfig.jobehost = ''; eBookConfig.proxyuri_runs = ''; eBookConfig.proxyuri_files = ''; @@ -162,6 +162,19 @@ eBookConfig.enable_chatcodes = false;
  • + + +
  • +
  • diff --git a/course/frontmatter.html b/course/frontmatter.html index 0130c4d..c80164f 100755 --- a/course/frontmatter.html +++ b/course/frontmatter.html @@ -101,7 +101,7 @@ eBookConfig.allow_pairs = false; eBookConfig.enableScratchAC = false; eBookConfig.build_info = ""; eBookConfig.python3 = null; -eBookConfig.runestone_version = '7.11.8'; +eBookConfig.runestone_version = '7.11.9'; eBookConfig.jobehost = ''; eBookConfig.proxyuri_runs = ''; eBookConfig.proxyuri_files = ''; @@ -162,6 +162,19 @@ eBookConfig.enable_chatcodes = false;
  • + + +
  • +
  • diff --git a/course/generated/latex-image/fig-Venn-diagram-2.svg b/course/generated/latex-image/fig-Venn-diagram-2.svg new file mode 100755 index 0000000..fd8ab23 --- /dev/null +++ b/course/generated/latex-image/fig-Venn-diagram-2.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/course/generated/latex-image/fig-Venn-diagram-complement-2.svg b/course/generated/latex-image/fig-Venn-diagram-complement-2.svg new file mode 100755 index 0000000..5523ca4 --- /dev/null +++ b/course/generated/latex-image/fig-Venn-diagram-complement-2.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/course/generated/latex-image/fig-Venn-diagram-difference-2.svg b/course/generated/latex-image/fig-Venn-diagram-difference-2.svg new file mode 100755 index 0000000..b550d30 --- /dev/null +++ b/course/generated/latex-image/fig-Venn-diagram-difference-2.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/course/generated/latex-image/fig-Venn-diagram-intersection-2.svg b/course/generated/latex-image/fig-Venn-diagram-intersection-2.svg new file mode 100755 index 0000000..9d45f3b --- /dev/null +++ b/course/generated/latex-image/fig-Venn-diagram-intersection-2.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/course/generated/latex-image/fig-Venn-diagram-union-2.svg b/course/generated/latex-image/fig-Venn-diagram-union-2.svg new file mode 100755 index 0000000..7742bee --- /dev/null +++ b/course/generated/latex-image/fig-Venn-diagram-union-2.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/course/index.html b/course/index.html index 1aabed2..cdd59ce 100755 --- a/course/index.html +++ b/course/index.html @@ -2,7 +2,7 @@ - + diff --git a/course/lunr-pretext-search-index.js b/course/lunr-pretext-search-index.js index 048060f..c3ab62f 100755 --- a/course/lunr-pretext-search-index.js +++ b/course/lunr-pretext-search-index.js @@ -9,6 +9,195 @@ var ptx_lunr_docs = [ "title": "Syllabus", "body": " Syllabus Course Information Day and Time Lectures: TR 11:40 am - 1:20 pm Gladfelter L024 Recitation Section 1: M 12:00 pm - 12:50 pm Wachman 9 Section 2: W 12:00 pm - 12:50 pm Wachman 15 Section 3: F 11:00 am - 11:50 am Wachman 408 Section 4: F 1:00 pm - 1:50 pm Wachman 408 Office Hours tbd Course Goals To introduce some of the basic concepts and techniques of probability and statistics, as applied in the Life and Environmental Sciences. Topics Covered Probability theory and descriptive statistics, probability models, introduction to inferential statistics. Prerequisite MATH 1041 (C), MATH 1038 (C), MATH 1941 (C), MATH 1951 (C), MATH 2043 to 3080 (C-), MA06 Y. Course Mode In-person Credits 4 Textbook and Other Required Materials Textbook Course notes are freely available online . Calculator You will need a scientific calculator (NOT a graphing calculator and NOT a calculator app on your phone\/watch). Any calculator capable of symbolic differentiation\/integration or performing statistical tests is NOT allowed. Scientific calculators (such as the TI-30XIIS ) typically cost around $10. Phone You will need a device (phone or tablet) to take pictures of your quizzes\/tests and upload your work. Assessments Homework There will be homework assignments posted on Canvas approximately weekly. There are more practice problems available in the course notes. Doing homework problems is the primary way that you will learn and internalize the material seen in class. Plan to spend a substantial amount of time throughout the semester doing homework problems. Quizzes There will be in-class quizzes every other week (on Jan 20, Feb 3, Mar 10, Mar 24, and Apr 21). Quizzes give you some practice in a test-like environment without the same grade pressure as a test. The quizzes should be a signal to you about whether you're keeping up with the pace of the course. Exams This course will have two midterm exams and a Final Exam. Exams are the main opportunity for you to demonstrate your understanding of course material. Test 1 Tuesday, February 17 (in class) Test 2 Tuesday, April 7 (in class) Final Exam Tuesday, May 5, 10:30 am - 12:30 pm Makeup Policy There are no quiz or exam makeups. The lowest quiz will be dropped and replaced with the recitation score. Grading The grade components and their weights are: Component Weight Homework 5% Quizzes 15% Exam 1 24% Exam 2 24% Final Exam 32% The lower cutoff for each letter grade is given in the table below. Letter Grade Lower Cutoff A 93% A- 90% B+ 87% B 83% B- 80% C+ 77% C 73% C- 70% D+ 65% D 55% F 0% Attendance Policy Attendance is expected and will be recorded. If you need to miss a class for some reason, it is your responsibility to get notes from another student for any material you've missed, and to arrange to turn in\/make up any in-class or collected work. Attendance and Your Health To achieve course learning goals, students must attend and participate in classes, according to the course requirements. However, if you have tested positive for or are experiencing symptoms of a contagious illness, you should not come to campus or attend in-person classes or activities. It is the student’s responsibility to contact me to create a plan for participation and engagement in the course as soon as you are able to do so, and to make a plan to complete all assignments in a timely fashion. Expectations for Class Conduct It is important to foster a respectful and productive learning environment that includes all students in our diverse community of learners. Our differences, some of which are outlined in the University's nondiscrimination statement, will add richness to this learning experience. Therefore, all opinions and experiences, no matter how different or controversial they may be perceived, must be respected in the tolerant spirit of academic discourse. Disability Statement Any student who has a need for accommodations based on the impact of a documented disability or medical condition should contact Disability Resources and Services (DRS) in Howard Gittis Student Center South, Rm 420 ( drs@temple.edu ; 215-204-1280) to request accommodations and learn more about the resources available to you. If you have a DRS accommodation letter to share with me, or you would like to discuss your accommodations, please contact me as soon as practical. I will work with you and with DRS to coordinate reasonable accommodations for all students with documented disabilities. All discussions related to your accommodations will be confidential. Academic Freedom Freedom to teach and freedom to learn are inseparable facets of academic freedom. The University has adopted a policy on Student and Faculty Academic Rights and Responsibilities (Policy # 03.70.02) which can be accessed here . Add\/Drop Policy Students will be charged for a course unless dropped by the Drop\/Add deadline date. Check the University calendar for exact dates. During the Drop\/Add period, students may drop a course with no record of the class appearing on their transcript. Students are not financially responsible for any courses dropped during this period. In the following weeks prior to or on the withdrawal date students may withdraw from a course with the grade of \"W\" appearing on their transcript. After the withdrawal date students may not withdraw from courses. Check the University calendar for exact dates. See the full policy by clicking here . AI Policy The use of generative AI tools (such as ChatGPT, DALL-E, etc.) is not permitted in this class unless specifically announced for a particular assignment; therefore, any use of AI tools for work in this class may be considered a violation of Temple University's Academic Honesty policy and Student Conduct Code, since the work is not your own. The use of unauthorized AI tools will result in a grade of zero on the assignment; a second offense will be reported to the Student Conduct Board. Incomplete Grades The grade \"I\" (an \"incomplete\") is only given if students cannot complete the course work due to circumstances beyond their control. It is necessary for the student to have completed the majority of the course work with a passing average and to sign an incomplete contract which clearly states what is left for the student to do and the deadline by which the work must be completed. The incomplete contract must also include a default grade that will be used in case the \"I\" grade is not resolved by the agreed deadline. See the full policy by clicking here . Student Support Services The following academic support services are available to students: The Math Consulting Center Student Success Center University Libraries Undergraduate Research Support Career Center Tuttleman Counseling Services Disability Resources and Services If you are experiencing food insecurity or financial struggles, Temple provides resources and support. Notably, the Temple University Cherry Pantry and the Temple University Emergency Student Aid Program are in operation as well as a variety of resources from the Division of Student Affairs. " }, +{ + "id": "notes-week-01", + "level": "1", + "url": "notes-week-01.html", + "type": "Section", + "number": "", + "title": "Week 1", + "body": " Week 1 This is an outline of the topics we covered in the first week of class. These notes are not a substitute for your own note-taking. I highly recommend that you take your own notes during class. If you ever miss a class for any reason, reach out to another student in class to get a copy of their notes. Tuesday 1\/13 Sec 1.1: Sets In prior math courses, you mostly asked deterministic questions. Now, we need new tools to model randomness . A toxin molecule in a cell has a certain chance each minute to leave the cell. A patient takes a diagnostic test for a disease and wants to know the chance that they have the disease based on the test result. The sample space , often denoted , is the set of all possible results of an experiment. A single result is called an outcome , while a collection of results is called an event . An experiment consists of rolling a standard 6-sided die (D6). The sample space is . One possible event is , i.e., the event that the result of the roll is even. Note: we'll use notation like D6 to indicate a 6-sided die with faces 1, 2, 3, 4, 5, 6. Similarly, for example, D4 will indicate a 4-sided die with faces 1, 2, 3, 4. The symbol means \"is an element of\", as in . The symbol means \"is a subset of\", as in . This means that every element of the set is also an element of the set . Consider sets and , each contained inside . We can combine sets in a variety of ways: Union The union of and is the set . Intersection The intersection of and is the set . Difference The set difference is the set . Complement The complement of is the set . Empty Set The empty set , usually written or , is the set which contains no elements. It's useful sometimes to draw pictures called Venn diagrams representing set interactions. Union Venn diagram showing sets with the region representing shaded. Intersection Venn diagram showing sets with the region representing shaded. Difference Venn diagram showing sets with the region representing shaded. Complement Venn diagram showing set with the region representing shaded. Sec 1.2: Probability Next, we want to start assigning probabilities to each individual outcome so we can then find the probabilities of events. An experiment consists of rolling a D6. The sample space is . We might assign probabilities as follows: Distribution for a fair die 1 2 3 4 5 6 Note that we don't have to assign the same probability to each outcome. If we do, we call this distribution uniform . If we have some event, such as , then we calculate the probability of the event by adding together the probabilities of the outcomes that make up the event: Note that this is the same result that we would get if we counted the total number of outcomes in and divided by the total number of outcomes in . Given a finite set , the cardinality of , written , is the number of elements in . If is a finite probability space with the uniform distribution and is an event, then: Suppose we have a weighted die that's much more likely to come up 6 than any other outcome. Distribution for a fair die 1 0.1 2 0.1 3 0.1 4 0.2 5 0.1 6 0.4 With this distribution, if , then: Pick a number from 1 to 10. What is the probability of picking 3? Some considerations: Are we using the uniform distribution? What even is the sample space here? Do we need to pick only integers, or is an outcome here? What would \"uniform\" mean if the sample space was infinite? A probability distribution on a sample space assigns probabilities to every event, satisfying the following conditions: . for any event . If , then . Suppose we flip a coin until we see heads. The sample space is . What would \"uniform\" mean here? Is there any way we could assign the same probability to each individual outcome here? Instead of \"uniform\", what if we want to treat the coin as a fair coin? Then what would the distribution be? Distribution assuming a fair coin # flips 1 2 3 4 One last (very useful!) observation. Recall: If , then . For any event , and . So: We can rewrite this in two useful ways: " +}, +{ + "id": "subsubsec-Sets-2", + "level": "2", + "url": "notes-week-01.html#subsubsec-Sets-2", + "type": "Paragraph (with a defined term)", + "number": "", + "title": "", + "body": "deterministic randomness " +}, +{ + "id": "subsubsec-Sets-3", + "level": "2", + "url": "notes-week-01.html#subsubsec-Sets-3", + "type": "Example", + "number": "1", + "title": "", + "body": " A toxin molecule in a cell has a certain chance each minute to leave the cell. " +}, +{ + "id": "subsubsec-Sets-4", + "level": "2", + "url": "notes-week-01.html#subsubsec-Sets-4", + "type": "Example", + "number": "2", + "title": "", + "body": " A patient takes a diagnostic test for a disease and wants to know the chance that they have the disease based on the test result. " +}, +{ + "id": "def-sample-space", + "level": "2", + "url": "notes-week-01.html#def-sample-space", + "type": "Definition", + "number": "3", + "title": "", + "body": " The sample space , often denoted , is the set of all possible results of an experiment. A single result is called an outcome , while a collection of results is called an event . " +}, +{ + "id": "example-sample-space", + "level": "2", + "url": "notes-week-01.html#example-sample-space", + "type": "Example", + "number": "4", + "title": "", + "body": " An experiment consists of rolling a standard 6-sided die (D6). The sample space is . One possible event is , i.e., the event that the result of the roll is even. " +}, +{ + "id": "def-subset", + "level": "2", + "url": "notes-week-01.html#def-subset", + "type": "Definition", + "number": "5", + "title": "", + "body": " The symbol means \"is an element of\", as in . The symbol means \"is a subset of\", as in . This means that every element of the set is also an element of the set . " +}, +{ + "id": "def-set-operations", + "level": "2", + "url": "notes-week-01.html#def-set-operations", + "type": "Definition", + "number": "6", + "title": "", + "body": " Consider sets and , each contained inside . We can combine sets in a variety of ways: Union The union of and is the set . Intersection The intersection of and is the set . Difference The set difference is the set . Complement The complement of is the set . Empty Set The empty set , usually written or , is the set which contains no elements. " +}, +{ + "id": "subsubsec-Sets-10", + "level": "2", + "url": "notes-week-01.html#subsubsec-Sets-10", + "type": "Paragraph (with a defined term)", + "number": "", + "title": "", + "body": "Venn diagrams " +}, +{ + "id": "fig-Venn-diagram-union", + "level": "2", + "url": "notes-week-01.html#fig-Venn-diagram-union", + "type": "Figure", + "number": "7", + "title": "", + "body": " Union Venn diagram showing sets with the region representing shaded. " +}, +{ + "id": "fig-Venn-diagram-intersection", + "level": "2", + "url": "notes-week-01.html#fig-Venn-diagram-intersection", + "type": "Figure", + "number": "8", + "title": "", + "body": " Intersection Venn diagram showing sets with the region representing shaded. " +}, +{ + "id": "fig-Venn-diagram-difference", + "level": "2", + "url": "notes-week-01.html#fig-Venn-diagram-difference", + "type": "Figure", + "number": "9", + "title": "", + "body": " Difference Venn diagram showing sets with the region representing shaded. " +}, +{ + "id": "fig-Venn-diagram-complement", + "level": "2", + "url": "notes-week-01.html#fig-Venn-diagram-complement", + "type": "Figure", + "number": "10", + "title": "", + "body": " Complement Venn diagram showing set with the region representing shaded. " +}, +{ + "id": "subsubsec-Probability-3", + "level": "2", + "url": "notes-week-01.html#subsubsec-Probability-3", + "type": "Example", + "number": "11", + "title": "", + "body": " An experiment consists of rolling a D6. The sample space is . We might assign probabilities as follows: Distribution for a fair die 1 2 3 4 5 6 Note that we don't have to assign the same probability to each outcome. If we do, we call this distribution uniform . If we have some event, such as , then we calculate the probability of the event by adding together the probabilities of the outcomes that make up the event: Note that this is the same result that we would get if we counted the total number of outcomes in and divided by the total number of outcomes in . " +}, +{ + "id": "def-cardinality", + "level": "2", + "url": "notes-week-01.html#def-cardinality", + "type": "Definition", + "number": "13", + "title": "", + "body": " Given a finite set , the cardinality of , written , is the number of elements in . " +}, +{ + "id": "subsubsec-Probability-5", + "level": "2", + "url": "notes-week-01.html#subsubsec-Probability-5", + "type": "Fact", + "number": "14", + "title": "", + "body": " If is a finite probability space with the uniform distribution and is an event, then: " +}, +{ + "id": "subsubsec-Probability-6", + "level": "2", + "url": "notes-week-01.html#subsubsec-Probability-6", + "type": "Example", + "number": "15", + "title": "", + "body": " Suppose we have a weighted die that's much more likely to come up 6 than any other outcome. Distribution for a fair die 1 0.1 2 0.1 3 0.1 4 0.2 5 0.1 6 0.4 With this distribution, if , then: " +}, +{ + "id": "subsubsec-Probability-7", + "level": "2", + "url": "notes-week-01.html#subsubsec-Probability-7", + "type": "Example", + "number": "17", + "title": "", + "body": " Pick a number from 1 to 10. What is the probability of picking 3? Some considerations: Are we using the uniform distribution? What even is the sample space here? Do we need to pick only integers, or is an outcome here? What would \"uniform\" mean if the sample space was infinite? " +}, +{ + "id": "def-probability-distribution", + "level": "2", + "url": "notes-week-01.html#def-probability-distribution", + "type": "Definition", + "number": "18", + "title": "", + "body": " A probability distribution on a sample space assigns probabilities to every event, satisfying the following conditions: . for any event . If , then . " +}, +{ + "id": "subsubsec-Probability-9", + "level": "2", + "url": "notes-week-01.html#subsubsec-Probability-9", + "type": "Example", + "number": "19", + "title": "", + "body": " Suppose we flip a coin until we see heads. The sample space is . What would \"uniform\" mean here? Is there any way we could assign the same probability to each individual outcome here? Instead of \"uniform\", what if we want to treat the coin as a fair coin? Then what would the distribution be? Distribution assuming a fair coin # flips 1 2 3 4 " +}, +{ + "id": "subsubsec-Probability-10", + "level": "2", + "url": "notes-week-01.html#subsubsec-Probability-10", + "type": "Example", + "number": "21", + "title": "", + "body": " One last (very useful!) observation. Recall: If , then . For any event , and . So: We can rewrite this in two useful ways: " +}, { "id": "recitation-calculus-review", "level": "1", @@ -59,7 +248,7 @@ var ptx_lunr_docs = [ "level": "2", "url": "recitation-calculus-review.html#thm-FTC", "type": "Theorem", - "number": "1", + "number": "22", "title": "Fundamental Theorem of Calculus.", "body": " Fundamental Theorem of Calculus If is an antiderivative of i.e., if then: " }, diff --git a/course/notes-week-01.html b/course/notes-week-01.html new file mode 100755 index 0000000..55484d2 --- /dev/null +++ b/course/notes-week-01.html @@ -0,0 +1,501 @@ + + + + + + + + + + + + + + + + +Week 1 + + + + + + + + + + + + + + + + + + + + +Skip to main content
    +
    + +

    +Section Week 1 +

    +
    This is an outline of the topics we covered in the first week of class. These notes are not a substitute for your own note-taking. I highly recommend that you take your own notes during class. If you ever miss a class for any reason, reach out to another student in class to get a copy of their notes. +

    +Subsection Tuesday 1/13 +

    +

    +Subsubsection Sec 1.1: Sets +

    +
    In prior math courses, you mostly asked deterministic questions. Now, we need new tools to model randomness. +
    +
    +Example 1. +
    +
    A toxin molecule in a cell has a certain chance each minute to leave the cell. +
    +
    +Example 2. +
    +
    A patient takes a diagnostic test for a disease and wants to know the chance that they have the disease based on the test result. +
    +
    +Definition 3. +
    +
    The sample space, often denoted \(\Omega\text{,}\) is the set of all possible results of an experiment. A single result is called an outcome, while a collection of results is called an event. +
    +
    +Example 4. +
    +
    An experiment consists of rolling a standard 6-sided die (D6). The sample space is \(\Omega = \{1, 2, 3, 4, 5, 6\}\text{.}\) One possible event is \(A = \{2, 4, 6\}\text{,}\) i.e., the event that the result of the roll is even. +
    +
    Note: we’ll use notation like D6 to indicate a 6-sided die with faces 1, 2, 3, 4, 5, 6. Similarly, for example, D4 will indicate a 4-sided die with faces 1, 2, 3, 4. +
    +
    +Definition 5. +
    +
    The symbol \(\in\) means "is an element of", as in \(4 \in A\text{.}\) +
    The symbol \(\subset\) means "is a subset of", as in \(A \subset \Omega\text{.}\) This means that every element of the set \(A\) is also an element of the set \(\Omega\text{.}\) +
    +
    +Definition 6. +
    +
    +
    Consider sets \(A\) and \(B\text{,}\) each contained inside \(\Omega\text{.}\) We can combine sets in a variety of ways:
    +
    +
    Union +
    +
    The union of \(A\) and \(B\) is the set \(A \cup B = \{x \mid x \in A \text{ or } x \in B\}\text{.}\) +
    +
    Intersection +
    +
    The intersection of \(A\) and \(B\) is the set \(A \cap B = \{x \mid x \in A \text{ and } x \in B\}\text{.}\) +
    +
    Difference +
    +
    The set difference \(A-B\) is the set \(A - B = \{x \mid x \in A \text{ and } x \notin B\}\text{.}\) +
    +
    Complement +
    +
    The complement of \(A\) is the set \(A^c = \{x \in \Omega \mid x \notin A\}\text{.}\) +
    +
    Empty Set +
    +
    The empty set, usually written \(\emptyset\) or \(\{\}\text{,}\) is the set which contains no elements. +
    +
    + +
    +
    It’s useful sometimes to draw pictures called Venn diagrams representing set interactions. +
    +
    +
    +
    described in detail following the image
    Venn diagram showing sets \(A, B\) with the region representing \(A \cup B\) shaded. +
    Figure 7. Union
    +
    described in detail following the image
    Venn diagram showing sets \(A, B\) with the region representing \(A \cap B\) shaded. +
    Figure 8. Intersection
    +
    +
    +
    described in detail following the image
    Venn diagram showing sets \(A, B\) with the region representing \(A - B\) shaded. +
    Figure 9. Difference
    +
    described in detail following the image
    Venn diagram showing set \(A \subset \Omega\) with the region representing \(A^c\) shaded. +
    Figure 10. Complement
    +
    +
    +

    +Subsubsection Sec 1.2: Probability +

    +
    Next, we want to start assigning probabilities to each individual outcome so we can then find the probabilities of events. +
    +
    +Example 11. +
    +
    An experiment consists of rolling a D6. The sample space is \(\Omega = \{1, 2, 3, 4, 5, 6\}\text{.}\) We might assign probabilities as follows: +
    Table 12. Distribution for a fair die
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    \(x\)\(\Pr(x)\)
    1\(1/6\)
    2\(1/6\)
    3\(1/6\)
    4\(1/6\)
    5\(1/6\)
    6\(1/6\)
    +
    Note that we don’t have to assign the same probability to each outcome. If we do, we call this distribution uniform. If we have some event, such as \(A = \{2, 4, 6\}\text{,}\) then we calculate the probability of the event by adding together the probabilities of the outcomes that make up the event:
    +
    +\begin{gather*} +\Pr(A) = \Pr(2) + \Pr(4) + \Pr(6) = \frac{1}{6} + \frac{1}{6} + \frac{1}{6} = \frac{3}{6} = \frac{1}{2} +\end{gather*} +
    +
    Note that this is the same result that we would get if we counted the total number of outcomes in \(A\) and divided by the total number of outcomes in \(\Omega\text{.}\) +
    + +
    +
    +Definition 13. +
    +
    Given a finite set \(A\text{,}\) the cardinality of \(A\text{,}\) written \(|A|\text{,}\) is the number of elements in \(A\text{.}\) +
    +
    +Example 15. +
    +
    Suppose we have a weighted die that’s much more likely to come up 6 than any other outcome. +
    Table 16. Distribution for a fair die
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    \(x\)\(\Pr(x)\)
    10.1
    20.1
    30.1
    40.2
    50.1
    60.4
    +
    With this distribution, if \(A = \{2, 4, 6\}\text{,}\) then:
    +
    +\begin{gather*} +\Pr(A) = 0.1 + 0.2 + 0.4 = 0.7 \neq \frac{3}{6}. +\end{gather*} +
    + +
    +
    +Example 17. +
    +
    Pick a number from 1 to 10. What is the probability of picking 3? +
    +
    Some considerations:
    +
      +
    1. +
      Are we using the uniform distribution? +
      + +
    2. +
    3. +
      What even is the sample space here? Do we need to pick only integers, or is \(\pi\) an outcome here? +
      + +
    4. +
    5. +
      What would "uniform" mean if the sample space was infinite? +
      + +
    6. +
    + +
    +
    +Definition 18. +
    +
    +
    A probability distribution on a sample space \(\Omega\) assigns probabilities to every event, satisfying the following conditions:
    +
      +
    1. +
      +\(\Pr(\Omega) = 1\text{.}\) +
      + +
    2. +
    3. +
      +\(0 \leq \Pr(A) \leq 1\) for any event \(A\text{.}\) +
      + +
    4. +
    5. +
      If \(A \cap B = \emptyset\text{,}\) then \(\Pr(A\cup B) = \Pr(A) + \Pr(B)\text{.}\) +
      + +
    6. +
    + +
    +
    +Example 19. +
    +
    Suppose we flip a coin until we see heads. The sample space is \(\Omega = \{H, TH, TTH, TTTH, \dotsc\}\text{.}\) What would "uniform" mean here? Is there any way we could assign the same probability to each individual outcome here? +
    Instead of "uniform", what if we want to treat the coin as a fair coin? Then what would the distribution be? +
    Table 20. Distribution assuming a fair coin
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    \(x\)# flips\(\Pr(x)\)
    \(H\)1\(1/2\)
    \(TH\)2\(1/4\)
    \(TTH\)3\(1/8\)
    \(TTTH\)4\(1/16\)
    \(\vdots\)\(\vdots\)
    \(T\dotsm TH\)\(n\)\(1/2^n\)
    \(\vdots\)\(\vdots\)
    +Example 21. +
    +
    One last (very useful!) observation. Recall: If \(A\cap B = \emptyset\text{,}\) then \(\Pr(A\cup B) = \Pr(A) + \Pr(B)\text{.}\) +
    +
    For any event \(A\text{,}\) \(A\cap A^c = \emptyset\) and \(A \cup A^c = \Omega\text{.}\) So:
    +
    +\begin{gather*} +1 = \Pr(\underbrace{A \cup A^c}_{\Omega}) = \Pr(A) + \Pr(A^c). +\end{gather*} +
    +
    We can rewrite this in two useful ways:
    +
    +\begin{align*} +\Pr(A) \amp = 1 - \Pr(A^c) \\ +\Pr(A^c) \amp = 1 - \Pr(A) +\end{align*} +
    + +
    +
    +
    +
    + + + diff --git a/course/recitation-calculus-review-printable.html b/course/recitation-calculus-review-printable.html index db6d045..72ab84d 100755 --- a/course/recitation-calculus-review-printable.html +++ b/course/recitation-calculus-review-printable.html @@ -101,7 +101,7 @@ eBookConfig.allow_pairs = false; eBookConfig.enableScratchAC = false; eBookConfig.build_info = ""; eBookConfig.python3 = null; -eBookConfig.runestone_version = '7.11.8'; +eBookConfig.runestone_version = '7.11.9'; eBookConfig.jobehost = ''; eBookConfig.proxyuri_runs = ''; eBookConfig.proxyuri_files = ''; @@ -161,6 +161,19 @@ eBookConfig.enable_chatcodes = false; +
  • + + +
  • @@ -232,7 +245,7 @@ eBookConfig.enable_chatcodes = false;
    If \(f(x)\) is a probability density function (don’t worry about what that means for now), then integrals such as \(\int_a^b f(x)\ dx\) are used to calculate certain probabilities, as are values of the form \(F(x)\text{.}\) +
    If \(f(x)\) is a probability density function (don’t worry about what that means for now), then integrals such as \(\int_a^b f(x)\ dx\) are used to calculate certain probabilities, as are values of the form \(F(x)\text{.}\)

    5.

    Practice with the following integrals: diff --git a/course/recitation-calculus-review.html b/course/recitation-calculus-review.html index 21dec02..eb8dbf1 100755 --- a/course/recitation-calculus-review.html +++ b/course/recitation-calculus-review.html @@ -101,7 +101,7 @@ eBookConfig.allow_pairs = false; eBookConfig.enableScratchAC = false; eBookConfig.build_info = ""; eBookConfig.python3 = null; -eBookConfig.runestone_version = '7.11.8'; +eBookConfig.runestone_version = '7.11.9'; eBookConfig.jobehost = ''; eBookConfig.proxyuri_runs = ''; eBookConfig.proxyuri_files = ''; @@ -161,6 +161,19 @@ eBookConfig.enable_chatcodes = false;
  • +
  • + + +
  • @@ -217,7 +230,7 @@ eBookConfig.enable_chatcodes = false;
    If \(f(x)\) is a probability density function (don’t worry about what that means for now), then integrals such as \(\int_a^b f(x)\ dx\) are used to calculate certain probabilities, as are values of the form \(F(x)\text{.}\) +
    If \(f(x)\) is a probability density function (don’t worry about what that means for now), then integrals such as \(\int_a^b f(x)\ dx\) are used to calculate certain probabilities, as are values of the form \(F(x)\text{.}\)

    5.

    Practice with the following integrals: diff --git a/course/recitations.html b/course/recitations.html index f1e559a..1243959 100755 --- a/course/recitations.html +++ b/course/recitations.html @@ -101,7 +101,7 @@ eBookConfig.allow_pairs = false; eBookConfig.enableScratchAC = false; eBookConfig.build_info = ""; eBookConfig.python3 = null; -eBookConfig.runestone_version = '7.11.8'; +eBookConfig.runestone_version = '7.11.9'; eBookConfig.jobehost = ''; eBookConfig.proxyuri_runs = ''; eBookConfig.proxyuri_files = ''; @@ -135,7 +135,7 @@ eBookConfig.enable_chatcodes = false;
    -PrevUpNext +PrevUpNext
  • +
  • + + +
  • @@ -171,7 +184,7 @@ eBookConfig.enable_chatcodes = false;
  • + + +
  • +
  • @@ -169,6 +182,7 @@ eBookConfig.enable_chatcodes = false;
    -PrevUpNext +PrevUpNext
    ' + + var this_rq_answer_and_controls = document.createElement('div'); + this_rq_answer_and_controls.setAttribute('style', 'width:80%; padding-left:10%; padding-right:10%; margin-top:0.5em;'); + this_rq_answer_and_controls.setAttribute('class', 'rq_answer'); + this_rq_answer_and_controls.innerHTML = hidden_answer_div + answer_div + this_rq_controls; + console.log("appending to ", reading_question_id); + $('#'+reading_question_id).append(this_rq_answer_and_controls); + // this.parentNode.insertAdjacentElement("afterend", this_rq_answer_and_controls); + + /* typeset the math in the reading questions answers */ + // MathJax.Hub.Queue(["Typeset",MathJax.Hub]); + if (mjvers && mjvers < 3) { + MathJax.Hub.Queue(['Typeset', MathJax.Hub]); + } else if (mjvers > 3) { + MathJax.typesetPromise(); + } + + } else if(role == "instructor" || role == "student") { + + var this_answer_link = document.createElement('div'); + this_answer_link.innerHTML = rq_answer_label; + console.log("inserting afterend of",reading_question); + // reading_question.insertAdjacentElement("afterend", this_answer_link); + reading_question.append(this_answer_link); + } else { + + console.log("should not be here"); + + } + + } + + function allow_student_answers(){ + /* make a new blank area to answer a question */ + $('.readingquestion_make_answer.student').mousedown(function(e){ + console.log(".readingquestion_make_answer student"); +// $(this).addClass("hidecontrols"); + // var this_ques_id = this.parentNode.parentNode.id; + var this_ques_id = this.parentNode.parentNode.id; + var this_ques_id_text = this_ques_id + "_text"; + var this_ques_id_controls = this_ques_id + "_controls"; + console.log(".rq", this_ques_id); + answer_textarea = ''; + this_rq_controls += 'preview'; +// this_rq_controls += 'delete'; + this_rq_controls += 'typing math?'; + this_rq_controls += '' + + var this_rq_answer_and_controls = document.createElement('div'); + this_rq_answer_and_controls.setAttribute('class', 'rq_answer editing'); + this_rq_answer_and_controls.setAttribute('style', 'width:80%; padding-left:10%; padding-right:10%; margin-top:0.5em;'); + + this_rq_answer_and_controls.innerHTML = answer_textarea + this_rq_controls; + this.parentNode.insertAdjacentElement("afterend", this_rq_answer_and_controls); + + this.remove(); + + // MathJax.Hub.Queue(["Typeset",MathJax.Hub]); + + console.log("adding other keypress listener"); + var this_textarea = document.getElementById(this_ques_id_text + "_input"); + console.log("to", this_textarea); + this_textarea.addEventListener("keypress", function() { + // if(this_textarea.scrollTop != 0){ + // console.log("this_textarea.scrollHeight", this_textarea.scrollHeight, "this_textarea.scrollTop", this_textarea.scrollTop); + // console.log("this_textarea.clientHeight", this_textarea.clientHeight); + this_textarea.overflow = "scroll"; + // console.log("this_textarea.scrollHeight", this_textarea.scrollHeight, "this_textarea.scrollTop", this_textarea.scrollTop); + // console.log("this_textarea.clientHeight", this_textarea.clientHeight); + this_textarea.overflow = "hidden"; + // console.log("this_textarea.scrollHeight", this_textarea.scrollHeight, "this_textarea.scrollTop", this_textarea.scrollTop); + // console.log("this_textarea.getBoundingClientRect()", this_textarea.getBoundingClientRect()); + this_textarea.style.height = this_textarea.scrollHeight + "px"; + // } + }, false); + var tmp_id = this_ques_id_text + "_input"; + console.log("want focus to ", tmp_id); + console.log("which is ", $("#" +tmp_id)); + console.log("the active ", document.hasFocus(), "element is", document.activeElement); + console.log("or maybe it is ", $(":focus")); + $("#" + tmp_id).focus(); + document.getElementById(tmp_id).focus(); + console.log("the active ", document.hasFocus(), "element is", document.activeElement); + }); + } + allow_student_answers(); + + $('.readingquestion_make_answer.instructor').mousedown(function(e){ + console.log(".readingquestion_make_answer instructor", "instId", uname, "pI", pageIdentifier); + if (jQuery.isEmptyObject(reading_answers) || this.classList.contains("reload")) { + rq_data = {"action": "retrieve", "instId": uname, "pw": emanu, "pI": pageIdentifier, "type": "readingquestions"}; + // myjson = {"action": "retrieve", "type": "readingquestions", "instId": "100002000", "pI": "beezer-FCLA___FPm"} + + $.ajax({ + url: "https://aimath.org/cgi-bin/u/highlights.py", + type: "post", + data: JSON.stringify(rq_data), + dataType: "json", + async: false, + success: function(data) { + reading_answers = data; + // console.log("something", data, "back from highlight"); + // alert(data); + }, + error: function(errMsg) { + console.log("seems to be an error?",errMsg); + alert("Error\n" + errMsg); + } + }); + } +// var this_ques_id = this.parentNode.previousSibling.id; + var this_ques_id = this.parentNode.parentNode.id; + console.log("this_ques_id", this_ques_id); + var compiled_answers = ""; + var title_of_this_section = $("section > h2 > .title").html(); + title_of_this_section = title_of_this_section.replace(/ /g, "%20"); + title_of_this_section = title_of_this_section.replace(/\?/g, ""); + var number_of_this_rq = 1 + $("#" + this_ques_id).index(".exercise-like"); +// console.log("first title_of_this_section", title_of_this_section); +// console.log("first title_of_this_section.html()", title_of_this_section.html()); +// console.log("second title_of_this_section[0]", title_of_this_section[0]); + for(var j=0; j < reading_answers.length; ++j) { + var this_answer_all = reading_answers[j]; + var this_student_id = this_answer_all[0]; + var these_specific_answers = JSON.parse(this_answer_all[1]); + console.log("these_specific_answers", these_specific_answers); + console.log("this_answer_all[2]", this_answer_all[2]); + var this_submitted_time = JSON.parse(this_answer_all[2]); + // var this_submitted_time = this_answer_all[2]; + console.log("looking for this answer:", this_ques_id + "_text"); + var this_specific_answer = these_specific_answers[this_ques_id + "_text"]; + console.log("this_answer_all",this_answer_all); + console.log("j",j,"this_specific_answer", this_specific_answer); + console.log("this_specific_time", this_submitted_time); + this_specific_answer = dollars_to_slashparen(escapeHTML(this_specific_answer)) + if (!this_specific_answer) { + this_specific_answer = "no answer submitted"; + compiled_answers += '
    '; + } else { + compiled_answers += '
    '; + } + if (this_student_id.indexOf('@') > -1) { + this_student_id = '' + this_student_id + ''; + } + compiled_answers += '
    ' + this_student_id + '
    '; + compiled_answers += '
    ' + this_submitted_time + '
    '; + compiled_answers += '
    ' + this_specific_answer + '
    '; + compiled_answers += '
    \n'; + console.log(j, "j", these_specific_answers) + } + // if the answers are being reloaded, remove the previous answers + $("#" + this_ques_id + "_ans").remove(); + var answers_to_this_question = document.createElement('div'); + answers_to_this_question.setAttribute('class', 'compiled_answers'); + answers_to_this_question.setAttribute('id', this_ques_id + "_ans"); + +// this_rq_answer_and_controls.setAttribute('style', 'width:80%; margin-left:auto; margin-right:auto; margin-top:0.5em;'); +// this_rq_answer_and_controls.setAttribute('class', 'rq_answer'); + answers_to_this_question.innerHTML = compiled_answers; + $('#'+this_ques_id).append(answers_to_this_question); +// this.parentNode.remove(); + this.innerHTML = "Reload responses"; + $(this).addClass("reload"); +// MathJax.Hub.Queue(["Typeset",MathJax.Hub]); + if (mjvers && mjvers < 3) { + MathJax.Hub.Queue(['Typeset', MathJax.Hub]); + } else if (mjvers > 3) { + MathJax.typesetPromise(); + } + + // this.parentNode.insertAdjacentElement("afterend", this_rq_answe + + }); + + + function save_one_reading_question(this_ques_id) { + this_ques_id_text = this_ques_id + "_text"; + var this_ans_text = $("#" + this_ques_id_text + "_input"); + console.log("the value:", this_ans_text.value); +// var this_ques_id = this_ans.parentNode.previousSibling.previousSibling.id; + // var this_ques_id = this_ans.id; + console.log("this_ques_id", this_ques_id); +// console.log("this_rq_ans", this_rq_ans); + var this_ans_text_value = this_ans_text.val(); + console.log("this_ans_text_value", this_ans_text_value); + if ( /[^\x00-\x7F]/.test(this_ans_text_value)) { + this_ans_text_value = this_ans_text_value.replace(/[^\x00-\x7F]/g, "XX"); + alert("Illegal characters in answer have been replaced by XX"); + } + this_ans_text_value = $.trim(this_ans_text_value); // jQuery trim (some chrome on windows had trouble with trim) + // we have the contents of the answer, so save it to local storage + console.log("saving in local storage at", this_ques_id_text, "the answer", this_ans_text_value); + reading_questions_object[this_ques_id_text] = this_ans_text_value; + localStorage.setObject(reading_questions_object_id, reading_questions_object); + console.log("Object.keys(reading_questions_object)",Object.keys(reading_questions_object)); + console.log("reading_questions", reading_questions); + if (Object.keys(reading_questions_object).length >= reading_questions.length && uname != "guest" && role=="student") { + console.log("all reading questions have been answered"); + reading_questions_all_answered = true; + make_submit_button(); + } + + // and save a copy hidden on the page + console.log("looking for", this_ques_id + "_text_hidden"); + // when the initial answer box is created, there is no hidden version + if ( !document.getElementById(this_ques_id + "_text_hidden")) { + console.log("making a place to hide the answer"); + var hidden_answer_div = document.createElement('div'); + hidden_answer_div.setAttribute('id', this_ques_id + '_text_hidden'); + hidden_answer_div.setAttribute('class', 'tex2jax_ignore asciimath2jax_ignore'); + hidden_answer_div.setAttribute('style', 'display: none'); + console.log("this_ans_text", this_ans_text); + document.getElementById(this_ques_id_text + "_input").insertAdjacentElement("beforebegin", hidden_answer_div); + // hidden_answer_div.insertBefore(this_ans_text); + } + console.log("hiding the raw answer in", this_ques_id + "_text_hidden"); + document.getElementById(this_ques_id + "_text_hidden").innerHTML = escapeHTML(this_ans_text_value); + + //and show it on the page + var this_ans_static = document.createElement('div'); + this_ans_static.setAttribute('id', this_ques_id_text); + this_ans_static.setAttribute('class', 'given_answer has_am process-math processme'); + console.log("setting this_ans_static.innerHTML to", dollars_to_slashparen(escapeHTML(this_ans_text_value))); + this_ans_static.innerHTML = dollars_to_slashparen(escapeHTML(this_ans_text_value)) + " " + +// this_rq_ans.replaceWith(this_ans_static); + console.log("about to replace this_ans_text", this_ans_text); + this_ans_text.replaceWith(this_ans_static); + +// MathJax.Hub.Queue(["Typeset",MathJax.Hub]); + if (mjvers && mjvers < 3) { + MathJax.Hub.Queue(['Typeset', MathJax.Hub]); + } else if (mjvers > 3) { + MathJax.typesetPromise(); + } + + + console.log(" this_ans_text", this_ans_text); + + console.log("this_ans_static", this_ans_static); + + $(this_ans_static).parent().addClass("rq_answer"); + + $('#' + this_ques_id + "_controls").addClass("hidecontrols"); +// $(this_q).parent().parent().addClass("rq_answer"); + +/* + var edit_button = document.createElement('span'); + edit_button.setAttribute('class', "action edit_item rq_edit"); + edit_button.innerHTML = "edit"; + this.replaceWith(edit_button); +*/ + }; + + /* edit an existing answer */ + function edit_one_reading_question(this_ans) { + console.log(".rq_edit", this_ans); + // var this_ques_id = this.parentNode.previousSibling.id; + var this_ques_id = this_ans.parentNode.parentNode.id; + console.log("edditing this_ques_id", this_ques_id); + var this_ques_id_text = this_ques_id + "_text"; + // var this_rq_ans = this.parentNode.previousSibling; + var this_rq_ans = this_ans; +// var this_rq_ans_id = this_ans.id; + console.log("now this_ques_id_text", this_ques_id_text); + console.log(".rq_edit", this_ques_id); + console.log("this_rq_ans", this_rq_ans); + var this_rq_text = this_rq_ans.innerHTML; + console.log("looking for", this_ques_id + "_hidden"); + var this_rq_text_raw = uNescapeHTML(document.getElementById(this_ques_id + "_text_hidden").innerHTML); + console.log("this_rq_text_raw",this_rq_text_raw); + + //this is copied from above. need to eliminate repeated code + + var answer_textarea_editable = document.createElement('textarea'); + answer_textarea_editable.setAttribute('id', this_ques_id_text + "_input"); + answer_textarea_editable.setAttribute('class', 'rq_answer_text'); + answer_textarea_editable.setAttribute('rows', '3'); + answer_textarea_editable.setAttribute('style', 'width:95%; height: 44px;'); + + this_rq_ans.replaceWith(answer_textarea_editable); + + console.log("this_ans is",this_ans); + console.log("adding editing to the parent of thing with id", this_ques_id_text, "which is the parent of", $('#' + this_ques_id_text)); + $('#' + this_ques_id_text + "_input").parent().addClass("editing"); + + $('#' + this_ques_id + "_controls").removeClass("hidecontrols"); + +// WHY? $(this).parent().parent().removeClass("rq_answer"); + +/* var save_button = document.createElement('span'); + save_button.setAttribute('class', "action edit_item rq_save"); + save_button.innerHTML = "save"; + this.replaceWith(save_button); +*/ + + $('#' + this_ques_id + "_text_input").val(this_rq_text_raw); + + answer_textarea_editable.style.height = answer_textarea_editable.scrollHeight + "px"; + answer_textarea_editable.addEventListener("keypress", function() { + // if(answer_textarea_editable.scrollTop != 0){ + answer_textarea_editable.style.height = answer_textarea_editable.scrollHeight + "px"; + // } + }, false); + }; + + +/* handle saving when leaving an answer box, or editing an existing answer + when hovering over an existing answer */ + + $('body').on('click','.given_answer', function(){ +// $(this).children().last().removeClass("hidecontrols"); + edit_one_reading_question(this); + $(this).attr('z-index', '2000'); + }); + + $('body').on('mousedown','.rq_save', function(){ +// $(this).children().last().addClass("hidecontrols"); +// $(this).attr('z-index', ''); + // var this_rq = $(this).find(".given_answer"); +//ooooooooo var this_ques_id = this.parentNode.id; + var this_ques_id = this.parentNode.parentNode.parentNode.id; + console.log("id of this quesiton", this_ques_id); +// var this_rq = $(this).find(".rq_answer_text"); +// console.log("this_rq iiIIIIII", this_rq); + var this_current_answer = document.getElementById(this_ques_id + "_text_input"); + console.log("this_current_answer", this_current_answer); + console.log("this_current_answer.value", this_current_answer.value); + // console.log("this_rq IIIIiiii value", this_rq.value); +// save_one_reading_question(this_rq.parentNode.id); + save_one_reading_question(this_ques_id); + console.log("left answer area"); + $(this).removeClass("editing"); + }); + + $('body').on('click','.rq_delete', function(){ + console.log(".rq_delete"); + var this_ques_id = this.parentNode.parentNode.parentNode.id; + console.log(".rq_delete", this_ques_id); + $('#' + this_ques_id + "_controls").removeClass("hidecontrols"); +//and now put in controls + var this_answer_link = document.createElement('div'); + this_answer_link.innerHTML = rq_answer_label; + console.log("inserting afterend of",reading_question); + // reading_question.insertAdjacentElement("afterend", this_answer_link); + $(this).parent().parent().replaceWith(this_answer_link); + allow_student_answers(); + + + console.log("reading_questions_object", reading_questions_object); + console.log("this_ques_id + _text", this_ques_id + "_text"); + delete reading_questions_object[this_ques_id + "_text"]; + console.log("now reading_questions_object", reading_questions_object); + // localStorage.removeItem(this_ques_id); + localStorage.setObject(reading_questions_object_id, reading_questions_object); + }); + + if(reading_questions_all_answered && uname != "guest" && role=="student") { + make_submit_button(); + console.log("made submit button"); + } + + $('body').on('click','#rq_submit', function(){ + console.log("submitting rq answers"); + $('#rq_submit').addClass('submitted'); + document.getElementById('rq_submit').textContent = "Resubmit answers"; + save_reading_questions(); + }); + + $('body').on('click','.amhelp', function(){ + var amhelpmessage = "Write math formulas as AsciiMath inside `backticks`.\n"; + amhelpmessage += "For example:\nThe Pythagorean theorem says `sin^2(x) + cos^2(x) = 1`,\n"; + amhelpmessage += "The quadratic formula is `x = (-b +- sqrt(b^2 - 4ac))/(2a)`. \n"; + amhelpmessage += "Note the use of parentheses for grouping.\n"; + amhelpmessage += "Visit http://asciimath.org for a list of AsciiMath commands.\n\n"; + amhelpmessage += "You can also use LaTeX, with either slash-parentheses \\(...\\)\n"; + amhelpmessage += "or dollar signs $...$ as delimiters for inline math."; + alert(amhelpmessage) + }); + + if(reading_questions_all_answered && uname != "guest" && role=="student") { + make_submit_button(); + } +} diff --git a/slides-annotated/_static/pretext/js/diagcess/diagcess.js b/slides-annotated/_static/pretext/js/diagcess/diagcess.js new file mode 100755 index 0000000..69acc7c --- /dev/null +++ b/slides-annotated/_static/pretext/js/diagcess/diagcess.js @@ -0,0 +1 @@ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.diagcess=e():t.diagcess=e()}(this,(function(){return(()=>{"use strict";var t={422:(t,e)=>{function n(t){return e.prefix_+"-"+t}Object.defineProperty(e,"__esModule",{value:!0}),e.noMove=e.attr=e.hasSpeech=e.getDocument=e.children=e.classList=e.getAttribute=e.getTextContent=e.toggleExpertMode=e.EXPERT_MODE=e.toArray=e.getId=e.idCounter_=e.addPrefix=e.prefix_=void 0,e.prefix_="cacc",e.addPrefix=n,e.idCounter_=0,e.getId=function(){return n("Id-"+e.idCounter_++)},e.toArray=function(t){for(var e=[],n=0;n=t.length&&(t=void 0),{value:t&&t[o++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.addStyle=e.replaceStyle_=e.removeStyle_=e.svgForegroundStyle_=e.svgBackgroundStyle_=e.svgHighlightStyle_=e.regionStyle_=e.svgBaseStyle_=e.nodeBackgroundStyle_=e.pickScheme=e.moleculeStyle=e.getScheme_=e.colorSchemes_=e.toggleMonochrome=e.toggleTextVisibility=e.documentCmlStyle=void 0;var r=n(422),i=n(807);function c(t){return e.colorSchemes_[t%e.colorSchemes_.length]}function a(t,e){var n=c(e);return[p(t,n.bkd),s(t,n.bkd),h(t,n.frgd),u(t,n.active,n.almost)]}function s(t,e){var n="";return f(n+="div.".concat(t.node.id," { background-color: ").concat(e,"; }"),"cacc-general-background")}function l(t){var e="svg."+t.id,n="";return n+="".concat(e," { overflow: hidden; }"),n+="".concat(e," text tspan[baseline-shift] { font-weight: 200; font-size: 13px; }"),n+="".concat(e," line, svg rect { stroke-width: 1.0; stroke-linecap: round; }"),n+="".concat(e," line.").concat("cacc-active",", svg g.").concat("cacc-active"," line { stroke-width: 3.0; }"),n+="".concat(e," line.").concat("cacc-almost",", svg g.").concat("cacc-almost"," line { stroke-width: 2.0; }"),n+="".concat(e," g.").concat("cacc-active"," text { font-weight: bold; }"),n+="".concat(e," g#gridSVG g.").concat("cacc-active"," text { font-weight: bold; }"),n+="ul.".concat("cacc-controls"," { list-style-type: none; }"),n+="".concat(e," polyline.").concat("cacc-active",", svg g.").concat("cacc-active"," polyline { stroke-width: 3.0; }"),n+="".concat(e," polyline.").concat("cacc-almost",", svg g.").concat("cacc-almost"," polyline { stroke-width: 2.0; }"),n+="".concat(e," polygon.").concat("cacc-active",", svg g.").concat("cacc-active"," polygon { stroke-width: 3.0; }"),n+="".concat(e," polygon.").concat("cacc-almost",", svg g.").concat("cacc-almost"," polygon { stroke-width: 2.0; }"),n+="".concat(e," path.").concat("cacc-active",", svg g.").concat("cacc-active"," path { stroke-width: 3.0; }"),n+="".concat(e," path.").concat("cacc-almost",", svg g.").concat("cacc-almost"," path { stroke-width: 2.0; }"),n+="".concat(e," rect.").concat("cacc-active",", svg g.").concat("cacc-active"," rect { stroke-width: 3.0; }"),n+="".concat(e," rect.").concat("cacc-almost",", svg g.").concat("cacc-almost"," rect { stroke-width: 2.0; }"),n+="".concat(e," circle.").concat("cacc-active",", svg g.").concat("cacc-active"," circle { stroke-width: 3.0; }"),n+="".concat(e," circle.").concat("cacc-almost",", svg g.").concat("cacc-almost"," circle { stroke-width: 2.0; }"),n+="".concat(e," ellipse.").concat("cacc-active",", svg g.").concat("cacc-active"," ellipse { stroke-width: 3.0; }"),f(n+="".concat(e," ellipse.").concat("cacc-almost",", svg g.").concat("cacc-almost"," ellipse { stroke-width: 2.0; }"),"cacc-base")}function u(t,e,n){var o="svg."+t.id,r="";return r+="".concat(o," line.").concat("cacc-active",", g.").concat("cacc-active"," line { stroke: ").concat(e,"; }"),r+="".concat(o," line.").concat("cacc-almost",", g.").concat("cacc-almost"," line { stroke: ").concat(n,"; }"),r+="".concat(o," g.").concat("cacc-active"," text { fill: ").concat(e,"; }"),r+="".concat(o," g.").concat("cacc-almost"," text { fill: ").concat(n,"; }"),r+="".concat(o," g#gridSVG g.").concat("cacc-active"," text { fill: ").concat(e,"; }"),r+="".concat(o," g#gridSVG g.").concat("cacc-almost"," text { fill: ").concat(n,"; }"),r+="".concat(o," polyline.").concat("cacc-active",", g.").concat("cacc-active"," polyline { stroke: ").concat(e,"; }"),r+="".concat(o," polyline.").concat("cacc-almost",", g.").concat("cacc-almost"," polyline { stroke: ").concat(n,"; }"),r+="".concat(o," polygon.").concat("cacc-active",", g.").concat("cacc-active"," polygon { stroke: ").concat(e,"; }"),r+="".concat(o," polygon.").concat("cacc-almost",", g.").concat("cacc-almost"," polygon { stroke: ").concat(n,"; }"),r+="".concat(o," path.").concat("cacc-active",", g.").concat("cacc-active"," path { stroke: ").concat(e,"; }"),r+="".concat(o," path.").concat("cacc-almost",", g.").concat("cacc-almost"," path { stroke: ").concat(n,"; }"),r+="".concat(o," rect.").concat("cacc-active",", g.").concat("cacc-active"," rect { stroke: ").concat(e,"; }"),r+="".concat(o," rect.").concat("cacc-almost",", g.").concat("cacc-almost"," rect { stroke: ").concat(n,"; }"),r+="".concat(o," circle.").concat("cacc-active",", g.").concat("cacc-active"," circle { stroke: ").concat(e,"; }"),r+="".concat(o," circle.").concat("cacc-almost",", g.").concat("cacc-almost"," circle { stroke: ").concat(n,"; }"),r+="".concat(o," ellipse.").concat("cacc-active",", g.").concat("cacc-active"," ellipse { stroke: ").concat(e,"; }"),f(r+="".concat(o," ellipse.").concat("cacc-almost",", g.").concat("cacc-almost"," ellipse { stroke: ").concat(n,"; }"),"cacc-color-highlight")}function p(t,e){var n="svg."+t.id,o="";return o+="".concat(n," { background-color: ").concat(e,"; }"),o+="".concat(n," g.atom rect { stroke: ").concat(e,"; fill: ").concat(e,"; }"),o+="div.container { background-color: ".concat(e,"; }"),o+="p.".concat(t.id,".").concat("cacc-message"," { background-color: ").concat(e,"; }"),f(o+="p.".concat(t.id,".").concat("cacc-message",".").concat("cacc-text-invisible"," { color: ").concat(e,"; }"),"cacc-color-background")}function h(t,e){var n="svg."+t.id,o="";return o+="".concat(n," line { stroke: ").concat(e,"; fill: ").concat(e,"; }"),o+="".concat(n,".").concat("cacc-text-monochrome"," text"),o+=" { stroke: ".concat(e,"; fill: ").concat(e,"; }"),o+="".concat(n," g#gridSVG text { stroke: ").concat(e,"; fill: ").concat(e,"; }"),o+="div.container { color: ".concat(e,"; }"),o+="p.".concat(t.id,".").concat("cacc-message"," { color: ").concat(e,"; }"),o+="ul.".concat(t.id,".").concat("cacc-controls"," { color: ").concat(e,"; }"),i.config.POLYLINE&&(o+="".concat(n," polyline { stroke: ").concat(e,"; fill: ").concat(e,"; }")),o+="".concat(n," polygon { stroke: ").concat(e,"; fill: ").concat(e,"; }"),o+="".concat(n," path { stroke: ").concat(e,"; }"),o+="".concat(n," rect { stroke: ").concat(e,"; }"),o+="".concat(n," circle { stroke: ").concat(e,"; }"),f(o+="".concat(n," ellipse { stroke: ").concat(e,"; }"),"cacc-color-elements")}function f(t,e){var n=document.createElement("style");return n.setAttribute("class",e),n.innerHTML=t,n}function d(t,e){var n=t.querySelector("style."+e);return!!n&&(t.removeChild(n),!0)}function v(t,e){t.appendChild(e)}e.documentCmlStyle=function(){document.body&&v(document.body,f("div.cml { display:none; }","cacc-cml"))},e.toggleTextVisibility=function(t){r.classList.toggle(t,"cacc-text-invisible")},e.toggleMonochrome=function(t){r.classList.toggle(t,"cacc-text-monochrome")},e.colorSchemes_=[{frgd:"#000",bkd:"#fff",active:"#fd6",almost:"#6df"},{frgd:"#fff",bkd:"#000",active:"#fd6",almost:"#6df"},{frgd:"#3C3",bkd:"#000",active:"#fd6",almost:"#6df"},{frgd:"#ee0",bkd:"#00e",active:"#4f4",almost:"#e3e"}],e.getScheme_=c,e.moleculeStyle=function(t){var e,n,r=new Map,i=l(t);r.set(i.className,i);try{for(var c=o(a(t,0)),s=c.next();!s.done;s=c.next()){var u=s.value;r.set(u.className,u)}}catch(t){e={error:t}}finally{try{s&&!s.done&&(n=c.return)&&n.call(c)}finally{if(e)throw e.error}}return r},e.pickScheme=a,e.nodeBackgroundStyle_=s,e.svgBaseStyle_=l,e.regionStyle_=function(t){v(t,f("p.".concat("cacc-message",".").concat("cacc-text-invisible"," { font-size: 0px; }"),"cacc-region"))},e.svgHighlightStyle_=u,e.svgBackgroundStyle_=p,e.svgForegroundStyle_=h,e.removeStyle_=d,e.replaceStyle_=function(t,e,n){d(t,n),v(t,f(e,n))},e.addStyle=v},446:(t,e)=>{Object.defineProperty(e,"__esModule",{value:!0}),e.CustomEvent=e.EventType=e.KeyCodes=void 0,function(t){t[t.RETURN=13]="RETURN",t[t.ENTER=13]="ENTER",t[t.ESC=27]="ESC",t[t.SPACE=32]="SPACE",t[t.LEFT=37]="LEFT",t[t.UP=38]="UP",t[t.RIGHT=39]="RIGHT",t[t.DOWN=40]="DOWN",t[t.A=65]="A",t[t.B=66]="B",t[t.C=67]="C",t[t.D=68]="D",t[t.E=69]="E",t[t.F=70]="F",t[t.G=71]="G",t[t.H=72]="H",t[t.I=73]="I",t[t.J=74]="J",t[t.K=75]="K",t[t.L=76]="L",t[t.M=77]="M",t[t.N=78]="N",t[t.O=79]="O",t[t.P=80]="P",t[t.Q=81]="Q",t[t.R=82]="R",t[t.S=83]="S",t[t.T=84]="T",t[t.U=85]="U",t[t.V=86]="V",t[t.W=87]="W",t[t.X=88]="X",t[t.Y=89]="Y",t[t.Z=90]="Z",t[t.COMMA=188]="COMMA",t[t.PERIOD=190]="PERIOD"}(e.KeyCodes||(e.KeyCodes={})),function(t){t.CLICK="click",t.DBLCLICK="dblclick",t.MOUSEDOWN="mousedown",t.MOUSEUP="mouseup",t.MOUSEOVER="mouseover",t.MOUSEOUT="mouseout",t.MOUSEMOVE="mousemove",t.SELECTSTART="selectstart",t.KEYPRESS="keypress",t.KEYDOWN="keydown",t.KEYUP="keyup",t.TOUCHSTART="touchstart",t.TOUCHMOVE="touchmove",t.TOUCHEND="touchend",t.TOUCHCANCEL="touchcancel"}(e.EventType||(e.EventType={}));var n=function(){function t(t,e,n){this.src=t,this.type=e,this.callback=n,this.src=t,this.type=e,this.callback=n}return t.prototype.add=function(){this.src.addEventListener(this.type,this.callback)},t.prototype.remove=function(){this.src.removeEventListener(this.type,this.callback)},t}();e.CustomEvent=n},807:(t,e)=>{var n,o,r,i,c,a;Object.defineProperty(e,"__esModule",{value:!0}),e.config=e.options=e.sound=e.speech=e.colors=e.regions=e.magnifiers=e.explorers=e.version=void 0,e.version="1.1.4",function(t){t.KEY="key",t.MENU="menu"}(n=e.explorers||(e.explorers={})),function(t){t.DUMMY="dummy",t.STEP="step",t.SIMPLE="simple"}(o=e.magnifiers||(e.magnifiers={})),function(t){t.SUBTITLE="subtitle",t.SPEECH="speech"}(r=e.regions||(e.regions={})),function(t){t.MONOCHROME="monochrome",t.CONTRAST="contrast"}(i=e.colors||(e.colors={})),function(t){t.LANGUAGE="language",t.EXPERT="expert",t.DETAIL="detail"}(c=e.speech||(e.speech={})),function(t){t.OSCILLATOR="oscillator",t.VOICING="voicing"}(a=e.sound||(e.sound={})),e.options={explorers:[n.KEY,n.MENU],magnifiers:[o.STEP,o.SIMPLE,o.DUMMY],regions:[r.SUBTITLE,r.SPEECH],colors:[i.MONOCHROME,i.CONTRAST],speech:[c.LANGUAGE,c.EXPERT,c.DETAIL],sound:[a.OSCILLATOR,a.VOICING]},e.config={MSG_VISIBLE:!0,MAG_ACTIVE:o.STEP,SONIFY:!1,VOICING:!0,VOICE_LANG:"en",ACTIVE_LANG:"en",SUB_VISIBLE:!1,SUBTITLE:null,POLYLINE:!1}},23:function(t,e,n){var o=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,n=e&&t[e],o=0;if(n)return n.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&o>=t.length&&(t=void 0),{value:t&&t[o++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},r=this&&this.__read||function(t,e){var n="function"==typeof Symbol&&t[Symbol.iterator];if(!n)return t;var o,r,i=n.call(t),c=[];try{for(;(void 0===e||e-- >0)&&!(o=i.next()).done;)c.push(o.value)}catch(t){r={error:t}}finally{try{o&&!o.done&&(n=i.return)&&n.call(i)}finally{if(r)throw r.error}}return c};Object.defineProperty(e,"__esModule",{value:!0}),e.Base=void 0;var i=n(966),c=n(422),a=n(911),s=n(807),l=n(402);!function(t){t.molMap={},t.init=function(){var e,n,u=l.forDocument();Object.keys(u).length>0&&(a.documentCmlStyle(),s.config.SUBTITLE=function(){var t=c.getDocument(),e=t.createElement("p");e.classList.add("explorer"),e.classList.add("cacc-message");var n=a.getScheme_(1);e.style.cssText+="position:fixed;left:0;bottom:0;"+"background-color: ".concat(n.bkd,"; color: ").concat(n.frgd),a.regionStyle_(t.body),s.config.SUB_VISIBLE||e.classList.add("cacc-text-invisible");return e}());try{for(var p=o(Object.entries(u)),h=p.next();!h.done;h=p.next()){var f=r(h.value,2),d=f[0],v=f[1];t.molMap[d]||(t.molMap[d]=new i.Container(d,v))}}catch(t){e={error:t}}finally{try{h&&!h.done&&(n=p.return)&&n.call(p)}finally{if(e)throw e.error}}}}(e.Base||(e.Base={}))},402:function(t,e,n){var o=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,n=e&&t[e],o=0;if(n)return n.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&o>=t.length&&(t=void 0),{value:t&&t[o++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.fromUri_=e.getUrlParameters_=e.getParam_=e.getData_=e.getMol_=e.forDocument=void 0;var r=n(29),i=0;function c(t){t.querySelector("div.svg")?(t.setAttribute("has-svg","true"),r.rewriteTransformations(t.querySelector("div.svg > svg"))):a(t,"svg","data-src",".svg"),t.querySelector("div.cml")?t.setAttribute("has-cml","true"):a(t,"cml","data-cml",".xml")}function a(t,e,n,o){var i=t.getAttribute(n)||s(t,n,o);i?u(i,(function(n){if(n){t.setAttribute("has-"+e,"true");var o=document.createElement("div");o.classList.add(e),"svg"===e&&r.rewriteTransformations(n),o.appendChild(n.documentElement),t.appendChild(o)}else t.setAttribute("has-"+e,"false")})):t.setAttribute("has-"+e,"false")}function s(t,e,n){if(!t.id)return null;var o=l()[t.id];return o?(o+=n,t.setAttribute(e,o),o):null}function l(){for(var t,e={},n=window.location.href.slice(window.location.href.indexOf("?")+1).split("&"),o=0;o{Object.defineProperty(e,"__esModule",{value:!0}),e.rewriteTransformations=void 0;var o=n(422);function r(t){var e=t,n=[];do{var o=e.getAttribute("transform");o&&n.unshift(o),e=e.parentNode}while("svg"!==e.tagName);return t.setAttribute("transform",n.join(" ")),t}function i(t){var e=document.createElementNS("http://www.w3.org/2000/svg","g"),n=t.getAttribute("id");return n&&(e.setAttribute("id",n),t.removeAttribute("id")),t.parentNode.replaceChild(e,t),e.appendChild(t),e}function c(t){var e=t;do{(e=e.parentNode).removeAttribute("transform")}while("svg"!==e.tagName)}e.rewriteTransformations=function(t){var e,n=o.toArray(t.querySelectorAll("text,polyline,line,rect,polygon,use,path"));return(e=n).forEach(r),e.forEach(c),e.filter((function(t){return t.hasAttribute("id")})).forEach(i),n}},113:function(t,e,n){var o,r=this&&this.__extends||(o=function(t,e){return o=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n])},o(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function n(){this.constructor=t}o(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)});Object.defineProperty(e,"__esModule",{value:!0}),e.Contrast=e.Monochrome=e.AbstractColor=void 0;var i=n(911),c=n(446),a=n(807),s=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return r(e,t),e.prototype.update=function(){},e}(n(530).AbstractController);e.AbstractColor=s;var l=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.name=a.colors.MONOCHROME,e}return r(e,t),e.prototype.customEvents=function(){var t=this;this.registerEvent(this.container.molecule.node,c.EventType.KEYDOWN,(function(e){return e.keyCode===c.KeyCodes.T&&i.toggleMonochrome(t.container.molecule.svg),e.stopPropagation(),!1}))},e}(s);e.Monochrome=l;var u=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.name=a.colors.CONTRAST,e.scheme=0,e}return r(e,t),e.prototype.customEvents=function(){var t=this;this.registerEvent(this.container.molecule.node,c.EventType.KEYDOWN,(function(e){return e.keyCode===c.KeyCodes.C&&t.container.replaceStyles(i.pickScheme(t.container.molecule,++t.scheme)),e.stopPropagation(),!1}))},e}(s);e.Contrast=u},530:function(t,e,n){var o=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,n=e&&t[e],o=0;if(n)return n.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&o>=t.length&&(t=void 0),{value:t&&t[o++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.AbstractController=void 0;var r=n(446),i=function(){function t(t){this.container=t,this._active=!1,this.internalEvents_=[],this.customEvents()}return Object.defineProperty(t.prototype,"active",{get:function(){return this._active},set:function(t){this._active=t},enumerable:!1,configurable:!0}),t.prototype.activate=function(){this.active=!0,this.addEvents()},t.prototype.deactivate=function(){this.removeEvents(),this.active=!1},t.prototype.registerEvent=function(t,e,n){this.internalEvents_.push(new r.CustomEvent(t,e,n))},t.prototype.addEvents=function(){var t,e;try{for(var n=o(this.internalEvents_),r=n.next();!r.done;r=n.next()){r.value.add()}}catch(e){t={error:e}}finally{try{r&&!r.done&&(e=n.return)&&e.call(n)}finally{if(t)throw t.error}}},t.prototype.removeEvents=function(){var t,e;try{for(var n=o(this.internalEvents_),r=n.next();!r.done;r=n.next()){r.value.remove()}}catch(e){t={error:e}}finally{try{r&&!r.done&&(e=n.return)&&e.call(n)}finally{if(t)throw t.error}}},t}();e.AbstractController=i},889:function(t,e,n){var o=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,n=e&&t[e],o=0;if(n)return n.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&o>=t.length&&(t=void 0),{value:t&&t[o++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.ControllerFactory=void 0;var r=n(807),i=n(113),c=n(172),a=n(10),s=n(556),l=n(150),u=n(916);!function(t){var e,n=((e={})[r.magnifiers.SIMPLE]=function(t){return new a.SimpleMagnifier(t)},e[r.magnifiers.STEP]=function(t){return new a.StepMagnifier(t)},e[r.magnifiers.DUMMY]=function(t){return new a.DummyMagnifier(t)},e[r.colors.MONOCHROME]=function(t){return new i.Monochrome(t)},e[r.colors.CONTRAST]=function(t){return new i.Contrast(t)},e[r.speech.DETAIL]=function(t){return new u.Detail(t)},e[r.speech.EXPERT]=function(t){return new u.Expert(t)},e[r.speech.LANGUAGE]=function(t){return new u.Language(t)},e[r.regions.SPEECH]=function(t){return new s.Speech(t)},e[r.regions.SUBTITLE]=function(t){return new s.Subtitle(t)},e[r.explorers.KEY]=function(t){return new c.KeyExplorer(t)},e[r.explorers.MENU]=function(t){return new c.MenuExplorer(t)},e[r.sound.OSCILLATOR]=function(t){return new l.Oscillator(t)},e[r.sound.VOICING]=function(t){return new l.Voicing(t)},e);t.get=function(t){var e,i,c=[],a=[].concat(r.options.magnifiers,r.options.regions,r.options.colors,r.options.speech,r.options.sound,r.options.explorers);try{for(var s=o(a),l=s.next();!l.done;l=s.next()){var u=l.value,p=n[u];if(p){var h=p(t);c.push(h)}}}catch(t){e={error:t}}finally{try{l&&!l.done&&(i=s.return)&&i.call(s)}finally{if(e)throw e.error}}return c}}(e.ControllerFactory||(e.ControllerFactory={}))},172:function(t,e,n){var o,r=this&&this.__extends||(o=function(t,e){return o=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n])},o(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function n(){this.constructor=t}o(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)});Object.defineProperty(e,"__esModule",{value:!0}),e.MenuExplorer=e.KeyExplorer=e.AbstractExplorer=void 0;var i=n(422),c=n(446),a=n(807),s=function(t){function e(e){var n=t.call(this,e)||this;return n.container=e,n.setupControls(),n}return r(e,t),e.prototype.activate=function(){this.active&&(this.container.update(),this.addControls(),t.prototype.activate.call(this))},e.prototype.deactivate=function(){this.active&&(t.prototype.deactivate.call(this),this.removeControls())},e.prototype.addEvents=function(){this.active&&t.prototype.addEvents.call(this)},e.prototype.setupControls=function(){},e.prototype.addControls=function(){},e.prototype.removeControls=function(){},e.prototype.update=function(){},e.prototype.customEvents=function(){var t=this,e=this.container.molecule.node;new c.CustomEvent(e,c.EventType.KEYDOWN,(function(e){return t.active&&e.keyCode===c.KeyCodes.ESC&&t.container.deactivate(),t.container.active||e.keyCode===t.getActivationKey()&&(t.active=!0,t.container.activate(),e.preventDefault()),e.stopPropagation(),!1})).add()},e}(n(530).AbstractController);e.AbstractExplorer=s;var l=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.name=a.explorers.KEY,e}return r(e,t),e.prototype.setupControls=function(){var t=this,e=function(e,n){var o=n();o.length>0?t.container.update(o[0]):(0,i.noMove)(),e.preventDefault()};this.registerEvent(this.container.molecule.node,c.EventType.KEYDOWN,(function(n){switch(n.keyCode){case c.KeyCodes.DOWN:case c.KeyCodes.D:e(n,(function(){return t.container.activeElement.getDown()}));break;case c.KeyCodes.UP:case c.KeyCodes.E:e(n,(function(){return t.container.activeElement.getUp()}));break;case c.KeyCodes.RIGHT:case c.KeyCodes.F:e(n,(function(){return t.container.activeElement.getRight()}));break;case c.KeyCodes.LEFT:case c.KeyCodes.S:e(n,(function(){return t.container.activeElement.getLeft()}))}return n.stopPropagation(),!1}))},e.prototype.getActivationKey=function(){return c.KeyCodes.A},e}(s);e.KeyExplorer=l;var u=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.name=a.explorers.MENU,e}return r(e,t),e.prototype.setupControls=function(){var t=this,e=this.container.molecule.createElement("ul");e.classList.add("cacc-controls"),e.setAttribute("role","navigation"),this.control_=e;var n=function(e,n){var o=n();o.length>0?t.container.update(o[0]):(0,i.noMove)()},o=function(e,o){t.registerEvent(e,c.EventType.KEYDOWN,(function(t){return t.keyCode!==c.KeyCodes.ENTER&&t.keyCode!==c.KeyCodes.SPACE||(n(0,o),t.preventDefault(),t.stopPropagation()),!1})),t.registerEvent(e,c.EventType.CLICK,(function(t){return n(0,o),t.stopPropagation(),!1}))};o(this.makeButton_("Down",e),(function(){return t.container.activeElement.getDown()})),o(this.makeButton_("Up",e),(function(){return t.container.activeElement.getUp()})),o(this.makeButton_("Next",e),(function(){return t.container.activeElement.getRight()})),o(this.makeButton_("Previous",e),(function(){return t.container.activeElement.getLeft()}))},e.prototype.addControls=function(){var t=this.container.molecule.node,e=t.querySelector(".cacc-message");e?t.insertBefore(this.control_,e):t.appendChild(this.control_)},e.prototype.removeControls=function(){this.control_.parentNode.removeChild(this.control_)},e.prototype.getActivationKey=function(){return c.KeyCodes.B},e.prototype.makeButton_=function(t,e){var n=this.container.molecule.createElement("li");return n.setAttribute("role","button"),n.setAttribute("aria-controls",this.container.molecule.node.getAttribute("id")),n.classList.add("cacc-button"),n.setAttribute("tabindex","0"),n.setAttribute("title",t),n.appendChild(document.createTextNode(t)),e.appendChild(n),n},e}(s);e.MenuExplorer=u},10:function(t,e,n){var o,r=this&&this.__extends||(o=function(t,e){return o=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n])},o(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function n(){this.constructor=t}o(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)});Object.defineProperty(e,"__esModule",{value:!0}),e.StepMagnifier=e.SimpleMagnifier=e.DummyMagnifier=e.AbstractMagnifier=void 0;var i=n(446),c=n(807),a=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return r(e,t),Object.defineProperty(e.prototype,"active",{get:function(){return this._active&&c.config.MAG_ACTIVE===this.name},set:function(t){this._active=t},enumerable:!1,configurable:!0}),e.prototype.update=function(){this.container.viewBox.select(this.container.activeElement.getDrawnElements())},e.prototype.deactivate=function(){this.active&&(this.container.viewBox.clearActive(),this.container.viewBox.reset(),t.prototype.deactivate.call(this))},e.prototype.customEvents=function(){var t=this;this.registerEvent(this.container.molecule.node,i.EventType.KEYDOWN,(function(e){return e.keyCode===t.getActivationKey()&&c.config.MAG_ACTIVE!==t.name&&(c.config.MAG_ACTIVE=t.name,t.update()),e.stopPropagation(),!1}))},e}(n(530).AbstractController);e.AbstractMagnifier=a;var s=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.name=c.magnifiers.DUMMY,e}return r(e,t),e.prototype.getActivationKey=function(){return i.KeyCodes.N},e.prototype.update=function(){this.container.viewBox.reset(),t.prototype.update.call(this)},e}(a);e.DummyMagnifier=s;var l=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.name=c.magnifiers.SIMPLE,e}return r(e,t),e.prototype.update=function(){this.container.viewBox.select(this.container.activeElement.getDrawnElements()),this.container.viewBox.zoom("cacc-active")},e.prototype.getActivationKey=function(){return i.KeyCodes.COMMA},e}(a);e.SimpleMagnifier=l;var u=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.name=c.magnifiers.STEP,e.lastActive=null,e}return r(e,t),e.prototype.update=function(){var t=this.container.activeElement,e=this.container.viewBox,n=this.lastActive?this.lastActive.getDrawnElements():[];this.container.viewBox.select(t.getDrawnElements(),"cacc-almost"),e.select(t.getDrawnElements().concat(n),"cacc-dummy"),this.lastActive=t,e.zoom("cacc-dummy",{duration:250,delay:25,wait:300,callback:function(){e.select([],"cacc-almost"),e.select(t.getDrawnElements(),"cacc-active"),e.zoom("cacc-active",{duration:250,delay:25,wait:300})}})},e.prototype.getActivationKey=function(){return i.KeyCodes.M},e}(a);e.StepMagnifier=u},556:function(t,e,n){var o,r=this&&this.__extends||(o=function(t,e){return o=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n])},o(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function n(){this.constructor=t}o(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)});Object.defineProperty(e,"__esModule",{value:!0}),e.Speech=e.Subtitle=e.LiveRegion=void 0;var i=n(422),c=n(911),a=n(446),s=n(807),l=function(t){function e(e){var n=t.call(this,e)||this;return n.container=e,n.setupMessage(),n}return r(e,t),e.prototype.setupMessage=function(){var t=this.container.molecule.createElement("p");t.classList.add("explorer"),t.setAttribute("aria-live","assertive"),i.hasSpeech()&&(speechSynthesis.onvoiceschanged=function(){speechSynthesis.getVoices().length&&t.removeAttribute("aria-live")}),t.setAttribute("tabindex","-1"),t.classList.add("cacc-message"),this.message=t},e.prototype.activate=function(){this.active||(this.addMessage(),t.prototype.activate.call(this))},e.prototype.deactivate=function(){this.active&&(t.prototype.deactivate.call(this),this.removeMessage())},e.prototype.removeMessage=function(){this.message.parentNode.removeChild(this.message)},e.prototype.update=function(){this.message.textContent=this.container.activeElement.getSpeech()},e.prototype.customEvents=function(){var t=this;this.registerEvent(this.container.molecule.node,a.EventType.KEYDOWN,(function(e){return e.keyCode===a.KeyCodes.Z&&(t.toggleMessageVisibility(),s.config.MSG_VISIBLE=!s.config.MSG_VISIBLE),e.stopPropagation(),!1}))},e.prototype.toggleMessageVisibility=function(){c.toggleTextVisibility(this.message)},e}(n(530).AbstractController);e.LiveRegion=l;var u=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.name=s.regions.SUBTITLE,e}return r(e,t),e.prototype.addMessage=function(){this.container.molecule.node.parentNode.appendChild(this.message),s.config.MSG_VISIBLE||this.message.classList.add("cacc-text-invisible")},e}(l);e.Subtitle=u;var p=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.name=s.regions.SPEECH,e}return r(e,t),e.prototype.setupMessage=function(){this.message=s.config.SUBTITLE},e.prototype.addMessage=function(){this.message.parentNode||i.getDocument().body.appendChild(this.message)},e.prototype.customEvents=function(){var t=this;this.registerEvent(this.container.molecule.node,a.EventType.KEYDOWN,(function(e){return e.keyCode===a.KeyCodes.V&&(t.toggleMessageVisibility(),s.config.SUB_VISIBLE=!s.config.SUB_VISIBLE),e.stopPropagation(),!1}))},e}(l);e.Speech=p},150:function(t,e,n){var o,r=this&&this.__extends||(o=function(t,e){return o=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n])},o(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function n(){this.constructor=t}o(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)});Object.defineProperty(e,"__esModule",{value:!0}),e.Voicing=e.Oscillator=e.AbstractSoundController=void 0;var i=n(422),c=n(446),a=n(807),s=n(530),l=n(394),u=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return r(e,t),e.prototype.activate=function(){t.prototype.activate.call(this),this.active=this.on()},e.prototype.deactivate=function(){this.stop(),t.prototype.deactivate.call(this)},e.prototype.update=function(){this.stop()},e.prototype.getStopKey=function(){return c.KeyCodes.K},e.prototype.customEvents=function(){var t=this;this.registerEvent(this.container.molecule.node,c.EventType.KEYDOWN,(function(e){return e.keyCode===t.getActivationKey()&&(t.active=!t.active,t.toggle(t.active),t.active?t.update():t.stop()),e.keyCode===t.getRepeatKey()&&t.update(),e.keyCode===t.getStopKey()&&t.stop(),e.stopPropagation(),!1}))},e}(s.AbstractController);e.AbstractSoundController=u;var p=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.name=a.sound.OSCILLATOR,e.currentOscillators=[],e}return r(e,t),e.prototype.update=function(){t.prototype.update.call(this),this.currentOscillators=l.Sonify.sonify(this.container.activeElement)},e.prototype.on=function(){return a.config.SONIFY},e.prototype.toggle=function(t){a.config.SONIFY=t},e.prototype.stop=function(){this.currentOscillators.forEach((function(t){return t.stop()}))},e.prototype.getActivationKey=function(){return c.KeyCodes.O},e.prototype.getRepeatKey=function(){return c.KeyCodes.P},e}(u);e.Oscillator=p;var h=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.name=a.sound.VOICING,e.languages=null,e}return r(e,t),e.prototype.update=function(){t.prototype.update.call(this);var e=new SpeechSynthesisUtterance(this.container.activeElement.getSpeech());e.lang=this.languages?this.languages.active:a.config.VOICE_LANG,speechSynthesis.speak(e)},e.prototype.activate=function(){i.hasSpeech()&&(this.languages=this.container.molecule.cmlStructure.getRootElement().getDiagram().languages,this.languages&&this.languages.updateActive(),t.prototype.activate.call(this))},e.prototype.getActivationKey=function(){return c.KeyCodes.Y},e.prototype.getRepeatKey=function(){return c.KeyCodes.U},e.prototype.on=function(){return a.config.VOICING},e.prototype.toggle=function(t){a.config.VOICING=t},e.prototype.stop=function(){i.hasSpeech()&&speechSynthesis.cancel()},e}(u);e.Voicing=h},916:function(t,e,n){var o,r=this&&this.__extends||(o=function(t,e){return o=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n])},o(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function n(){this.constructor=t}o(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)});Object.defineProperty(e,"__esModule",{value:!0}),e.Language=e.Expert=e.Detail=e.AbstractSpeech=void 0;var i=n(422),c=n(446),a=n(807),s=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return r(e,t),e.prototype.update=function(){},e}(n(530).AbstractController);e.AbstractSpeech=s;var l=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.name=a.speech.DETAIL,e}return r(e,t),e.prototype.customEvents=function(){var t=this;this.registerEvent(this.container.molecule.node,c.EventType.KEYDOWN,(function(e){return e.keyCode===c.KeyCodes.W&&(t.container.activeElement.link?(t.container.activeElement.links=!0,t.container.update(),t.container.activeElement.links=!1):((0,i.toggleExpertMode)(),t.container.update(),(0,i.toggleExpertMode)())),e.stopPropagation(),!1}))},e}(s);e.Detail=l;var u=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.name=a.speech.EXPERT,e}return r(e,t),e.prototype.customEvents=function(){var t=this;this.registerEvent(this.container.molecule.node,c.EventType.KEYDOWN,(function(e){return e.keyCode===c.KeyCodes.X&&((0,i.toggleExpertMode)(),t.container.update()),e.stopPropagation(),!1}))},e}(s);e.Expert=u;var p=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.name=a.speech.LANGUAGE,e}return r(e,t),e.prototype.customEvents=function(){var t=this;this.registerEvent(this.container.molecule.node,c.EventType.KEYDOWN,(function(e){e.keyCode===c.KeyCodes.L&&(t.container.molecule.cmlStructure.getMap().nextLanguage()&&t.container.active&&t.container.update());return e.stopPropagation(),!1}))},e}(s);e.Language=p},250:(t,e)=>{Object.defineProperty(e,"__esModule",{value:!0}),e.Bbox=void 0;var n=function(){function t(t,e,n,o){this.x=t,this.y=e,this.width=n,this.height=o}return t.fromSvgRect=function(e){return new t(e.x,e.y,e.width,e.height)},t.fromSvgViewBox=function(e){return new t(e.baseVal.x,e.baseVal.y,e.baseVal.width,e.baseVal.height)},t.combineBboxes=function(e){var n=function(t){var e=t.getBBox();if(0===e.x&&0===e.y&&0===e.width&&0===e.height&&t.childNodes&&t.childNodes[0]&&t.childNodes[0].transform){var n=t.childNodes[0],o=void 0,r=void 0,i=void 0,c=void 0;if(n.x&&n.y&&n.width&&n.height)o=n.x.baseVal.value,r=n.y.baseVal.value,i=n.width.baseVal.value,c=n.height.baseVal.value;else{var a=n.getBBox();o=a.baseVal.x,r=a.baseVal.y,i=a.baseVal.width,c=a.baseVal.height}var s=t.ownerSVGElement.createSVGPoint(),l=n.transform.baseVal;s.x=o,s.y=r;for(var u=l.length-1;u>=0;u--)s=s.matrixTransform(l.getItem(u).matrix);e.x=s.x,e.y=s.y,e.width=i,e.height=c}return e},o=t.fromSvgRect(n(e[0]));return 1===e.length?o:e.slice(1).reduce((function(t,e){return t.combine(n(e))}),o)},t.prototype.add=function(e){return new t(this.x+e.x,this.y+e.y,this.width+e.width,this.height+e.height)},t.prototype.difference=function(t,e,n){var o=this.add(t.scale(-1));return e&&(o.x=o.x-e/2,o.width=o.width+e),n&&(o.y=o.y-n/2,o.height=o.height+n),o},t.prototype.scale=function(e){return new t(this.x*e,this.y*e,this.width*e,this.height*e)},t.prototype.combine=function(e){var n=Math.min(this.x,e.x),o=Math.min(this.y,e.y);return new t(n,o,Math.max(this.x+this.width,e.x+e.width)-n,Math.max(this.y+this.height,e.y+e.height)-o)},t.prototype.viewBoxValue=function(){return this.x+" "+this.y+" "+this.width+" "+this.height},t}();e.Bbox=n},966:function(t,e,n){var o,r=this&&this.__extends||(o=function(t,e){return o=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n])},o(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function n(){this.constructor=t}o(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)}),i=this&&this.__awaiter||function(t,e,n,o){return new(n||(n=Promise))((function(r,i){function c(t){try{s(o.next(t))}catch(t){i(t)}}function a(t){try{s(o.throw(t))}catch(t){i(t)}}function s(t){var e;t.done?r(t.value):(e=t.value,e instanceof n?e:new n((function(t){t(e)}))).then(c,a)}s((o=o.apply(t,e||[])).next())}))},c=this&&this.__generator||function(t,e){var n,o,r,i,c={label:0,sent:function(){if(1&r[0])throw r[1];return r[1]},trys:[],ops:[]};return i={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function a(i){return function(a){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;c;)try{if(n=1,o&&(r=2&i[0]?o.return:i[0]?o.throw||((r=o.return)&&r.call(o),0):o.next)&&!(r=r.call(o,i[1])).done)return r;switch(o=0,r&&(i=[2&i[0],r.value]),i[0]){case 0:case 1:r=i;break;case 4:return c.label++,{value:i[1],done:!1};case 5:c.label++,o=i[1],i=[0];continue;case 7:i=c.ops.pop(),c.trys.pop();continue;default:if(!(r=c.trys,(r=r.length>0&&r[r.length-1])||6!==i[0]&&2!==i[0])){c=0;continue}if(3===i[0]&&(!r||i[1]>r[0]&&i[1]=t.length&&(t=void 0),{value:t&&t[o++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},s=this&&this.__read||function(t,e){var n="function"==typeof Symbol&&t[Symbol.iterator];if(!n)return t;var o,r,i=n.call(t),c=[];try{for(;(void 0===e||e-- >0)&&!(o=i.next()).done;)c.push(o.value)}catch(t){r={error:t}}finally{try{o&&!o.done&&(n=i.return)&&n.call(i)}finally{if(r)throw r.error}}return c};Object.defineProperty(e,"__esModule",{value:!0}),e.Container=void 0;var l=n(911),u=n(446),p=n(807),h=n(530),f=n(889),d=n(691),v=n(229),g=function(t){function e(e,n){var o=t.call(this,null)||this;return o.key=e,o.mol=n,o.name="Container",o.controllers=[],o.initElement().then((function(){o.viewBox=new v.ViewBox(o.molecule.svg),o.controllers=o.controllers.concat(f.ControllerFactory.get(o)),o.styles=l.moleculeStyle(o.molecule),o.activeElement=o.molecule.cmlStructure.getRootElement(),o.customEvents(),o.getDefaultExplorer()})).catch((function(){console.error("Failed to initialize element ".concat(e))})),o}return r(e,t),e.loadElement=function(t){return i(this,void 0,void 0,(function(){var e;return c(this,(function(n){return e=0,[2,new Promise((function(n,o){var r=function(){t.hasAttribute("has-svg")&&t.hasAttribute("has-cml")?n():e>5?o(t):(e++,setTimeout(r,100))};r()})).catch((function(t){"true"!==t.getAttribute("has-svg")&&console.error("Failed to load SVG diagram."),"true"!==t.getAttribute("has-cml")&&console.error("Failed to load XML annotation.")}))]}))}))},e.prototype.initElement=function(){return i(this,void 0,void 0,(function(){var t=this;return c(this,(function(n){return[2,e.loadElement(this.mol).then((function(){if("true"!==t.mol.getAttribute("has-svg")||"true"!==t.mol.getAttribute("has-cml"))return Promise.reject("Loading of components failed.");var e=t.mol.querySelector("div.svg").children[0],n=t.mol.querySelector("div.cml").children[0];return e&&n?(t.mol.setAttribute("tabindex","0"),t.mol.setAttribute("role","application"),e.setAttribute("aria-hidden","true"),n.setAttribute("aria-hidden","true"),t.mol.hasAttribute("aria-label")||t.mol.setAttribute("aria-label","Navigatable ".concat(n.tagName)),t.molecule=new d.Molecule(t.mol,e,n),Promise.resolve()):Promise.reject("Empty Diagram elements.")})).catch((function(t){console.error("Molecule Structure initialisation failed with:",t)}))]}))}))},e.prototype.update=function(t){t&&(this.activeElement=t),this.controllers.forEach((function(t){t.active&&t.update()}))},e.prototype.activate=function(){t.prototype.activate.call(this),this.addStyles(),this.controllers.forEach((function(t){return t.activate()}))},e.prototype.deactivate=function(){this.controllers.forEach((function(t){return t.deactivate()})),this.removeStyles(),t.prototype.deactivate.call(this)},e.prototype.addStyles=function(){var t,e;try{for(var n=a(this.styles),o=n.next();!o.done;o=n.next()){var r=s(o.value,2)[1];this.molecule.node.appendChild(r)}}catch(e){t={error:e}}finally{try{o&&!o.done&&(e=n.return)&&e.call(n)}finally{if(t)throw t.error}}},e.prototype.removeStyles=function(){var t,e;try{for(var n=a(this.styles),o=n.next();!o.done;o=n.next()){var r=s(o.value,2)[1];this.molecule.node.removeChild(r)}}catch(e){t={error:e}}finally{try{o&&!o.done&&(e=n.return)&&e.call(n)}finally{if(t)throw t.error}}},e.prototype.replaceStyles=function(t){var e,n;this.removeStyles();try{for(var o=a(t),r=o.next();!r.done;r=o.next()){var i=r.value;this.styles.set(i.className,i)}}catch(t){e={error:t}}finally{try{r&&!r.done&&(n=o.return)&&n.call(o)}finally{if(e)throw e.error}}this.addStyles()},e.prototype.customEvents=function(){var t,e=this;(null===(t=this.molecule)||void 0===t?void 0:t.node)&&(this.registerEvent(this.molecule.node,u.EventType.KEYDOWN,(function(t){return t.keyCode===u.KeyCodes.SPACE&&(e.update(),t.preventDefault(),t.stopPropagation()),!1})),new u.CustomEvent(this.molecule.node,u.EventType.KEYDOWN,(function(t){return e.defaultExplorer&&!e.active&&t.keyCode===u.KeyCodes.ENTER&&(e.defaultExplorer.active=!0,e.activate(),t.preventDefault(),t.stopPropagation()),!1})).add(),new u.CustomEvent(this.molecule.node,u.EventType.CLICK,(function(t){return e.defaultExplorer&&!e.active&&(e.defaultExplorer.active=!0,e.activate(),t.stopPropagation()),!1})).add())},e.prototype.getDefaultExplorer=function(){this.defaultExplorer=this.controllers.find((function(t){return t.name===p.explorers.KEY})),this.defaultExplorer||(this.defaultExplorer=this.controllers.find((function(t){return t.name===p.explorers.MENU})))},e}(h.AbstractController);e.Container=g},88:(t,e)=>{Object.defineProperty(e,"__esModule",{value:!0}),e.Diagram=void 0;var n=function(){function t(t){this.languages=t,this.diagram_={}}return t.prototype.set=function(t,e){this.diagram_[t]=e},t.prototype.get=function(t){return this.diagram_[t]},t.prototype.forEach=function(t){for(var e in this.diagram_)t.call(this,this.diagram_[e])},t.prototype.find=function(t){for(var e in this.diagram_){var n=this.diagram_[e];if(t.call(this,n))return n}return null},t.prototype.translate=function(t){return this.languages?this.languages.translate(t):t},t.prototype.nextLanguage=function(){return this.languages?(this.languages.nextLanguage(),this.languages.active):null},t}();e.Diagram=n},465:(t,e,n)=>{Object.defineProperty(e,"__esModule",{value:!0}),e.Language=void 0;var o=n(422),r=function(){function t(t){this.cmlDocument=t,this.messages={},this.language=this.getLanguage(),this.insertMessages()}return t.prototype.getLanguage=function(){var t=this.cmlDocument.querySelector("language");return t?t.textContent:""},t.prototype.insertMessages=function(){for(var t=this.cmlDocument.querySelectorAll("message"),e=0,n=void 0;n=t[e];e++)this.messages[(0,o.getAttribute)(n,"msg")]=n.textContent},t.prototype.translate=function(t){return this.messages[t]},t}();e.Language=r},495:function(t,e,n){var o=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,n=e&&t[e],o=0;if(n)return n.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&o>=t.length&&(t=void 0),{value:t&&t[o++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.Languages=void 0;var r=n(807),i=n(465),c=function(){function t(t){this.cmlDocument=t,this.languages={},this.insertLanguages(),this.active=this.defaultActive()}return t.createLanguages=function(e){return 0===Array.from(e.querySelectorAll("messages")).filter((function(t){return t.childNodes.length})).length?null:new t(e)},t.prototype.insertLanguages=function(){var t,e,n=this.cmlDocument.querySelectorAll("messages");try{for(var r=o(Array.from(n)),c=r.next();!c.done;c=r.next()){var a=c.value,s=a.querySelector("language");s&&(this.languages[s.textContent]=new i.Language(a))}}catch(e){t={error:e}}finally{try{c&&!c.done&&(e=r.return)&&e.call(r)}finally{if(t)throw t.error}}},t.prototype.updateActive=function(){this.active=this.languages[r.config.ACTIVE_LANG]?r.config.ACTIVE_LANG:this.defaultActive()},t.prototype.defaultActive=function(){return this.languages[r.config.VOICE_LANG]?r.config.VOICE_LANG:Object.keys(this.languages)[0]},t.prototype.translate=function(t){return this.languages[this.active].translate(t)},t.prototype.nextLanguage=function(){var t=Object.keys(this.languages),e=t.indexOf(this.active)+1;this.active=t.length>e?t[e]:t[0],r.config.ACTIVE_LANG=this.active},t}();e.Languages=c},229:function(t,e,n){var o=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,n=e&&t[e],o=0;if(n)return n.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&o>=t.length&&(t=void 0),{value:t&&t[o++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},r=this&&this.__read||function(t,e){var n="function"==typeof Symbol&&t[Symbol.iterator];if(!n)return t;var o,r,i=n.call(t),c=[];try{for(;(void 0===e||e-- >0)&&!(o=i.next()).done;)c.push(o.value)}catch(t){r={error:t}}finally{try{o&&!o.done&&(n=i.return)&&n.call(i)}finally{if(r)throw r.error}}return c};Object.defineProperty(e,"__esModule",{value:!0}),e.ViewBox=e.ZoomOptions=void 0;var i=n(422),c=n(250),a=function(){};e.ZoomOptions=a;var s=function(){function t(t){this.svg=t,this.focusgroups=new Map,this.defaultZoomOptions={xPad:this.svg.width.baseVal.value/5,yPad:this.svg.height.baseVal.value/5,duration:100,delay:10,wait:0,callback:function(){}},this.reset()}return t.prototype.reset=function(){this.setViewBox_(c.Bbox.fromSvgRect(this.svg.getBBox()))},t.prototype.setClass=function(t,e){for(var n=0;n=t.length&&(t=void 0),{value:t&&t[o++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.Atom=void 0;var c=n(422),a=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.speech="",e.links=!1,e}return r(e,t),e.prototype.getSpeech=function(){if(this.link&&this.links)return this.speakLinks();var t=[];this.neighbourhood.forEach((function(e){"external"!==e.location&&t.push(e.getSpeech())}));var e=this.getMessage();return this.speech=this.getDiagram().translate(e)+" "+t.join(" "),this.speech},e.prototype.getDrawnElements=function(){if(this.drawnElements)return this.drawnElements;this.drawnElements=[];var t=this.getDrawnElement();t&&this.drawnElements.push(t);for(var e=this.getAnnotation().querySelectorAll("component *"),n=0;n=t.length&&(t=void 0),{value:t&&t[o++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.AtomSet=void 0;var c=n(422),a=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return r(e,t),e.prototype.getDrawnElements=function(){var t,e,n=this;if(this.drawnElements)return this.drawnElements;this.drawnElements=[];var o={},r=function(t){o[t.id]||(o[t.id]=!0,n.drawnElements.push(t))},a=this.getDrawnElement();a&&r(a);try{for(var s=i(this.getDown()),l=s.next();!l.done;l=s.next()){(h=l.value).getDrawnElements().forEach(r),(a=h.getDrawnElement())&&r(a)}}catch(e){t={error:e}}finally{try{l&&!l.done&&(e=s.return)&&e.call(s)}finally{if(t)throw t.error}}for(var u=this.getAnnotation().querySelectorAll("component bond,passive"),p=0;p atom,active"),e=0,n=void 0;n=t[e];e++){var o=this.getDiagram().get((0,i.getTextContent)(n));o&&this.ends.push(o)}},e}(n(41).NeighbourHood);e.Bond=c},903:function(t,e,n){var o=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,n=e&&t[e],o=0;if(n)return n.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&o>=t.length&&(t=void 0),{value:t&&t[o++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.ChemObj=void 0;var r=n(422),i=["circular"],c=function(){function t(t,e){var n,c;this.annotation_=t,this.diagram_=e,this.drawnElements=null,this.attributes=new Map,this.up=null,this.down=[],this.link=!1,this.drawn_=null,this.id_=this.getId_(),this.parent_=this.getParent_(),this.position=this.getPosition_();try{for(var a=o(i),s=a.next();!s.done;s=a.next()){var l=s.value,u=r.getAttribute(t,l);u&&this.attributes.set(l,u)}}catch(t){n={error:t}}finally{try{s&&!s.done&&(c=a.return)&&c.call(a)}finally{if(n)throw n.error}}}return t.prototype.getAnnotation=function(){return this.annotation_},t.prototype.getDiagram=function(){return this.diagram_},t.prototype.getId=function(){return this.id_},t.prototype.setDrawnElement=function(t){this.drawn_=t},t.prototype.getDrawnElement=function(){return this.drawn_},t.prototype.getDrawnElements=function(){return this.drawnElements||(this.drawnElements=[],this.drawn_&&this.drawnElements.push(this.drawn_)),this.drawnElements},t.prototype.complete=function(){if(this.down.length&&this.down.sort((function(t,e){return!t.position&&e.position?1:t.position-e.position})),this.parent_){var t=this.getDiagram().get(this.parent_);this.up=t,t.down.push(this)}},t.prototype.getParent=function(){return this.parent_},t.prototype.getKey=function(){return this.id_+this.parent_},t.prototype.getUp=function(){return this.up?[this.up]:[]},t.prototype.getDown=function(){return this.down},t.prototype.getRight=function(){if(!this.up)return[];var t=this.up.getDown(),e=t.indexOf(this);if(t.length>e+1){var n=t[e+1];return n.position?[n]:[]}return this.up.attributes.get("circular")?[t[0]]:[]},t.prototype.getLeft=function(){if(!this.up)return[];var t=this.up.getDown(),e=t.indexOf(this);if(e>0){var n=t[e-1];return n.position?[n]:[]}return this.up.attributes.get("circular")?[t[t.length-1]]:[]},t.prototype.getId_=function(){var t=this.getAnnotation().firstElementChild;return t?t.textContent:""},t.prototype.getParent_=function(){var t=this.getAnnotation().querySelector("parents > *");return t?r.getTextContent(t):""},t.prototype.getPosition_=function(){return parseInt(r.getTextContent(this.getAnnotation().querySelector("position")),10)},t}();e.ChemObj=c},183:function(t,e,n){var o=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,n=e&&t[e],o=0;if(n)return n.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&o>=t.length&&(t=void 0),{value:t&&t[o++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.CML=void 0;var r=n(88),i=n(495),c=n(839),a=n(111),s=n(202),l=function(){function t(t){this.cmlDocument=t,this.root=null,this.map=new r.Diagram(i.Languages.createLanguages(t)),this.initMap_()}return t.prototype.getMap=function(){return this.map},t.prototype.getRootElement=function(){return this.root||(this.root=this.map.find((function(t){return!(t instanceof s.Bond||t.up)}))),this.root},t.prototype.initMap_=function(){var t,e,n=Array.from(this.cmlDocument.querySelectorAll("annotations > annotation"));try{for(var r=o(n),i=r.next();!i.done;i=r.next()){var l=i.value,u=l.firstElementChild;if(u){var p=void 0;switch(u.localName.toLowerCase()){case"atom":case"active":p=new c.Atom(l,this.map),this.map.set(p.getKey(),p);break;case"bond":case"passive":p=new s.Bond(l,this.map),this.map.set(p.getId(),p);break;case"atomset":case"grouped":p=new a.AtomSet(l,this.map),this.map.set(p.getId(),p)}}}}catch(e){t={error:e}}finally{try{i&&!i.done&&(e=r.return)&&e.call(r)}finally{if(t)throw t.error}}this.map.forEach((function(t){return t.complete()}))},t}();e.CML=l},691:(t,e,n)=>{Object.defineProperty(e,"__esModule",{value:!0}),e.Molecule=void 0;var o=n(422),r=n(183),i=function(){function t(t,e,n){this.node=t,this.svg=e,this.cml=n,this.id=o.getId(),o.classList.add(this.svg,this.id),o.classList.add(this.cml,this.id),this.cmlStructure=new r.CML(this.cml),this.topLevelDescription=this.cmlStructure.getRootElement().getSpeech(),this.combine_()}return t.prototype.createElement=function(t){var e=document.createElement(t);return e.classList.add(this.id),e},t.prototype.combine_=function(){var t=this;this.cmlStructure.getMap().forEach((function(e){var n=t.svg.getElementById(e.getId());n&&e.setDrawnElement(n)}))},t}();e.Molecule=i},171:(t,e,n)=>{Object.defineProperty(e,"__esModule",{value:!0}),e.Neighbour=void 0;var o=n(422),r=n(122),i=function(){function t(t,e){var n=this;this.from_=t,this.annotation_=e,this.speech=null,this.speech2=null;var i=o.children(this.annotation_);this.to_=this.from_.getDiagram().get(o.getTextContent(i[0])),this.location=o.getAttribute(this.annotation_,o.attr.LOCATION),this.via_=i.slice(1).map((function(t){return new r.Via(n,t)}))}return t.prototype.from=function(){return this.from_},t.prototype.to=function(){return this.to_},t.prototype.via=function(){return this.via_},t.prototype.getSpeech=function(){return this.from_.getDiagram().translate(this.getMessage())},t.prototype.getMessage=function(){if(o.EXPERT_MODE){if(this.speech2)return this.speech2;if(this.speech2=o.getAttribute(this.annotation_,o.attr.EXPERT_SPEECH),this.speech2)return this.speech2}return this.speech||(this.speech=o.getAttribute(this.annotation_,o.attr.SIMPLE_SPEECH)),this.speech},t}();e.Neighbour=i},41:function(t,e,n){var o,r=this&&this.__extends||(o=function(t,e){return o=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n])},o(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function n(){this.constructor=t}o(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)});Object.defineProperty(e,"__esModule",{value:!0}),e.NeighbourHood=void 0;var i=n(422),c=n(903),a=n(171),s=function(t){function e(e,n){var o=t.call(this,e,n)||this;return o.neighbourhood=[],o}return r(e,t),e.prototype.complete=function(){t.prototype.complete.call(this);for(var e=this.getAnnotation().querySelectorAll("neighbours neighbour"),n=0,o=void 0;o=e[n];n++)this.neighbourhood.push(new a.Neighbour(this,o))},e.prototype.getSpeech=function(){return this.getDiagram().translate(this.getMessage())},e.prototype.getMessage=function(){var t=i.getAttribute(this.getAnnotation(),i.attr.EXPERT_SPEECH),e=i.getAttribute(this.getAnnotation(),i.attr.SIMPLE_SPEECH);return i.EXPERT_MODE?t||e:e||t},e.prototype.getNeighbours=function(){return this.neighbourhood},e}(c.ChemObj);e.NeighbourHood=s},122:(t,e,n)=>{Object.defineProperty(e,"__esModule",{value:!0}),e.Via=void 0;var o=n(422),r=function(){function t(t,e){this.neighbour_=t,this.annotation_=e,this.type_=o.getAttribute(this.annotation_,o.attr.TYPE);var n=o.children(this.annotation_);this.connectedBy_=this.getConnectedBy_(n[0]),"Connecting bond"===this.type_&&"external"===this.neighbour_.location&&(this.neighbour_.from().link=!0),void 0!==this.connectedBy_&&(this.connectedBy_.link=!0),this.position_=parseInt(o.getTextContent(n[1]),10)}return t.prototype.getDrawnElements=function(){return this.connectedBy_.getDrawnElements()||[]},t.prototype.getType=function(){return this.type_||""},t.prototype.getPosition=function(){return this.position_},t.prototype.getConnectedBy_=function(t){var e=this.neighbour_.from().getDiagram(),n=o.getTextContent(t),r=e.get(n);return r||e.get(n+this.neighbour_.from().getId())},t}();e.Via=r},394:function(t,e,n){var o=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,n=e&&t[e],o=0;if(n)return n.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&o>=t.length&&(t=void 0),{value:t&&t[o++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.Sonify=void 0;var r=n(422),i=function(){function t(t){this.path=[],this.points=[],this.context=new AudioContext,this.prepare(t),this.sonifiable=this.path.length}return t.sonify=function(e){var n=e.getAnnotation().querySelectorAll("sonification > *"),o=[];n.length&&(o=Array.from(n).map((function(t){var n=(0,r.getTextContent)(t),o=t.tagName.toLowerCase(),i=n+("active"!==o&&"sre:active"!==o?"":n===e.getId()?e.getParent():e.getId());return e.getDiagram().get(i)})));var i=new t(o);return i.sonifiable?i.sonify():[]},t.prototype.prepare=function(t){var e,n;try{for(var r=o(t),i=r.next();!i.done;i=r.next()){var c=i.value;if(c.getDrawnElement()){for(var a=c.getDrawnElement().querySelectorAll("polyline"),s=0;s20?(r=n/10+.01,i=n/10+.1):(r=n/2+.01,i=n/2+.5),o.start(this.context.currentTime+r),o.stop(this.context.currentTime+i)}return e},t.prototype.sonify=function(){var e,n,o=[],r=this.context.createOscillator();o.push(r),r.type="sine",r.connect(this.context.destination);for(var i=0;i20?(e=i/10+.01,n=i/10):(e=i/2+.01,n=i/2+.5),console.log(e),i?r.frequency.setValueAtTime(this.points[i][1]?t.BASE_FREQUENCY-this.points[i][1]:0,this.context.currentTime+n):(r.frequency.setValueAtTime(t.BASE_FREQUENCY-this.points[i][1],this.context.currentTime),r.start(this.context.currentTime));return r.stop(this.context.currentTime+n),o},t.prototype.smooth=function(){if(this.points.length){for(var e=[this.points[0]],n=0;n{var t=o;Object.defineProperty(t,"__esModule",{value:!0}),t.version=t.Config=t.Base=void 0;var e=n(807),r=n(23);t.Base=r.Base,t.Config=e.config,t.version=e.version})(),o})()})); \ No newline at end of file diff --git a/slides-annotated/_static/pretext/js/edit.js b/slides-annotated/_static/pretext/js/edit.js new file mode 100755 index 0000000..1d7a66a --- /dev/null +++ b/slides-annotated/_static/pretext/js/edit.js @@ -0,0 +1,4988 @@ + +editorLog = console.log; +// editorLog = function(){}; +debugLog = function(){}; +// debugLog = console.log; +//parseLog = function(){}; +parseLog = console.log; +errorLog = console.log; + +/* the structure of each object, and its realization as PreTeXt source or in HTML, + is recorded in the objectStructure dictionary. + +{"xml:id": new_id, "sourcetag": new_tag, "parent": parent_description, "title": ""} + +In pretext, pieces are of the form ["piecename", "tag"], while +in source, pieces are of the form ["piecename", "required_component"], while + +*/ + +editing_mode = 0; + +current_page = location.host+location.pathname; +debugLog("current_page", current_page); +chosen_edit_option_key = "edit_option".concat(current_page); +chosen_edit_option = readCookie(chosen_edit_option_key) || ""; +editing_mode = chosen_edit_option; /* delete one of those variables */ +debugLog("chosen_edit_option", chosen_edit_option, "chosen_edit_option", chosen_edit_option > 0); + + +objectStructure = { + "type": { + "html": { + "tag": "span", + "attributes": ['class="type"', 'data-parent_id="<&>xml:id<;>"', 'data-editable="70XX"', 'tabindex="-1"'], + "pieces": [["(capitalize,sourcetag)", ""]] + } + }, + "type-child": { + "html": { + "tag": "span", + "attributes": ['class="type"', 'data-editable="70YY"', 'tabindex="-1"'], + "pieces": [["(capitalize,sourcetag)", ""]] + // "pieces": [["(capitalize,type-contained)", ""]] + } + }, + "type-proof": { + "html": { + "tag": "span", + "attributes": ['class="type"', 'data-editable="70YY"', 'tabindex="-1"'], + "pieces": [["(literal,Proof)", ""]] + // "pieces": [["(capitalize,type-contained)", ""]] + } + }, + "sectiontype": { + "html": { + "tag": "span", + "attributes": ['class="type"'], // the type of a section is not editable? + "pieces": [["(capitalize,sourcetag)", ""]] + } + }, + "codenumber": { + "html": { + "tag": "span", + "attributes": ['class="codenumber"'], + "pieces": [["(codenumber,)", ""]] // maybe () was a bad idea? do we need a triple? + } + }, + "period": { + "html": { + "tag": "span", + "attributes": ['class="period"'], + "pieces": [["(period,)", ""]] + } + }, + "comma": { // used in bibliography + "html": { + "tag": "span", + "attributes": ['class="comma"'], + "pieces": [["(comma,)", ""]] + } + }, + "titleperiod": { + "html": { + "tag": "span", + "attributes": ['class="period"'], + "pieces": [["(titleperiod,)", ""]] + } + }, + "space": { + "html": { + "tag": "span", + "attributes": ['class="space"'], + "pieces": [["(space,)", ""]] + } + }, + "title": { + "html": { + "tag": "span", + "attributes": ['class="title"', 'data-editable="70"', 'tabindex="-1"'], + "pieces": [["title", ""]] + } + }, + "nbsp": { + "html": { + "tag": "", + "pieces": [["(literal, )", ""]], + }, + "pretext": { + "tag": "nbsp", + "pieces": [] + }, + "source": { + "pieces": [] + } + }, + "idx": { + "html": { + "tag": "", + "pieces": [], + }, + "pretext": { + "tag": "idx", + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", ""]] + } + }, + "h": { + "html": { + "tag": "", + "pieces": [], + }, + "pretext": { + "tag": "h", + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", ""]] + } + }, + + "theorem_like_heading": { + "html": { + "tag": "h4", + "cssclass": "heading", + "attributes": ['class="<&>{cssclass}<;>"', 'data-parent_id="<&>xml:id<;>"'], + "pieces": [["{type}", ""], ["{space}", ""], ["{codenumber}", ""], ["{period}", ""], ["{space}", ""], ["{title}", ""], ["{titleperiod}", ""]] + // * means editable piece + } + }, + "proof_like_heading": { + "html": { + "tag": "h5", + "cssclass": "heading", + "attributes": ['class="<&>{cssclass}<;>"', 'xml:id="<&>xml:id<;>"'], + "pieces": [["{type-child}", ""], ["{period}", ""]] + } + }, + "proof_heading": { + "html": { + "tag": "h5", + "cssclass": "heading", + "attributes": ['class="<&>{cssclass}<;>"', 'xml:id="<&>xml:id<;>"'], + "pieces": [["{type-proof}", ""], ["{period}", ""]] + } + }, + "section_like_heading": { + "html": { + "tag": "h2", + "cssclass": "heading hide-type", + "attributes": ['class="<&>{cssclass}<;>"', 'data-parent_id="<&>xml:id<;>"'], + "pieces": [["{sectiontype}", ""], ["{codenumber}", ""], ["{space}", ""], ["{title}", ""]] + } + }, + "title_heading": { + "html": { + "tag": "h2", + "cssclass": "heading", + "attributes": ['class="<&>{cssclass}<;>"', 'data-parent_id="<&>xml:id<;>"'], + "pieces": [["{title}", ""], ["{period}", ""]] + } + }, + "task_like_heading": { + "html": { + "tag": "h6", + "cssclass": "heading", + "attributes": ['class="<&>{cssclass}<;>"', 'data-parent_id="<&>xml:id<;>"'], + "pieces": [["{codenumber}", ""], ["{space}", ""], ["{title}", ""]] + } + }, + "caption_like_heading": { + "html": { + "tag": "h5", + "cssclass": "captionheading", + "attributes": ['class="<&>{cssclass}<;>"', 'data-parent_id="<&>xml:id<;>"'], + "pieces": [["{type}", ""], ["{space}", ""], ["{codenumber}", ""], ["{period}", ""], ["{space}", ""]] + } + }, + "xref": { + "html": { + "tag": "span", + "attributes": ['class="ref tmp"', 'id="<&>xml:id<;>"', 'ref="<&>ref<;>"'], + "pieces": [["(literal,REFERENCE)", ""]], + }, + "pretext": { + "tag": "xref", + "attributes": ['ref="<&>ref<;>"', 'text="<&>text<;>"'], + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", ""]], + "attributes": [["ref", "*"], ["text", ""],["detail",""],["first",""],["last",""]] + } + }, + "introduction": { + "html": { + "tag": "section", + "attributes": ['class="introduction"', 'id="<&>xml:id<;>"', 'data-editable="XYX"', 'tabindex="-1"'], + "pieces": [["content", ""]], + }, + "pretext": { + "tag": "introduction", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["title", "title"], ["content", ""]] + }, + "source": { + "pieces": [["title", "*"], ["content", "p"]] + } + }, + "conclusion": { + "html": { + "tag": "section", + "attributes": ['class="conclusion"', 'id="<&>xml:id<;>"', 'data-editable="XYX"', 'tabindex="-1"'], + "pieces": [["{proof_like_heading}", ""], ["content", ""]], + }, + "pretext": { + "tag": "conclusion", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["title", "title"], ["content", ""]] + }, + "source": { + "pieces": [["title", "*"], ["content", "p"]] + } + }, + + "worksheet": { + "html": { + "tag": "section", + "attributes": ['class="worksheet"', 'id="<&>xml:id<;>"', 'data-editable="XYX"', 'tabindex="-1"'], + "pieces": [["{section_like_heading}", ""], ["content", ""]], + }, + "pretext": { + "tag": "worksheet", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["title", "title"], ["content", ""]] + }, + "source": { + "pieces": [["title", "*"], ["content", "p"]] + } + }, + "section": { + "html": { + "tag": "section", + "attributes": ['class="section"', 'id="<&>xml:id<;>"', 'data-editable="XYX"', 'tabindex="-1"'], + "pieces": [["{section_like_heading}", ""], ["content", ""]], + }, + "pretext": { + "tag": "section", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["title", "title"], ["content", ""]] + }, + "source": { + "pieces": [["title", "*"], ["content", "p"]] + } + }, + "subsection": { + "html": { + "tag": "section", + "attributes": ['class="subsection"', 'id="<&>xml:id<;>"', 'data-editable="XYX"', 'tabindex="-1"'], + "pieces": [["{section_like_heading}", ""], ["content", ""]], + }, + "pretext": { + "tag": "subsection", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["title", "title"], ["content", ""]] + }, + "source": { + "pieces": [["title", "*"], ["content", "p"]] + } + }, + "paragraphs": { + "html": { + "tag": "section", + "attributes": ['class="paragraphs"', 'id="<&>xml:id<;>"', 'data-editable="XYX"', 'tabindex="-1"'], + "pieces": [["{title_heading}", ""], ["content", ""]], + }, + "pretext": { + "tag": "paragraphs", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["title", "title"], ["content", ""]] + }, + "source": { + "pieces": [["title", "*"], ["content", "p"]] + } + }, + "exercises": { + "html": { + "tag": "section", + "attributes": ['class="exercises"', 'id="<&>xml:id<;>"', 'data-editable="XYX"', 'tabindex="-1"'], + "pieces": [["{section_like_heading}", ""], ["content", ""]], + }, + "pretext": { + "tag": "exercises", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["title", "title"], ["content", ""]] + }, + "source": { + "pieces": [["title", "*"], ["content", "p"]] + } + }, + "exercisegroup": { + "html": { + "tag": "section", + "attributes": ['class="exercisegroup"', 'id="<&>xml:id<;>"', 'data-editable="XYX"', 'tabindex="-1"'], + "pieces": [["{section_like_heading}", ""], ["content", ""]], + }, + "pretext": { + "tag": "exercisegroup", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["title", "title"], ["content", ""]] + }, + "source": { + "pieces": [["title", "*"], ["content", "p"]] + } + }, + + + "page": { + "html": { + "tag": "section", + "attributes": ['class="onepage"', 'id="<&>xml:id<;>"', 'data-editable="PPP"', 'tabindex="-1"'], + "pieces": [["content", ""]], + }, + "pretext": { + "tag": "page", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", "p"]] + } + }, + "objectives": { + "html": { + "tag": "article", + "cssclass": "objectives", + "pieces": [["{proof_like_heading}", ""], ["content",""]], + "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"', 'class="<&>{cssclass}<;>"'], + "data_editable": "160" + }, + "pretext": { + "tag": "objectives", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", "p"]] + } + }, + + + + "p": { + "html": { + "tag": "p", + "pieces": [["content", ""]], + "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"'], + "data_editable": "99" + }, + "pretext": { + "tag": "p", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", ""]] + } + }, +/* probably there is a better way to do it, but we have separate elements for p + that occur in + text // ip + math + text // mp + math + text // mp + math + text // fp +*/ + "ip": { + "html": { + "tag": "p", + "pieces": [["content", ""]], + "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"'], + "data_editable": "99" + }, + "pretext": { + "tag_opening": "\n\n'], + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", ""]] + } + }, + "mp": { + "html": { + "tag": "p", + "pieces": [["content", ""]], + "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"', 'class="mp"'], + "data_editable": "99" + }, + "pretext": { + "tag_opening": "", + "tag_closing": "", + "attributes": [], + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", ""]] + } + }, + "fp": { + "html": { + "tag": "p", + "pieces": [["content", ""]], + "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"', 'class="fp"'], + "data_editable": "99" + }, + "pretext": { + "tag_opening": "", + "tag_closing": "\n

    \n", + "attributes": [], + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", ""]] + } + }, + + "li": { + "html": { + "tag": "li", + "pieces": [["content", ""]], + "attributes": ['id="<&>xml:id<;>"'], + "data_editable": "98aZ" + }, + "pretext": { + "tag": "li", + "pieces": [["title", "title"], ["content", ""]] + }, + "source": { + "pieces": [["title", ""], ["content", "p"]] + } + }, + + "blockquote": { + "html": { + "tag": "blockquote", + "pieces": [["content", ""], ["attribution", ""]], + "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"', 'class="<&>{cssclass}<;>"'], + "cssclass": "blockquote", + "data_editable": "44?" + }, + "pretext": { + "tag": "blockquote", + "pieces": [["content", ""], ["attribution", "attribution"]], + }, + "source": { + "pieces": [["content", "p"], ["attribution", ""]] // attribution can contain a "line". come back to that + } + }, + + "list": { + "html": { + "tag": "ol", + "pieces": [["content", ""]], + "attributes": ['id="<&>xml:id<;>"', 'list-style-type="a"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"'], + "data_editable": "AAA" + }, + "pretext": { + "tag": "ol", + "pieces": [["content", ""]], + "attributes": ['label="A"'] + }, + "source": { + "pieces": [["title", ""], ["content", "li"]], + "attributes": [["label", "A"]] + } + }, + + "figure": { + "html": { + "tag": "figure", + "pieces": [["content",""],["{caption}", ""]], + "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"', 'class="<&>{cssclass}<;>"'], + "data_editable": "32", + "cssclass": "figure figure-like" + }, + "pretext": { + "tag": "figure", + "pieces": [["content"], ["{caption}"]], + "attributes": ['xml:id="<&>xml:id<;>"'] + }, + "source": { + "pieces": [["content", ""], ["captiontext", ""]], + "attributes": [] + } + }, + + "image": { + "html": { + "tag": "div", + "pieces": [["{bareimage}",""]], + "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"', 'class="<&>{cssclass}<;>"', 'style="width: <&>width<;>%; margin-right: <&>marginright<;>%; margin-left: <&>marginleft<;>%"'], + "data_editable": "31", + "cssclass": "image-box" + }, + "pretext": { + "tag": "image", + "pieces": [], + "attributes": ['xml:id="<&>xml:id<;>"', 'source="<&>source<;>"', 'alt="<&>alt<;>"', 'width="<&>width<;>%"', 'margins="<&>marginleft<;>% <&>marginright<;>%"'] + }, + "source": { + "pieces": [["",""]], + "attributes": [["source", ""], ["width", "40"], ["marginleft", "20"], ["marginright", "40"], ["alt", ""]] + } + }, + + "bareimage": { + "html": { + "tag": "img", + "pieces": [], + "attributes": ['src="<&>source<;>"', 'alt="<&>alt<;>"', 'class="contained"'], + } + }, + + "sidebyside": { + "html": { + "tag": "div", + "pieces": [["{sbsrow}",""]], + "attributes": ['id="<&>xml:id<;>"', 'class="<&>{cssclass}<;>"'], + "cssclass": "sidebyside" + }, + "pretext": { + "tag": "sidebyside", + "pieces": [["content", ""]], + "attributes": ['xml:id="<&>xml:id<;>"', 'margins="<&>marginleft<;>% <&>marginright<;>%"', 'width="<&>(percentlist,widths)<;>"'], + }, + "source": { + "pieces": [["content",""]], + "attributes": [["marginleft", ""], ["marginright", ""], ["widths", ""]] + } + }, + "sbsrow": { + "html": { + "tag": "div", + "pieces": [["content",""]], // ???? + "attributes": ['class="<&>{cssclass}<;>"', 'style="margin-right: <&>marginright<;>%; margin-left: <&>marginleft<;>%"'], + "data_editable": "89", + "cssclass": "sbsrow" + }, + "pretext": { + "tag": "", + "pieces": [["content",""]], + "attributes": [] + }, + "source": { + "pieces": [["content",""]], + "attributes": [["marginleft", "20"], ["marginright", "40"]] + } + }, + "sbspanel": { + "html": { + "tag": "div", + "pieces": [["content",""]], + "attributes": ['id="<&>xml:id<;>"', 'class="<&>{cssclass}<;>"', 'style="width:<&>(nthitem,widths)<;>%"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"'], + "data_editable": "90", + "cssclass": "sbspanel" + }, + "pretext": { + "tag": "stack", + "pieces": [["content", ""]], + "attributes": ['xml:id="<&>xml:id<;>"'] + }, + "source": { + "pieces": [["content",""]] + } + }, + + "proof": { + "html": { + "tag": "article", + "cssclass": "proof", + "pieces": [["{proof_heading}", ""], ["proof",""]], + "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"', 'class="<&>{cssclass}<;>"'], + "data_editable": "60" + }, + "pretext": { + "tag": "proof", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["proof", ""]] + }, + "source": { + "pieces": [["content", "p"]] + } + }, + "proof-standalone": { + "html": { + "tag": "article", + "cssclass": "proof", + "pieces": [["{XXXXXnorbeingusedXXXXXXX_proof_heading}", ""], ["content",""]], + "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"', 'class="<&>{cssclass}<;>"'], + "data_editable": "60" + }, + "pretext": { + "tag": "proof", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", "p"]] + } + }, + + "caption": { + "html": { + "tag": "figcaption", + "data_editable": "321", + // "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"'], + "attributes": ['id="<&>xml:id<;>"'], + "pieces": [["{caption_like_heading}", ""], ["{captiontext}",""]] + // "pieces": [["(literal, caption goes here)",""]] + }, + "pretext": { + "tag": "caption", + "attributes": [], + "pieces": [["captiontext", ""]] + }, + "source": { + "pieces": [["content", ""]] + } + }, + "captiontext": { /* not used */ + "html": { + "tag": "p", + "data_editable": "321", + // "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"'], +// do it like editabe title? + "attributes": ['data-source_id="<&>xml:id<;>"', 'data-component="caption"', 'class="caption"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"'], + "pieces": [["captiontext",""]] + }, + "pretext": { + "tag": "", + "attributes": [], + "pieces": [["captiontext", ""]] + }, + "source": { + "pieces": [["captiontext", ""]] + } + }, + + "references": { + "html": { + "tag": "article", + "cssclass": "bib", + "data_editable": "33", + "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"', 'class="<&>{cssclass}<;>"'], + "pieces": [["{section_like_heading}", ""], ["content",""]] + }, + "pretext": { + "tag": "references", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["title", ""], ["content",""]] + }, + "source": { + "pieces": [["title", ""], ["content",""]] + } + }, + +/* biblio, the items directly following it, and the refrences above, + are not usable at this time. references have to be handled in + a text editor. 3 Dec 2021 +*/ + "biblio": { + "html": { + "tag": "div", + "cssclass": "bibentry", + "data_editable": "333", + "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"', 'class="<&>{cssclass}<;>"'], + "pieces": [["{bib-title}", ""], ["{journal}",""], ["{volume}",""], ["{number}",""], ["{author}",""], ["{pages}",""]] + }, + "pretext": { + "tag": "biblio", + "attributes": ['xml:id="<&>xml:id<;>"', 'type="raw"'], + "pieces": [["title", ""], ["journal",""], ["volume",""], ["number",""], ["author",""], ["pages",""]] + }, + "source": { + "pieces": [["title", ""], ["journal",""], ["volume",""], ["number",""], ["author",""], ["pages",""]] + } + }, + + "bib-title": { + "html": { + "tag": "i", + "pieces": [["title", "zz"]] + } + }, + "author": { + "html": { + "tag": "", + "pieces": [["author", ""], ["{comma}", ""]] + } + }, + "journal": { + "html": { + "tag": "", + "pieces": [["journal", ""]] + } + }, + "volume": { + "html": { + "tag": "b", + "pieces": [["volume", ""]] + } + }, + "pages": { + "html": { + "tag": "jjj", + "pieces": [["pages", ""]] + } + }, + "number": { + "html": { + "tag_opening": "no. ", + "tag_closing": " ", + "pieces": [["number", ""]] + } + }, + + "remark-like": { + "html": { + "tag": "article", + "cssclass": "remark-like", + "data_editable": "92", + "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"', 'class="<&>{cssclass}<;>"'], + "pieces": [["{theorem_like_heading}", ""], ["statement",""]] + }, + "pretext": { + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["title", "title"], ["statement", "statement"]] + }, + "source": { + "pieces": [["title", ""], ["statement", "p"]] + } + }, + + "project-like-tasks": { + "html": { + "tag": "article", + "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"', 'class="<&>{cssclass}<;>"'], + "cssclass": "project-like", + "data_editable": "194", + "pieces": [["{theorem_like_heading}", ""], ["content",""], ["tasks", ""]] + }, + "pretext": { + "tag": "sourcetag", + "pieces": [["title", "title"], ["content", ""], ["tasks", ""]], + "attributes": ['xml:id="<&>xml:id<;>"'] + }, + "source": { + "pieces": [["title", ""], ["content", "p"], ["tasks", ""]] + // "attributes": [["workspace", "0"]] + } + }, + "project-like": { + "html": { + "tag": "article", + "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"', 'class="<&>{cssclass}<;>"'], + "cssclass": "project-like", + "data_editable": "94", + "pieces": [["{theorem_like_heading}", ""], ["statement",""], ["%hint%", "hint"], ["%answer%", "answer"], ["%solution%", "solution"], ["{workspace}", ""]] + }, + "pretext": { + "tag": "sourcetag", + "pieces": [["title", "title"], ["statement", "statement"], ["hint", "hint"], ["answer", "answer"], ["solution", "solution"]], + "attributes": ['xml:id="<&>xml:id<;>"', 'workspace="<&>workspace<;>"'] + }, + "source": { + "pieces": [["title", ""], ["statement", "p"], ["hint", ""], ["answer", ""], ["solution", ""]], + "attributes": [["workspace", "0"]] + } + }, + + "definition-like": { + "html": { + "tag": "article", + "cssclass": "definition-like", + "data_editable": "95a", + "pieces": [["{theorem_like_heading}", ""], ["statement", ""]], + "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"', 'class="<&>{cssclass}<;>"'] + }, + "pretext": { + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["title", "title"], ["statement", "statement"]] + }, + "source": { + "pieces": [["title", ""], ["statement", "p"]] + } + }, + + "theorem-like": { + "html": { + "tag": "article", + "cssclass": "theorem-like", + "data_editable": "93", + "pieces": [["{theorem_like_heading}", ""], ["statement", ""], ["{proof}", ""]], + "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"', 'class="<&>{cssclass}<;>"'] + }, + "pretext": { + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["title", "title"], ["statement", "statement"], ["{proof}", ""]] + }, + "source": { + "pieces": [["title", ""], ["statement", "p"], ["proof", ""]] + } + }, + + "task": { + "html": { + "tag": "article", + "cssclass": "exercise-like task", + "data_editable": "94ZZZA", + "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"', 'class="<&>{cssclass}<;>"'], + "pieces": [["{task_like_heading}", ""], ["statement",""], ["%hint%", "hint"], ["%answer%", "answer"], ["%solution%", "solution"], ["{workspace}", ""]] + }, + "pretext": { + "tag": "task", + "pieces": [["title", "title"], ["statement", "statement"], ["hint", ""], ["answer", ""], ["solution", ""]], + "attributes": ['workspace="<&>workspace<;>"'] + }, + "source": { + "pieces": [["title", ""], ["statement", "p"], ["hint", ""], ["answer", ""], ["solution", ""]], + "attributes": [["workspace", "0"]] + } + }, + +/* consolidate H/A/S */ + "hint": { + "html": { + "tag": "article", + "cssclass": "solution-like hint", + "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"', 'class="<&>{cssclass}<;>"'], + "pieces": [["{proof_like_heading}", ""], ["content",""]], +// go back and make the lack of data_editable automatically make the contained p editable +/* "data_editable": "454", */ + "data_editable": "hhhh" + }, + "pretext": { + "tag": "hint", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["title", "title"], ["content", ""]] + }, + "source": { + "pieces": [["content", "p"]] + } + }, + "answer": { + "html": { + "tag": "article", + "cssclass": "solution-like answer", + "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"', 'class="<&>{cssclass}<;>"'], + "pieces": [["{proof_like_heading}", ""], ["content",""]], +// go back and make the lack of data_editable automatically make the contained p editable +/* "data_editable": "454", */ + "data_editable": "hhhh" + }, + "pretext": { + "tag": "answer", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["title", "title"], ["content", ""]] + }, + "source": { + "pieces": [["content", "p"]] + } + }, + "solution": { + "html": { + "tag": "article", + "cssclass": "solution-like solution", + "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"', 'class="<&>{cssclass}<;>"'], + "pieces": [["{proof_like_heading}", ""], ["content",""]], +// go back and make the lack of data_editable automatically make the contained p editable +/* "data_editable": "454", */ + "data_editable": "hhhh" + }, + "pretext": { + "tag": "solution", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["title", "title"], ["content", ""]] + }, + "source": { + "pieces": [["content", "p"]] + } + }, + + "workspace": { + "html": { + "tag": "div", + "cssclass": "workspace", + "data_editable": "WwW", + "attributes": ['data-parent_id="<&>xml:id<;>"', 'data-space="<&>workspace<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"', 'class="<&>{cssclass}<;>"'], + "pieces": [] + } + }, + + "term": { // need to mark it as inline + "html": { + "tag": "dfn", + "attributes": ['id="<&>xml:id<;>"', 'class="terminology"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"'], + "data_editable": "ttt", + "pieces": [["content", ""]] + }, + "pretext": { + "tag": "term", + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", ""]] + } + }, + + "em": { // need to mark it as inline + "html": { + "tag": "em", + "attributes": ['id="<&>xml:id<;>"', 'class="emphasis"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"'], + "data_editable": "eemm", + "pieces": [["content", ""]] + }, + "pretext": { + "tag": "em", + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", ""]] + } + }, + "fn": { // need to mark it as inline + "html": { + "tag": "a", + "attributes": ['id="<&>xml:id<;>"', 'class="id-ref fn-knowl original"', 'data-knowl=" "', 'href=" "', 'data-refid="hk-<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"'], + "data_editable": "fnfnfn", +// "pieces": [["content", "sup"]] + "pieces": [["(literal,footnote)", "sup"]] + }, + "pretext": { + "tag": "fn", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", ""]] + } + }, + "alert": { // need to mark it as inline + "html": { + "tag": "em", + "attributes": ['id="<&>xml:id<;>"', 'class="alert"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"'], + "data_editable": "aall", + "pieces": [["content", ""]] + }, + "pretext": { + "tag": "alert", + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", ""]] + } + }, + "init": { // need to mark it as inline + "html": { + "tag": "abbr", + "attributes": ['id="<&>xml:id<;>"', 'class="initialism"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"'], + "data_editable": "iinniitt", + "pieces": [["content", ""]] + }, + "pretext": { + "tag": "init", + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", ""]] + } + }, + "c": { // need to mark it as inline + "html": { + "tag": "code", + "attributes": ['id="<&>xml:id<;>"', 'class="code-inline tex2jax_ignore"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"'], + "data_editable": "ccoo", + "pieces": [["content", ""]] + }, + "pretext": { + "tag": "c", + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", ""]] + } + }, + + "ellipsis": { // need to mark it as inline + "html": { + "tag": "span", + "attributes": ['id="<&>xml:id<;>"', 'class="abbrev"', 'contenteditable="false"'], + "data_editable": "abbr", + "pieces": [["(literal,…)", ""]] + }, + "pretext": { + "tag": "ellipsis", + "pieces": [] + }, + "source": { + } + }, + "etc": { // need to mark it as inline + "html": { + "tag": "span", + "attributes": ['id="<&>xml:id<;>"', 'class="abbrev"', 'contenteditable="false"'], + "pieces": [["(literal,etc)", ""]] + }, + "pretext": { + "tag": "etc", + "pieces": [] + }, + "source": { + } + }, + + "q": { // need to mark it as inline + "html": { + "tag": "q", + "attributes": ['id="<&>xml:id<;>"', 'class="quote"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"'], + "data_editable": "qqq", + "pieces": [["content", ""]] + }, + "pretext": { + "tag": "q", + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", ""]] + } + }, + + "m": { // need to mark it as inline + "html": { + "tag_opening": "\\(", + "tag_closing": "\\)", + // "tag": "script", + // "attributes": ['type="math/tex"'], + "pieces": [["content", ""]] + }, + "pretext": { + "tag": "m", + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", ""]] + } + }, + + "me": { + "html": { + "tag": "div", + "attributes": ['id="<&>xml:id<;>"', 'class="displaymath process-math"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"'], + "data_editable": "42", + "pieces": [["{me_raw}", ""]] + }, + "pretext": { + "tag": "me", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", ""]] + } + }, + "men": { + "html": { + "tag": "div", + "attributes": ['id="<&>xml:id<;>"', 'class="displaymath process-math"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"'], + "data_editable": "42", + "pieces": [["{me_raw}", ""]] + }, + "pretext": { + "tag": "men", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", ""]] + } + }, + "md": { + "html": { + "tag": "div", + "attributes": ['id="<&>xml:id<;>"', 'class="displaymath process-math"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"'], + "data_editable": "42", + "pieces": [["{md_raw}", ""]] + }, + "pretext": { + "tag": "md", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", ""]] + } + }, + "mdn": { + "html": { + "tag": "div", + "attributes": ['id="<&>xml:id<;>"', 'class="displaymath process-math"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"'], + "data_editable": "42", + "pieces": [["{me_raw}", ""]] + }, + "pretext": { + "tag": "mdn", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", ""]] + } + }, + "me_raw": { + "html": { + "tag_opening": "\\begin{equation*}", + "tag_closing": "\\end{equation*}", + "pieces": [["content", ""]] + } + }, + "md_raw": { + "html": { + "tag_opening": "\\begin{align*}", + "tag_closing": "\\end{align*}", + "pieces": [["content", ""]] + } + }, +// check if mrow is actually used + "mrow": { + "html": { + "tag_opening": "", + "tag_closing": "\\cr", + "data_editable": "422", + "pieces": [["content", ""]] + }, + "pretext": { + "tag": "mrow", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", ""]] + } + } + +} + +var environment_instances = { + "definition-like": ["definition", "conjecture", "axiom", "principle", "heuristic", "hypothesis", "assumption"], + "theorem-like": ["lemma", "proposition", "theorem", "corollary", "claim", "fact", "identity", "algorithm"], + "remark-like": ["remark", "warning", "note", "observation", "convention", "insight"], + "project-like-tasks": ["investigation", "exploration", "project"], + "project-like": ["exercise", "activity"] +} + +for (const [owner, instances] of Object.entries(environment_instances)) { + var data_editable_base = objectStructure[owner].html.data_editable; + var cssclass_base = objectStructure[owner].html.cssclass; + var source_pieces = objectStructure[owner].source.pieces; + var pretext_pieces = objectStructure[owner].pretext.pieces; + var pretext_attributes = (objectStructure[owner].pretext.attributes || []); + var source_attributes = (objectStructure[owner].source.attributes || []); + for (var j=0; j < instances.length; ++j) { + var this_tag = instances[j]; + objectStructure[this_tag] = { + "owner": owner, + "html": { + "tag": objectStructure[owner].html.tag, + "pieces": objectStructure[owner].html.pieces, + "attributes": objectStructure[owner].html.attributes, + "cssclass": cssclass_base + " " + this_tag, + "data_editable": data_editable_base + j.toString(), + "heading": objectStructure[owner].html.heading + }, + "pretext": { + "tag": this_tag, + "pieces": pretext_pieces, + "attributes": pretext_attributes + }, + "source": { + "tag": this_tag, + "pieces": source_pieces, + "attributes": source_attributes + } + } + } +} + +editorLog('objectStructure["exercise"]', objectStructure["exercise"]); + +var sidebyside_instances = { +"sbs": [["2 panels", "sbs2"], ["3 panels", "sbs3"], ["4 panels", "sbs4"]], +//"sbs2": [["full across XX", "sbs2_0_50_50_0"], ["gap but no margin", "sbs2_0_40_40_0"], ["spaced equally", "sbs2_5_40_40_5"]], +"sbs2": [["full across", "sbs_0_50_50_0"], ["gap but no margin", "sbs_0_45_45_0"], ["spaced equally", "sbs_5_40_40_5"]], +"sbs3": [["full across", "sbs_0_34_32_34_0"], ["gap but no margin", "sbs_0_28_28_38_0"], ["spaced equally", "sbs_5_25_26_25_5"]], +"sbs4": [["full across", "sbs_0_25_25_25_25_0"], ["gap but no margin", "sbs_0_20_20_20_20_0"], ["spaced equally", "sbs_3_18_18_18_18_3"]] +} + +Object.assign(objectStructure, sidebyside_instances); + +// shoudl we distinguish empty tags by format? +var always_empty_tags = ["img", "image", "xref"]; +// eventially xref will move to allowed_empty_tags +var allowed_empty_tags = ["div", "span", "p", "stack"]; +var tag_display = { /* the default is "block" */ + "inline": ["m", "em", "ellipsis", "span", "term", "dfn", "q", "c", "code", "alert", "nbsp", "xref", "idx", "h", "init"], + "title": ["title", "idx", "h1", "h2", "h3", "h4", "h5", "h6", "div", "usage", "image"], + "block-tight": [] // ["mp", "mrow"] +} + +inline_tags = tag_display["inline"]; +inline_math = ["m"]; +inline_abbrev = ["ellipsis", "ie", "eg", "etc"]; + +// contained_objects are a componnet of another object, not just buried in content +var contained_objects =["title", "statement", "caption", "captiontext", "proof", + "author", "journal", "volume", "number", "pages"]; + +function tag_type(tag) { + var this_type; + + if (["p", "ip", "mp", "fp"].includes(tag)) { this_type = "p" } + else if (["me", "men", "md", "mdn"].includes(tag)) { this_type = "md" } + else { this_type = tag } + + return this_type +} + +function process_value_from_source(fcn, piece, src) { + + editorLog(fcn, "process_value_from_source", piece, "in", src); + var content_raw = ""; + if (["literal", "codenumber", "period", "titleperiod", "space"].includes(fcn)) { + content_raw = piece; + } else { + if (piece in src) { + content_raw = src[piece] + } else { + if ("parent" in src) { + var parent_src = internalSource[src["parent"][0]]; + if (piece in parent_src) { + content_raw = parent_src[piece] + } else { + errorLog("Error: piece", piece, "fcn", fcn, "from", parent_src, "not in src or parent_src") + } + } else { + errorLog("at the top, or else there is an error") + } + } + } /* fcn != literal */ + +/* + if (typeof myVar == 'string') { + content_raw = content_raw.replace(/-standalone$/, "") // hack because we need alternate to sourcetag + } +*/ + if (fcn == "capitalize") { + content = content_raw.charAt(0).toUpperCase() + content_raw.slice(1); + } else if (fcn == "literal") { + editorLog("literally", piece); + // alert(piece); + content = piece + } else if (fcn == "space") { + content = content_raw + " " + } else if (fcn == "period") { + content = content_raw + "." + } else if (fcn == "comma") { + content = content_raw + "," + } else if (fcn == "titleperiod") { + if (src.title && !(/[!?]$/.test(src.title))) { + content = content_raw + "." + } + } else if (fcn == "percentlist") { + if (content_raw) { + content = content_raw.join("% ") + "%" + } else { + content = "MISSING" + "%" + } + } else if (fcn == "nthitem") { + + editorLog("calculating nthitem", piece, "from ", content_raw, "within", src); + var item_index = 0; // need to be its location among siblings + content = content_raw[item_index]; + } else if (fcn == "codenumber") { + editorLog(" CODENUMBER", src); + content = internalSource.root_data.number_base; + if (src["xml:id"] == top_level_id) { + // no need to add locan number + } else { + content += ".N" + } + } else { + content = content_raw + } + + return content +} + +$(".autopermalink > a").attr("tabindex", -1); + +var editable_objects = [["p", 99], ["ol", 97], ["ul", 96], ["article", 95], ["blockquote", 80], ["section", 66], + ["title", 20], ["caption", 890]]; +for(var j=0; j < editable_objects.length; ++j) { + $(editable_objects[j][0]).attr("data-editable", editable_objects[j][1]); + $(editable_objects[j][0]).attr("tabindex", -1); +} + +/* the code */ + +editorLog(" enabling edit"); + +user_level = "novice"; + +// we have to keep track of multiple consecutive carriage returns +this_char = ""; +prev_char = ""; +prev_prev_char = ""; + +// for resizing and layout changes +// clobal variables so we can adjust them dynamically +move_scale = 1; +magnify_scale = 1; + +final_added_object = ""; +previous_added_object = ""; + +// sometimes we have to prevent Tab from changing focus +this_focused_element = ""; +prev_focused_element = ""; +prev_prev_focused_element = ""; + +var menu_neutral_background = "#ddb"; +var menu_active_background = "#fdd"; + +var recent_editing_actions = []; // we unshift to this, so most recent edit is first. + // currently just a human-readable list +var ongoing_editing_actions = []; +var old_content = {}; // to hold old versions of changed materials + +// what will happen with internationalization? +var keyletters = ["KeyA", "KeyB", "KeyC", "KeyD", "KeyE", "KeyF", "KeyG", "KeyH", "KeyI", "KeyJ", "KeyK", "KeyL", "KeyM", "KeyN", "KeyO", "KeyP", "KeyQ", "KeyR", "KeyS", "KeyT", "KeyU", "KeyV", "KeyW", "KeyX", "KeyY", "KeyZ"]; + +var movement_location_options = []; +var movement_location = 0; +var first_move = true; // used when starting to move, because object no longer occupies its original location + +Storage.prototype.setObject = function(key, value) { +// this.setItem(key, JSON.stringify(value)); + this.setItem(key, JSON.stringify(value, function(key, val) { +// console.log("key", key, "value", value, "val", val); + return val.toFixed ? Number(val.toFixed(3)) : val; +})); +} + +Storage.prototype.getObject = function(key) { + var value = this.getItem(key); + return value && JSON.parse(value); +} + +function randomstring(len) { + if (!len) { len = 10 } + return "tMP" + (Math.random() + 1).toString(36).substring(2,len) +} + +function removeItemFromList(lis, value) { + var index = lis.indexOf(value); + if (index > -1) { + lis.splice(index, 1); + } + return lis; +} + +function rescale(width, max, margin_left, margin_right) { + var available_width = max - parseFloat(margin_left) - parseFloat(margin_right); + return width * 100 / available_width +} + +function spacemath_to_tex(text) { + thetext = text; + + thetext = thetext.replace(/ d([a-zA-Z])(\s|$)/, " \\,d$1$2"); + thetext = thetext.replace(/ *< */, " < "); + + return thetext +} + +function split_paragraphs(paragraph_content) { + + // does the textbox contain more than one paragraph? + var paragraph_content_list = paragraph_content.split("

    "); + editorLog("there were", paragraph_content_list.length, "paragraphs, but some may be empty"); + for (var j=0; j < paragraph_content_list.length; ++j) { + editorLog("paragraph", j, "begins", paragraph_content_list[j].substring(0,20)) + } + + var paragraph_content_list_trimmed = []; + + for (var j=0; j < paragraph_content_list.length; ++j) { + // probably each paragraph is wrapped in meaningless div tags + var this_paragraph_contents_raw = paragraph_content_list[j]; + this_paragraph_contents_raw = this_paragraph_contents_raw.replace(/<\/div>
    /g, "\n"); + this_paragraph_contents_raw = this_paragraph_contents_raw.replace(/
    /g, ""); + this_paragraph_contents_raw = this_paragraph_contents_raw.replace(/<\/div>/g, ""); + this_paragraph_contents_raw = this_paragraph_contents_raw.replace(/ /g, " "); + this_paragraph_contents_raw = this_paragraph_contents_raw.replace(/ +
    /g, "\n"); + this_paragraph_contents_raw = this_paragraph_contents_raw.replace(/
    /g, "\n"); + this_paragraph_contents_raw = this_paragraph_contents_raw.trim(); + if (!this_paragraph_contents_raw) { editorLog("empty paragraph") } + else { paragraph_content_list_trimmed.push(this_paragraph_contents_raw) } + editorLog("this_paragraph_contents_raw", this_paragraph_contents_raw); + } + + if (!paragraph_content_list_trimmed.length ) { + // empty, so insert it and delete it later + paragraph_content_list_trimmed = [""]; + } + + return paragraph_content_list_trimmed +} + +var submenu_options = { // revise as these are handled previously +"math-like": [["me"], ["chem"], ["code"]], +"image-like": [["image"], ["video"], ["audio"]], +"aside-like": [["aside"], ["historical"], ["biographical"]], +"list-like": [["itemized list", "list"], ["dictionary list", "dl"], ["table"]], +"layout-like": [["side-by-side panels", "sbs"], ["assemblage"], ["biographical aside"], ["titled paragraph", "paragraphs"]], +"section-like": ["section", "subsection", "paragraphs", "rq", "exercises"], +"sbs": [["2 panels", "sbs2"], ["3 panels", "sbs3"], ["4 panels", "sbs4"]], +"list-like": [["itemized list", "list"], ["dictionary list", "dl"], ["table"]], +"example-like": ["example", "question", "problem"] +} + +Object.assign(submenu_options, sidebyside_instances); + +Object.assign(submenu_options, environment_instances); // wrong because string not list + // or not wrong, if the code is more clever + +/* need to distinguish between the list of objects of a type, + and the list of types that can go in a location. + OR, is it okay that these are all in one list? + It seems to not be okay, because the "blockquote" entry + says that only a "p" can go in a blockquote. But blockquote + is an entry under "quoted". +*/ +base_menu_for = { +"section": [["paragraph", "p"], + ["display math/chemistry/code", "math-like", "c"], + ["list or table", "list-like"], + ["example-like", "example-like"], + ["definition-like", "definition-like"], + ["theorem-like", "theorem-like"], + ["remark-like"], + ["project/exercise-like", "project-like", "j"], + ["image/video/sound", "image-like", "v"], + ["blockquote/poem/music/etc", "quoted"], + ["aside-like", "aside-like", "d"], + ["interactives"], + ["proof", "proof-standalone", "o"], + ["layout-like"], + ["section-like"], + // ["Save", "save"], + ["PreTeXt source", "source"]], + // need to unify section with paragraphs (when sections are managed by the CAT) +"paragraphs": [["paragraph", "p"], + ["display math/chemistry/code", "math-like", "c"], + ["list or table", "list-like"], + ["example-like", "example-like"], + ["definition-like", "definition-like"], + ["theorem-like", "theorem-like"], + ["remark-like"], + ["project/exercise-like", "project-like", "j"], + ["image/video/sound", "image-like", "v"], + ["blockquote/poem/music/etc", "quoted"], + ["aside-like", "aside-like", "d"], + ["interactives"], + ["proof", "proof-standalone", "o"], + ["layout-like"], + // ["Save", "save"], + ["PreTeXt source", "source"]], +"blockquote": [["paragraph", "p"]], +// "ol": [["list item", "li"]], +"article": [["paragraph", "p"], // this is for theorem-like and similar + ["math/chemistry/code", "math-like", "c"], + ["list or table", "list-like"], + ["image/video/sound", "image-like", "v"]], +"li": [["new list item", "li", "i"], + ["paragraph", "p"], + ["list or table", "list-like"], + ["math/chemistry/code", "math-like", "c"], + ["image/video/sound", "image-like", "v"]], +"p": [["emphasis-like"], ["formula"], ["abbreviation"], ["symbol"], ["ref or link", "ref"]] +} +base_menu_for["proof"] = base_menu_for["article"]; +base_menu_for["page"] = base_menu_for["section"]; + + +// not currently used +function past_edits() { + + var the_past_edits = []; + if(recent_editing_actions.length) { + the_past_edits = recent_editing_actions.map(x => [x.join(" ")])} + else { the_past_edits = [["no chnages yet"]] } + + return the_past_edits +} + +// this should be created from inner_menu_for +editing_container_for = { "p": 1, "ip": 1, "mp": 1, "fp": 1, + "theorem-like": ["theorem", "proposition", "lemma", "corollary", "claim", "fact", "identity", "algorithm"], + "definition-like": ["definition", "conjecture", "axiom", "hypothesis", "principle", "heuristic", "assumption"], +"remark-like": ["remark", "warning", "note", "observation", "convention", "insight"], +"example-like": ["example", "question", "problem"], +"exercise-like": ["exercise"], +"ol": ["item"], +"li": [""], +"list": [""], +"image": [""], +"sbs2": [""], +"sbs3": [""], +"sbs4": [""], +"source": 1, +"proof-standalone": [""], //just a guess +"proof": [""] //just a guess +} + +function make_current_editing_tree_from_id(theid) { + +//current_editing keeps track of where we are in the tree. maybe need a better name? + + editorLog(" OOOOO make_current_editing_tree_from_id", theid); + editorLog(" which has internalSource", internalSource[theid]); + editorLog(" within", internalSource); + editorLog(" and the DOM object is", document.getElementById(theid)); + // the existing current_editing know the top level id +// var top_id = current_editing["tree"][0][0].id; + var top_id = top_level_id; + + // but now we need to start over and go bottom-up + current_editing = { + "level": -1, + "location": [], + "tree": [ ] + } + + var current_id = theid; + var current_element = document.getElementById(current_id); + console.log("current_id", current_id, "current_element", current_element); + var selectable_parent, current_element_siblings, selectable_parent_id; + var ct=0; + while (current_id != top_id && ct < 10) { + ct += 1; + editorLog("ct", ct); + editorLog("looking to match current_element", current_element, "until we hit", top_id); + selectable_parent = current_element.parentElement.closest("[data-editable]"); + current_element_siblings = next_editable_of(selectable_parent, "children"); + current_id = selectable_parent.id; + editorLog("current_id", current_id); + current_editing["tree"].unshift(current_element_siblings); + editorLog("looking for", current_element, "in", current_element_siblings); + for (var j=0; j < current_element_siblings.length; ++j) { + if (current_element == current_element_siblings[j]) { + current_editing["level"] += 1; + current_editing["location"].unshift(j); + editorLog("this is item", j); + break + } else { + editorLog(current_element == current_element_siblings[j], "aaa", current_element,"zzz", current_element_siblings[j]) + } + } + current_element = selectable_parent + } + current_editing["level"] += 1; + current_editing["location"].unshift(0); + current_editing["tree"].unshift([document.getElementById(top_id)]); + + editorLog("built current_editing after", ct, "levels"); + editorLog("current_editing[level]", current_editing["level"]); + editorLog("current_editing[location]", current_editing["location"]); + editorLog("current_editing[tree]", current_editing["tree"]) + + editorLog(" OOOOO done with make_current_editing_tree_from_id", theid, document.getElementById(theid)); + +} + +function standard_title_form(object_id) { + var the_object = internalSource[object_id]; + var the_title = the_object.title; + + var title_form = '' + the_title + ''; + + return title_form +} +function standard_caption_form(object_id) { + var the_object = internalSource[object_id]; + editorLog("editing caption of object_id", object_id, "which contains", the_object); + var the_caption = the_object.caption; + + var caption_form = '' + the_caption + ''; + + return caption_form +} + + +function menu_options_for(object_id, component_type, level) { + // this should be a function of the object, not just its tag + // p in li vs p child of section, for example + var menu_for; + + if (!component_type) { component_type = internalSource[object_id]["sourcetag"] } + editorLog("component_tag", component_type); + if (level == "base") { + menu_for = base_menu_for + } else if (level == "move-or-delete") { + editorLog("C0 menu options for", component_type); + var m_d_options; + var component_parent = internalSource[object_id]["parent"][0]; + var component_parent_tag = internalSource[component_parent]["sourcetag"]; + if (component_type == "p" && component_parent_tag == "li") { + m_d_options = [ + ["move-local-p", "Move these words within this page"], + ["move-local-li", "Move this list item within this page"], + ["move-global", "Move this another page (not implemented yet)"], + ["delete", "Delete"] // does it matter whether there are other p in this li? + ]; + } else if(tag_type(component_type) == "p") { + m_d_options = [ + ["move-local-p", "Move this text within this page"], + ["move-global", "Move to another page (not implemented yet)"], + ["delete", "Delete"] + ]; + } else { + m_d_options = [ + ["move-local", "Move within this page"], + ["move-global", "Move to another page (not implemented yet)"], + ["delete", "Delete"] + ]; + } + var this_menu = ""; + for (var i=0; i < m_d_options.length; ++i) { + this_menu += '
  • ' + this_item_shortcut + ''); + } + } else { + this_menu += 'data-jump="' + this_item_name.charAt(0) + '"'; + } + if (i==0) { this_menu += ' id="choose_current"'} + this_menu += '>'; + + if (this_item_name.match(/^[a-z]/i)) { + first_character = this_item_name.charAt(0); + this_item_name = this_item_name.replace(first_character, "" + first_character + ""); + } + + this_menu += this_item_name + // little right triangle if there is a submenu + // if (this_item_label in inner_menu_for()) { this_menu += '
    ▻
    ' } + if (this_item_label in submenu_options) { this_menu += '
    ▻
    ' } + this_menu += '
  • '; + } + + return this_menu +} + +function top_menu_options_for(this_obj) { + editorLog("top menu options for aa", this_obj); + var this_id = this_obj.id; + +// maybe the "classList" in this function shoudl instead look at the internalSource? + + var this_list = ""; + + if (this_obj.classList.contains("heading")) { + var this_obj_parent = this_obj.parentElement; + editorLog("heading options for bbb", this_obj_parent); + var this_obj_parent_id = this_obj_parent.id; + var this_obj_parent_source = internalSource[this_obj_parent_id]; + var this_obj_environment = this_obj_parent_source["sourcetag"]; + + editorLog("this_obj_environment", this_obj_environment); + + this_list = '
  • Change the title
  • '; + this_list += '
  • Change "' + this_obj_environment + '" to
    ▻
  • '; + } else { + var this_object_type = this_obj.tagName; // needs to examine other attributes and then look up a reasonable name +//consolidate this redundancy + this_obj_id = this_obj.id; + if (!this_obj_id) { + this_obj_id=this_obj.getAttribute("data-parent_id") + editorLog("now has id", this_obj_id); + } + this_obj_source = internalSource[this_obj_id]; + editorLog("this_obj_source", this_obj_source); + editorLog("this_obj", this_obj, "classList", this_obj.classList, "T/F", this_obj.classList.contains("image-box")); + this_obj_environment = this_obj_source["sourcetag"]; + if (this_object_type == "P" || this_obj.classList.contains("displaymath")) { + this_list = '
  • Edit ' + this_obj_environment + '
  • '; + var editable_children = next_editable_of(this_obj, "children"); + editorLog("editable_children", editable_children); + if (editable_children.length && !(this_object_type == "P")) { + this_list += '
  • Enter ' + this_obj_environment + '
  • '; + } + if (editable_children.length > 2 && (this_object_type == "SECTION")) { + this_list += '
  • Go to end of ' + this_obj_environment + '
  • '; + } + } else if (this_obj.classList.contains("image-box")) { + editorLog("found an image-box"); + this_list = '
  • Modify layout
    ▻
  • '; + } else if (this_obj.classList.contains("sbspanel")) { + this_list = '
  • Modify layout
    ▻
  • '; + } else { + this_list += '
  • Enter ' + this_obj_environment + '
  • '; + if (this_object_type == "SECTION") { + this_list += '
  • Go to end of ' + this_obj_environment + '
  • '; + } + } + + if (this_obj.classList.contains("sbspanel")) { + this_list += '
  • Insert in panel
    ▻
  • '; + } + + if (this_obj.classList.contains("project-like")) { + this_list += '
  • Add a task
  • '; + } + + if (this_id != top_level_id) { + this_list += '
  • Insert before
    ▻
  • '; + this_list += '
  • Insert after
    ▻
  • '; + + this_list += '
  • Move or delete
    ▻
  • '; + } + this_list += '
  • Metadata
    ▻
  • '; + if (this_id != top_level_id) { + this_list += '
  • Revert
    ▻
  • '; + } + if (previous_editing() && this_id == top_level_id) { + this_list += '
  • Resume previous editing
  • '; + } + this_list += '
  • Save
  • '; + this_list += '
  • Stop editing
  • '; + } + return this_list +} + +function edit_menu_from_current_editing(motion) { + // obviously we need to think a bit about current_editing and how it is used + var object_of_interest = current_editing["tree"][ current_editing["level"] ][ current_editing["location"][ current_editing["level"] ] ]; + edit_menu_for(object_of_interest, motion); +} + +function edit_menu_for(this_obj_or_id, motion) { + editorLog("make edit menu", motion, "for", this_obj_or_id); + + // delete the old menu, if it exists + if (document.getElementById('edit_menu_holder')) { + var current_menu = document.getElementById('edit_menu_holder'); + editorLog("current_menu", current_menu); + editorLog("this_choice", document.getElementById('enter_choice')); + if (this_choice = document.getElementById('enter_choice')) { + if (this_choice.getAttribute("data-location") == "stay") { + current_menu.previousSibling.classList.remove("may_leave") + } else { + current_menu.parentElement.classList.remove("may_select"); + current_menu.parentElement.classList.remove("may_enter"); + } + } + current_menu.parentElement.classList.remove("may_select"); + current_menu.parentElement.classList.remove("may_enter"); + current_menu.remove(); + } + + if (!this_obj_or_id) { + errorLog("error: empty this_obj_or_id", motion); + return "" + } else { + editorLog("this_obj_or_id", this_obj_or_id, "string?", typeof this_obj_or_id === 'string'); + editorLog("which has parent", this_obj_or_id.parentElement) + } + + console.log("this_obj_or_id", this_obj_or_id, typeof this_obj_or_id); + if (typeof this_obj_or_id === 'string') { + this_obj = document.getElementById(this_obj_or_id) + } else { + this_obj = this_obj_or_id + } + console.log("this_obj", this_obj); + var this_id = this_obj.id; +// this_obj = document.getElementById(this_obj); // because is is an id? Maybe need cases here + + if (motion == "entering") { + menu_location = "afterbegin"; + this_obj.classList.remove("may_leave"); + if (next_editable_of(this_obj, "children").length && !(this_obj.tagName == "P")) { + this_obj.classList.add("may_enter"); + } else { + this_obj.classList.add("may_select"); + } + if (inline_tags.includes(this_obj.tagName.toLowerCase())) { + this_obj.classList.add("inline"); + } + } else { menu_location = "afterend"; + this_obj.classList.remove("may_select"); + this_obj.classList.remove("may_enter"); + this_obj.classList.add("may_leave"); + editorLog("added may_leave to", this_obj) + } // when motion is 'leaving' + + var edit_menu_holder = document.createElement('div'); + edit_menu_holder.setAttribute('id', 'edit_menu_holder'); + edit_menu_holder.setAttribute('class', 'edit_menu_holder'); + edit_menu_holder.setAttribute('tabindex', '-1'); + editorLog("adding menu for", this_obj_or_id, "menu_location", menu_location); + editorLog("which has tag", this_obj.tagName); + editorLog("does", this_obj.classList, "include type", this_obj.classList.contains("type")); + + this_obj.insertAdjacentElement(menu_location, edit_menu_holder); + editorLog("added edit_menu_holder", document.getElementById("edit_menu_holder"), motion); + + var edit_option = document.createElement('span'); + edit_option.setAttribute('id', 'enter_choice'); + + if (motion == "entering") { + editorLog("inline_tags", inline_tags, "tag", this_obj.tagName.toLowerCase()); + editorLog("next_editable_of(this_obj, children)", next_editable_of(this_obj, "children")); + if (false && inline_tags.includes(this_obj.tagName.toLowerCase())) { + edit_option.innerHTML = "change this?"; + edit_option.setAttribute('data-location', 'inline'); + } else if (this_obj.classList.contains("type")) { + // e.g., changing "proposition" to "theorem" + // need to code this better: over-writing edit_option + edit_option = document.createElement('ol'); + edit_option.setAttribute('id', 'edit_menu'); + edit_option.setAttribute('class', 'edit_menu'); + this_obj_parent_id = this_obj.parentElement.parentElement.id; + this_obj_environment = internalSource[this_obj_parent_id]["sourcetag"]; + edit_option.innerHTML = '
  • Change "' + this_obj_environment + '" to
    ▻
  • '; + edit_option.setAttribute('data-location', 'inline'); + } else if (this_obj.classList.contains("image-box")) { + edit_option.innerHTML = "modify this image layout, or add near here?"; + } else if (this_obj.classList.contains("sbspanel")) { + edit_option.innerHTML = "modify this panel layout, or change panel contents?"; + } else if (this_obj.classList.contains("title") || this_obj.classList.contains("caption")) { + var this_contained_type = "title"; + if (this_obj.classList.contains("caption")) { this_contained_type = "caption" } + edit_option = document.createElement('ol'); + edit_option.setAttribute('id', 'edit_menu'); + edit_option.setAttribute('class', 'edit_menu'); + editorLog("this_obj", this_obj); + editorLog("this_obj.innerHTML", this_obj.innerHTML); + editorLog("menu only?", this_obj.innerHTML == '
    '); + this_obj_parent_id = this_obj.parentElement.parentElement.id; + this_obj_environment = internalSource[this_obj_parent_id]["sourcetag"]; + if (this_obj.innerHTML == '
    ') { + edit_option.innerHTML = '
  • Add a ' + this_contained_type + '
  • '; + } else { + edit_option.innerHTML = '
  • Change ' + this_contained_type + '
  • '; + } + edit_option.setAttribute('data-location', 'inline'); + } else if ((this_obj.classList.contains("placeholder") && (this_obj.classList.contains("hint") || + this_obj.classList.contains("answer") || + this_obj.classList.contains("solution") || + this_obj.classList.contains("proof")) ) ) { + var theverb = "add" + var thenoun = this_obj.getAttribute("data-HAS"); + edit_option.setAttribute('id', 'choose_current'); + edit_option.setAttribute('data-env', thenoun); + edit_option.setAttribute('data-parent_id', this_obj.getAttribute("data-parent_id")); + edit_option.innerHTML = "" + theverb + "" + " " + thenoun; + } else if (this_obj.classList.contains('workspace')) { + edit_option.setAttribute('id', 'choose_current'); + edit_option.setAttribute('data-env', 'workspace'); + edit_option.setAttribute('data-action', 'modify'); + edit_option.setAttribute('data-parent_id', this_obj.getAttribute("data-parent_id")); + + edit_option.innerHTML = "" + "adjust" + "" + " " + "workspace"; + } else { + if (next_editable_of(this_obj, "children").length && this_obj.tagName != "P") { + editorLog("this_obj", this_obj); + if (this_id == top_level_id) { + edit_option.innerHTML = "enter this " + internalSource[this_obj.id]["sourcetag"] + "?"; + } else { + edit_option.innerHTML = "enter this " + internalSource[this_obj.id]["sourcetag"] + ", or add near here?"; + } + } else { + edit_option.innerHTML = "edit this passage, or add near here?"; + } + edit_option.setAttribute('data-location', 'next'); + } + } else { + edit_option.setAttribute('data-location', 'stay'); + edit_option.innerHTML = "continue editing this " + internalSource[this_obj.id]["sourcetag"]; + } + editorLog("edit_option", edit_option); + document.getElementById("edit_menu_holder").insertAdjacentElement("afterbegin", edit_option); + document.getElementById('edit_menu_holder').focus(); +} + +function next_editable_of(obj, relationship) { + var next_to_edit; + editorLog("finding", relationship, "editable of", obj); + if (relationship == "children") { + next_to_edit = $(obj).find(' > [data-editable], > .sidebyside > .sbsrow > [data-editable], > li > [data-editable], > .heading > [data-editable], > .hint > [data-editable], > .answer > [data-editable]') + } else if (relationship == "outer-block") { // for example, a direct child of a section + next_to_edit = $(obj).find(' > [data-editable]') + } else if (relationship == "inner-block") { // typically a paragraph + next_to_edit = $(obj).find('section > [data-editable], [data-editable="99"], [data-editable="42"]') + } else if (relationship == "li-only") { // typically a paragraph + next_to_edit = $(obj).find('li') + } else { + editorLog("unimplemented next_editable_of") + } + + editorLog("next_to_edit", next_to_edit); + return next_to_edit +} + +function create_new_internal_object(new_tag, new_id, parent_description) { + + var new_source = {"xml:id": new_id, "sourcetag": new_tag, "parent": parent_description, "title": ""} + + editorLog("create new internal object", new_tag, "new_id", new_id, "parent_description", parent_description); + editorLog("within", internalSource[parent_description[0]]); + if (new_tag.startsWith("sbs")) { // creating an sbs, which contains one sbsrow, which contains several sbspanels + new_source.sourcetag = "sidebyside"; + var sbs_layout = new_tag.split("_"); + var [margin_left, margin_right] = [sbs_layout[1], sbs_layout[sbs_layout.length - 1]]; + editorLog("sbs side margins", margin_left, "jj", margin_right); + new_source.marginleft = margin_left; + new_source.marginright = margin_right; + + var col_content = ""; + var widths = []; + for (var j=2; j <= sbs_layout.length - 2; ++j) { + widths.push(sbs_layout[j]); + var new_col_id = randomstring(); + col_content += "<&>" + new_col_id + "<;>"; + internalSource[new_col_id] = {"xml:id": new_col_id, "sourcetag": "sbspanel", + "content": "", "parent": [new_id, "content"]} + } + new_source.widths = widths; + new_source.content = col_content; + + editorLog("new sbs", new_source); + + } else { + + editorLog("new_tag", new_tag); + var thisstructure = objectStructure[new_tag]; + var thisownersourcestructure = {}; + if (!thisstructure) { errorLog(new_tag + " not implemented yet"); return "" } + if ("owner" in thisstructure) { + var thisownerstructure = objectStructure[thisstructure.owner]; + thisownersourcestructure = thisownerstructure.source; + } + var thissourcestructure = Object.assign({},thisownersourcestructure, thisstructure.source); + + editorLog("thissourcestructure", thissourcestructure); + + if ("attributes" in thissourcestructure) { + these_source_attributes = thissourcestructure.attributes; + for (var j=0; j < these_source_attributes.length; ++j) { + editorLog("adding", j, "attribute", these_source_attributes[j]); + new_source[these_source_attributes[j][0]] = these_source_attributes[j][1] + } + } + +/* here need to also use the owner structure */ + var these_source_pieces = thissourcestructure.pieces; + for (var j=0; j < these_source_pieces.length; ++j) { + editorLog("adding a piece", these_source_pieces[j]); + var [this_piece, this_piece_contains] = these_source_pieces[j]; + + if (this_piece_contains) { + var new_child_id = randomstring(); + new_source[this_piece] = "<&>" + new_child_id + "<;>"; + create_new_internal_object(this_piece_contains, new_child_id, [new_id, this_piece]); + } else { + new_source[this_piece] = "" + } + } + } + + editorLog("made the new_source", new_source); + + internalSource[new_id] = new_source; + + editorLog("parent_description", parent_description, "new_tag", new_tag, "new_id", new_id); + editorLog("internalSource", internalSource); + if (new_tag == "list") { + // do nothing, because it is the child "li" which we are really creating + // chack that: maybe do add, if the stack is in the proper order + } else if (tag_type(new_tag) == "p"){ + // p is the default, so no need to keep track of it + } else if (new_tag == "source"){ + // not really a tag + } else if (new_tag.startsWith("sbs")){ + ongoing_editing_actions.push(["new", "sbs", new_id]); + } else { + ongoing_editing_actions.push(["new", new_tag, new_id]); + } + return new_tag +} + +function show_source(sibling, relative_placement) { +// var current_source = document.getElementById("newpretextsource"); +// if (current_source) { editorLog("curr sou", current_source); alert("curr sour"); current_source.remove() } + var edit_placeholder = document.createElement("span"); + edit_placeholder.setAttribute('id', "newsource"); + sibling.insertAdjacentElement(relative_placement, edit_placeholder); + + editorLog("just added", edit_placeholder); + + var the_pretext_source = output_from_id("", top_level_id, "pretext"); + + the_pretext_source = the_pretext_source.replace(/\n\n/g, '\n'); + + // remove temporary ids + the_pretext_source = the_pretext_source.replace(/ xml:id="tMP[0-9a-z]+"/g, ''); + the_pretext_source = the_pretext_source.replace(/^ +$/mg, ''); // m = multiline + +// var the_old_source = document.getElementById("newsource"); +// if (the_old_source) { the_old_source.remove() } + edit_placeholder.insertAdjacentHTML('afterend', ''); +} + +// unify with show_source, and maybe make the clean up part of output_from_id +function save_source() { +// var current_source = document.getElementById("newpretextsource"); +// if (current_source) { editorLog("curr sou", current_source); alert("curr sour"); current_source.remove() } + editorLog(" QQ saving"); + + var the_pretext_source = output_from_id("", top_level_id, "pretext"); + + the_pretext_source = the_pretext_source.replace(/\n\n/g, '\n'); + // remove temporary ids + the_pretext_source = the_pretext_source.replace(/ xml:id="tMP[0-9a-z]+"/g, ''); + the_pretext_source = the_pretext_source.replace(/^ +$/mg, ''); // m = multiline + + editorLog(" RR saving", top_level_id, "which begins", the_pretext_source.substring(0,50)); + parent.save_file(top_level_id, the_pretext_source) +} + +function create_object_to_edit(new_tag, new_objects_sibling, relative_placement) { + + // when relative_placement is "afterbegin", the new_objects_sibling is actually its parent + editorLog("create object to edit", new_tag, new_objects_sibling, relative_placement); + // first insert a placeholder to edit-in-place + var new_id = randomstring(); + recent_editing_actions.push(["new", new_tag, new_id]); + // we won;t need all of these, so re-think when these are created + var edit_placeholder = document.createElement("span"); + edit_placeholder.setAttribute('id', new_id); + + // when adding an li, you are actually focused on somethign inside an li + // but, maybe that distinction shoud be mede before calling create object to edit ? + if (new_tag == "li") { new_objects_sibling = new_objects_sibling.parentElement } + + var sibling_id, parent_description, object_neighbor; + + if (["hint", "answer", "solution", "proof"].includes(new_tag)) { // this is only for the case that a solution does not already exist +// create_new + + sibling_id = new_objects_sibling.parentElement.id; + parent_description = [sibling_id, new_tag] + + editorLog(new_tag, "parent_description", parent_description); + object_neighbor = "" ; // will not be used + + } else if(new_objects_sibling.classList.contains("sbspanel")) { //bad hack! use internalCOntents! + sibling_id = new_objects_sibling.id; + editorLog("special case for empty sbspanel",sibling_id); + // alert("special sbspanel case"); + parent_description = [sibling_id, "content"] + object_neighbor = "" ; // will not be used??? + } else { + // and describe where it goes + editorLog("new_objects_sibling",new_objects_sibling); + sibling_id = new_objects_sibling.id; + parent_description = internalSource[sibling_id]["parent"]; + object_neighbor = new RegExp('(<&>' + sibling_id + '<;>)'); + } + + if (relative_placement == "afterbegin" && new_tag != "image") { // when adding to a sbs panel + // redo the condition so that it explicitly uses sbs + // redundant with the "special case" above? + // parent_description = [new_id, "content"]; + } + var object_neighbor = new RegExp('(<&>' + sibling_id + '<;>)'); + if (new_tag == "task" && !(new_objects_sibling.classList.contains("task"))) { + // when task is being added by selecting from the parent exercise + relative_placement = "atend"; + parent_description = [sibling_id, "tasks"]; + } + if (["hint", "answer", "solution", "proof"].includes(new_tag)) { // this is only for the case that a solution does not already exist + relative_placement = "replace"; + } + editorLog(new_tag, "parent_description", parent_description); + + // then create the empty internalSource for the new object + var new_obj = create_new_internal_object(new_tag, new_id, parent_description); + if (!new_obj) { return "" } + + // we have made the new object, but we still have to put it in the correct location + + var the_current_arrangement = internalSource[parent_description[0]][parent_description[1]]; + editorLog(" the_current_arrangement", the_current_arrangement); + editorLog(" from parent_description", parent_description, "internalSource[parent_description[0]]", internalSource[parent_description[0]]); + editorLog(" current_editing", current_editing); + +// maybe the changes to current_editing is different for lists? + + var neighbor_with_new = ''; + var current_level = current_editing["level"]; + var current_location = current_editing["location"][current_level]; + editorLog(" UUU current_editing", current_editing["level"], current_editing["location"].length, current_editing["tree"].length, current_editing["tree"][current_editing["level"]]) + if (relative_placement == "beforebegin" || relative_placement == "afterbegin") { + neighbor_with_new = '<&>' + new_id + '<;>' + '$1'; + } + else if (relative_placement == "afterend" || relative_placement == "beforeend"){ + neighbor_with_new = '$1' + '<&>' + new_id + '<;>' + current_location += 1 + } + editorLog("makking new_arrangement from", object_neighbor, "by", neighbor_with_new); + + if (relative_placement == "atend" || relative_placement == "replace" ) { + // this is not quire gight, but it works for hint/answer/solution + new_arrangement = the_current_arrangement + '<&>' + new_id + '<;>'; + } else if(the_current_arrangement) { + new_arrangement = the_current_arrangement.replace(object_neighbor, neighbor_with_new); + } else { + new_arrangement = '<&>' + new_id + '<;>'; + } + editorLog("which became", new_arrangement); +// alert("here"); + internalSource[parent_description[0]][parent_description[1]] = new_arrangement; + if (new_tag == "list") { + // current_editing["level"] += 1; + // current_editing["location"].push(0); + // current_editing["tree"].push([document.getElementById(new_p_id)]) + } else { + current_editing["location"][current_level] = current_location; + } + editorLog(" new_arrangement", new_arrangement); + editorLog("tried to insert", new_id, "next to", sibling_id, "in", the_current_arrangement) + editorLog(" updated current_editing", current_editing); + editorLog(" VVV current_editing", current_editing["level"], current_editing["location"].length, current_editing["tree"].length, current_editing["tree"][current_editing["level"]]) + editorLog("relative_placement", relative_placement, "edit_placeholder", edit_placeholder); + + if (relative_placement == "atend") { + new_objects_sibling.insertAdjacentElement("beforeend", edit_placeholder); + } else if (relative_placement == "replace") { + new_objects_sibling.replaceWith(edit_placeholder); + } else { + new_objects_sibling.insertAdjacentElement(relative_placement, edit_placeholder); + } + + return edit_placeholder +} + +function edit_in_place(obj, oldornew) { + // currentlt old_or_new is onlu use as "new" for a new li, so that we know + // to immediately make a new li to edit + // previous comment probebly wrong/out of date + + var thisID; + editorLog("in edit in place", obj); + if (thisID = obj.getAttribute("id")) { + editorLog("will edit in place id", thisID, "which is", obj); + thisTagName = obj.tagName.toLowerCase(); + } else { // editing somethign without an id, so probably is a title or caption + if (obj.classList.contains("heading")) { + editorLog("changing a heading"); + editorLog("except we don;t know how to do that") + } else { + errorLog("error: I don't know how to edit", obj) + } + return "" + } + + // this only works for paragraphs, + // which may be right, because existing content is mostly titles and paragraphs + if ( internalSource[thisID] ) { + var new_tag = internalSource[thisID]["sourcetag"]; + var new_id = thisID; // track down why new_id is in the code + editorLog("new_tag is", new_tag, "from thisID", thisID, "from", internalSource[thisID]); + if (tag_type(new_tag) == "p" || tag_type(new_tag) == "md") { // make into a category? + var this_content_container = document.createElement('div'); + this_content_container.setAttribute('id', "actively_editing"); + this_content_container.setAttribute('data-age', oldornew); + editorLog("thing with thisID", thisID, "is",document.getElementById(thisID)); + $("#" + thisID).replaceWith(this_content_container); + + var idOfEditText = 'editing' + '_input_text'; + var paragraph_editable = document.createElement('div'); + paragraph_editable.setAttribute('contenteditable', 'true'); + if (tag_type(new_tag) == "md") { + paragraph_editable.setAttribute('class', 'text_source displaymath_input'); + } else { + paragraph_editable.setAttribute('class', 'text_source paragraph_input'); + } + paragraph_editable.setAttribute('id', idOfEditText); + paragraph_editable.setAttribute('data-source_id', thisID); + paragraph_editable.setAttribute('data-parent_id', internalSource[thisID]["parent"][0]); + paragraph_editable.setAttribute('data-parent_component', internalSource[thisID]["parent"][1]); + + document.getElementById('actively_editing').insertAdjacentElement("afterbegin", paragraph_editable); + + editorLog("setting", $('#' + idOfEditText), "to have contents", internalSource[thisID]["content"]); + +// from https://stackoverflow.com/questions/21257688/paste-rich-text-into-content-editable-div-and-only-keep-bold-and-italics-formatt + +// figure out better how to do this as needed. + $('[contenteditable]').on('paste',function(e) { + e.preventDefault(); + var text = (e.originalEvent || e).clipboardData.getData('text/plain') || prompt('Paste something..'); + document.execCommand('insertText', false, text); + }); + the_contents = internalSource[thisID]["content"]; + the_contents = expand_condensed_source_html(the_contents, "edit"); + the_contents = the_contents.replace(/\\cr/g, "

    "); + $('#' + idOfEditText).html(the_contents); + document.getElementById(idOfEditText).focus(); + editorLog("made edit box for", thisID); + editorLog("which is", document.getElementById(idOfEditText)); + editorLog("Whth content CC" + document.getElementById(idOfEditText).innerHTML + "DD"); + editorLog("Whth content EE" + document.getElementById(idOfEditText).innerText + "FF"); + editorLog("Whth content GG" + document.getElementById(idOfEditText).textContent + "HH"); + this_char = ""; + prev_char = ""; + + } else if (new_tag == "image") { + var this_content_container = document.createElement('div'); + this_content_container.setAttribute('id', "actively_editing"); + this_content_container.setAttribute('data-age', oldornew); + this_content_container.setAttribute('style', "width:50%; margin-left:auto; margin-right: auto; padding: 2em 3em 3em 3em; background: #fed"); + $("#" + thisID).replaceWith(this_content_container); + + var idOfEditText = 'editing' + '_input_image'; + var image_editable = document.createElement('div'); + image_editable.setAttribute('contenteditable', 'true'); + image_editable.setAttribute('class', 'image_source'); + image_editable.setAttribute('id', idOfEditText); + image_editable.setAttribute('style', "background: #fff"); + image_editable.setAttribute('data-source_id', thisID); + image_editable.setAttribute('data-parent_id', internalSource[thisID]["parent"][0]); + image_editable.setAttribute('data-parent_component', internalSource[thisID]["parent"][1]); + + document.getElementById('actively_editing').insertAdjacentElement("afterbegin", image_editable); + + var edit_instructions = document.createElement('span'); + edit_instructions.setAttribute('style', "font-size: 90%"); + edit_instructions.innerHTML = "URL of image:" + document.getElementById('actively_editing').insertAdjacentElement("afterbegin", edit_instructions); + + document.getElementById(idOfEditText).focus(); + editorLog("made edit box for", thisID); + editorLog("which is", document.getElementById(idOfEditText)); + editorLog("Whth content CC" + document.getElementById(idOfEditText).innerHTML + "DD"); + editorLog("Whth content EE" + document.getElementById(idOfEditText).innerText + "FF"); + editorLog("Whth content GG" + document.getElementById(idOfEditText).textContent + "HH"); + this_char = ""; + prev_char = ""; + + } else { + + editorLog(new_tag, "create the object, then edit p in place", obj); + + var this_object = html_from_internal_id(new_id, ""); + var where_it_goes = document.getElementById(thisID); + where_it_goes.insertAdjacentHTML('afterend', this_object[0]); + where_it_goes.remove(); + var where_it_is = document.getElementById(thisID); + + + editorLog("added this_object", this_object); + editorLog("where it is", where_it_is); + + var this_source = internalSource[thisID]; + if (this_source.sourcetag == "sidebyside") { // generalize to container was added + editorLog("added an sbs", document.getElementById(thisID)); + editorLog("its .firstChild", document.getElementById(thisID).firstElementChild); + editorLog("its .firstChild.firstchild", document.getElementById(thisID).firstElementChild.firstElementChild); + var first_panel_id = document.getElementById(thisID).firstElementChild.firstElementChild.id; + editorLog("first_panel_id", first_panel_id, document.getElementById(first_panel_id)); + make_current_editing_tree_from_id(first_panel_id); + edit_menu_from_current_editing("entering"); + } else { + var empty_p_child = $(where_it_is).find("p"); + if (empty_p_child[0]) { + editorLog("found the empty p", empty_p_child); + editorLog("found the empty p[0]", empty_p_child[0]); + edit_in_place(empty_p_child[0], "new"); + } else { + errorLog("error: no empty p to edit") + } + } + } + + } else { + editorLog("Error: edit in place of object that is not already known", obj); + editorLog("What is known:", internalSource) + } +} + +function resume_editing() { + internalSource = previous_editing(); + replace_by_id(internalSource["root_data"]["id"], "html"); + edit_menu_for(top_level_id, "entering"); + console.log("editing resumed"); +} + +function replace_by_id(theid, format) { + + if (format != "html") { return "" } + + var this_object_new = output_from_id("",theid, format); + + console.log("id", theid); +// GO BACK AND REVISIT THE NEXT 3 LINES +// document.getElementById(theid).setAttribute("id", "delete_me"); +// document.getElementById("delete_me").insertAdjacentHTML('beforebegin', this_object_new); +// document.getElementById("delete_me").remove(); + +// need to also work with MJ3 +// MathJax.Hub.Queue(['Typeset', MathJax.Hub, document.getElementById(theid)]); + console.log("MathJax on", theid, document.getElementById(theid)); +// MathJax.typesetPromise(document.getElementById(theid)); + MathJax.typesetPromise(); + + console.log("adjusting workspace"); +window.setTimeout(adjustWorkspace, 1000); +} + +// temporary: need to unify img and sbs layout +function modify_by_id(theid, modifier) { + editorLog("modifying by id", theid); + if (internalSource[theid]["sourcetag"] == "sbspanel") { + modify_by_id_sbs(theid, modifier) + } else if (environment_instances["project-like"].includes(internalSource[theid]["sourcetag"]) + || internalSource[theid]["sourcetag"] == "task") { + modify_by_id_workspace(theid, modifier) + } else { + modify_by_id_image(theid, modifier) + } + save_edits() +} + +function modify_by_id_workspace(theid, modifier) { + + var the_height = internalSource[theid]["workspace"]; + editorLog("the_height", the_height, "from", internalSource[theid]); + +//modify: enlarge, shrink, enlargeslightly, shrinkslightly, done + + the_height = parseInt(the_height); + editorLog('the_height, ', the_height); + + if (modifier == "enlarge") { the_height += 10 } + else if (modifier == "shrink") { the_height -= 10 } + else if (modifier == "enlargeslightly") { the_height += 1 } + else if (modifier == "shrinkslightly") { the_height -= 1 } + + if (the_height < 0) { the_height = 0 } + + internalSource[theid]["workspace"] = the_height; + editorLog("the_height is now", the_height); + + var this_workspace = document.getElementById(theid).querySelector(".workspace"); + this_workspace.setAttribute("style", "height:" + the_height*10 + "px"); + this_workspace.setAttribute("data-space", the_height); +} + +function modify_by_id_image(theid, modifier) { + + var width = internalSource[theid]["width"]; + var marginleft = internalSource[theid]["marginleft"]; + var marginright = internalSource[theid]["marginright"]; + editorLog('width, , marginright, , marginleft', width, "mr", marginright, "ml", marginleft); + width = parseInt(width); + marginright = parseInt(marginright); + marginleft = parseInt(marginleft); + + var scale_direction = 1; + var moving_direction = 1; + if (modifier == "shrink") { scale_direction = -1 } + else if (modifier == "left") { moving_direction = -1 } + if ("enlarge shrink".includes(modifier)) { + if ((width >= 100 && scale_direction == 1) || (width <= 0 && scale_direction == -1)) { + editorLog("can't go above 100 or below 0"); + return + } else { + width += 2*scale_direction*magnify_scale; + } + if (marginleft > 0 && marginright > 0) { + marginleft += -1*scale_direction*magnify_scale; + marginright += -1*scale_direction*magnify_scale; + } else if (marginleft > 0) { + marginleft += -2*scale_direction*magnify_scale; + } else if (marginright > 0) { + marginright += -2*scale_direction*magnify_scale; + } else if (scale_direction < 0) { // applies when we shrink a 100 width image + marginleft += -1*scale_direction*magnify_scale; + marginright += -1*scale_direction*magnify_scale; + } else { + // do nothing: this is a placeholder which is reached when both margins are 0 + editorLog("already have no margins, width is", width); + } + } else if ("left right".includes(modifier)) { + editorLog("marginleft*moving_direction", marginleft*moving_direction, "marginright*moving_direction", marginright*moving_direction); + if ((marginleft > 0 && marginright > 0) || (marginright*moving_direction > 0) || (marginleft*moving_direction < 0)) { + marginleft += moving_direction*move_scale; + marginright += -1*moving_direction*move_scale; + } else { + // do nothing: this is a placeholder which is reached when both margins are 0 + // we choose to prioritize scale, so a 100% image cannot be shifted + editorLog("already at 100%, width is", width); + } + } + + var the_new_sizes = "width: " + width + "%;"; + the_new_sizes += "margin-right: " + marginright + "%;"; + the_new_sizes += "margin-left: " + marginleft + "%;"; + + internalSource[theid]["width"] = width; + internalSource[theid]["marginleft"] = marginleft; + internalSource[theid]["marginright"] = marginright; + + document.getElementById(theid).setAttribute("style", the_new_sizes); +} + +function modify_by_id_sbs(theid, modifier) { + + var this_panel_source = internalSource[theid]; +// var this_width = this_panel_source["width"]; + var this_sbs_id = this_panel_source["parent"][0]; + var this_sbs_source = internalSource[this_sbs_id]; + editorLog("this_sbs_source", this_sbs_source); + var marginleft = parseInt(this_sbs_source["marginleft"]); + var marginright = parseInt(this_sbs_source["marginright"]); + var these_siblings = this_sbs_source["content"]; + these_siblings = these_siblings.replace(/^\s*<&>\s*/, ""); + these_siblings = these_siblings.replace(/\s*<;>\s*$/, ""); + these_siblings = these_siblings.replace(/>\s*<"); + editorLog("these_siblings", these_siblings); + var these_siblings_list = these_siblings.split("<;><&>"); + var this_panel_index = these_siblings_list.indexOf(theid); + editorLog("this panel", theid, "is", this_panel_index, "within", these_siblings_list); + var these_panel_widths = this_sbs_source.widths; + var this_width = parseInt(these_panel_widths[this_panel_index]); + var total_width = 0; + editorLog("these html siblings",document.getElementById(these_siblings_list[0])," and ", document.getElementById(these_siblings_list[1])) + editorLog("these siblings source", internalSource[these_siblings_list[0]], "and", internalSource[these_siblings_list[1]]); + for(var j=0; j < these_siblings_list.length; ++j) { + // var t_wid = parseInt(internalSource[these_siblings_list[j]]["width"]); + var t_wid = parseInt(these_panel_widths[j]); + these_panel_widths[j] = t_wid; // put it back as an integer + editorLog("adding width", t_wid); + total_width += t_wid; + // these_panel_widths.push(t_wid); + } +// if (this_width != these_panel_widths[this_panel_index]) { +// errorLog("error: width", this_width, "not on list", these_panel_widths) +// } else { +// editorLog("width", this_width, "on list", these_panel_widths) +// } + + editorLog("occ", marginleft, "u", total_width, "pi", marginright, "total", marginleft + total_width + marginright, "ratio", marginright/total_width) + var remaining_space = 100 - (marginleft + total_width + marginright); + editorLog("remaining_space", remaining_space); + +//modify: enlarge, shrink, left, right, ??? done + +// make the data structure better, then delete this comment +// currently style looks like "width: 66%; margin-right: 17%; margin-left: 17%" + editorLog('width', this_width, "mr", marginright, "ml", marginleft); + + editorLog("modifier", modifier); + + var scale_direction = 1; + var moving_direction = 1; + + if (modifier == "enlargeall") { + editorLog("enlarging all", "remaining space", remaining_space); + if (remaining_space >= these_panel_widths.length) { + for (var j=0; j < these_panel_widths.length; ++j) { + these_panel_widths[j] += 1 + } + // probablu the next case handles the first case + } else if (remaining_space + marginleft + marginright >= these_panel_widths.length) { + for (var j=0; j < these_panel_widths.length; ++j) { + these_panel_widths[j] += 1 + } + var missing_length = these_panel_widths.length - remaining_space; + while (missing_length) { + missing_length -= 1; + if (missing_length % 2) { + if (marginleft) { marginleft -= 1 } + else { marginright -= 1 } + } else { + if (marginright) { marginright -= 1 } + else { marginleft -= 1 } + } + } + } else { + editorLog("Problem: not implemented yet") + } + } else if (modifier == "shrinkall") { + for (var j=0; j < these_panel_widths.length; ++j) { + if (these_panel_widths[j]) { these_panel_widths[j] -= 1 } + } + } else if (modifier == "enlarge") { + editorLog("enlarging one"); + if (remaining_space) { these_panel_widths[this_panel_index] += 1 } + } else if (modifier == "shrink") { + editorLog("shrinking one"); + if (these_panel_widths[this_panel_index]) { these_panel_widths[this_panel_index] -= 1 } + } else if (modifier == "leftplus") { + if (remaining_space) { marginleft += 1 } + } else if (modifier == "leftminus") { + if (marginleft) { marginleft -= 1 } + } else if (modifier == "rightplus") { + if (remaining_space) { marginright += 1 } + } else if (modifier == "rightminus") { + if (marginright) { marginright -= 1 } + } + editorLog("now these_panel_widths", these_panel_widths); + +// missing cases?? + + internalSource[this_sbs_id]["marginleft"] = marginleft; + internalSource[this_sbs_id]["marginright"] = marginright; + internalSource[this_sbs_id]["widths"] = these_panel_widths; + +// next is wrong, becuase the sbsrow does not have an id +/* + document.getElementById(this_sbs_id).style.marginLeft = marginleft + "%"; + document.getElementById(this_sbs_id).style.marginRight = marginright + "%"; +*/ + document.getElementById(theid).parentElement.style.marginLeft = marginleft + "%"; + document.getElementById(theid).parentElement.style.marginRight = marginright + "%"; + + for (var j=0; j < these_siblings_list.length; ++j) { + var this_id = these_siblings_list[j]; +// internalSource[this_id]["width"] = these_panel_widths[j]; + var width = rescale(these_panel_widths[j], 100, marginleft, marginright) + // document.getElementById(this_id).style.width = these_panel_widths[j] + "%"; + document.getElementById(this_id).style.width = width + "%"; + } + editorLog("NOW these html siblings",document.getElementById(these_siblings_list[0])," and ", document.getElementById(these_siblings_list[1])) + editorLog("NOW these siblings source", internalSource[these_siblings_list[0]], "and", internalSource[these_siblings_list[1]]); + editorLog("and internalSource[this_id]", internalSource[this_id]); + editorLog("and also internalSource[this_sbs_id]", internalSource[this_sbs_id]); +} + +function move_by_id_local(theid, thehandleid) { + // when moving an object within a page, we create a phantomobject that is manipulated + // the actual movement is handled by move_object(e) + + first_move = true; + + document.getElementById("edit_menu_holder").remove() + document.getElementById(theid).classList.remove("may_select"); + + moved_content = internalSource[theid]; + moved_content_tag = moved_content["sourcetag"]; + ongoing_editing_actions.push(["moved", moved_content_tag, theid]); + moved_parent_and_location = moved_content["parent"]; + editorLog("moving", theid); + editorLog("moved_parent_and_location", moved_parent_and_location); + // code duplicated elsewhere + var where_it_was = internalSource[moved_parent_and_location[0]][ moved_parent_and_location[1] ]; + editorLog("where_it_was", where_it_was); + var object_in_parent = '<&>' + theid + '<;>'; + var where_it_is = where_it_was.replace(object_in_parent, ""); + editorLog("where_it_is ZZ" + where_it_is + "EE"); + internalSource[moved_parent_and_location[0]][ moved_parent_and_location[1] ] = where_it_is; + + // but first, remember the initial location of the object + + moving_object = document.getElementById(theid); + editorLog("moving", moving_object, "within this page"); + editorLog("moving_id", theid); + editorLog("current_editing[tree][0]", current_editing["tree"][0]); + if (moved_content_tag == "li") { + movement_location_neighbors = next_editable_of(current_editing["tree"][0][0], "li-only"); + } else if (tag_type(moved_content_tag) == "p") { + movement_location_neighbors = next_editable_of(current_editing["tree"][0][0], "inner-block"); + } else { + movement_location_neighbors = next_editable_of(current_editing["tree"][0][0], "outer-block"); + } + editorLog("movement_location_neighbors", movement_location_neighbors); + var foundit = false; + var movement_location_tmp = 0; + for (var j=0; j < movement_location_neighbors.length; ++j) { + if (movement_location_neighbors[j] == moving_object) { + movement_location_tmp = j; + // delete the one which is being moved, because + // we are making a list of slots to place it, and its slot will still be there + // isn't there a better way to delete one item from a list? + movement_location_neighbors.splice(j, 1); + foundit = true; + break; + } + } + + if (!foundit) { errorLog("serious error: trying to move an object that is not movable", theid) } + + editorLog("movement_location_tmp", movement_location_tmp); + // a paragraph by itself in an item or a statement can have a new paragraph before or after it + movement_location_options = [[movement_location_neighbors[0], "beforebegin"], + [movement_location_neighbors[0], "afterend"]]; + movement_location = 0; + var movement_location_ct = 1; + for (var j=1; j < movement_location_neighbors.length; ++j) { + if (movement_location_tmp == j) { movement_location = movement_location_ct } + movement_location_ct += 1; + if (movement_location_neighbors[j-1].parentElement == movement_location_neighbors[j].parentElement) { + movement_location_options.push([movement_location_neighbors[j], "afterend"]) + } else { + movement_location_ct += 1; + movement_location_options.push([movement_location_neighbors[j], "beforebegin"]) + movement_location_options.push([movement_location_neighbors[j], "afterend"]) + } + } + editorLog("movement_location_ct", movement_location_ct); + editorLog("movement_location", movement_location); + + editorLog("made", movement_location_options.length, "movement_location_options", movement_location_options); + editorLog("from", movement_location_neighbors.length, "movement_location_neighbors", movement_location_neighbors); + + var the_phantomobject = document.createElement('div'); + the_phantomobject.setAttribute("id", "phantomobject"); + the_phantomobject.setAttribute("data-moving_id", theid); + the_phantomobject.setAttribute("data-handle_id", thehandleid); + the_phantomobject.setAttribute("class", "phantomobject move"); + the_phantomobject.setAttribute("tabindex", "-1"); + var these_instructions = '
    ↑

    "shift-tab", or "up arrow", to move up

    '; + these_instructions += '

    "return" or "escape" to set in place

    '; + these_instructions += '
    ↓

    "tab" or "down arrow" to move down

    '; + the_phantomobject.innerHTML = these_instructions; + // if we are moving a p which has parent li, and it is the only p there, then delete the parent li + // note: this will be wrong if there is other non-p siblings inside the li + var moving_object_replace = moving_object; + if (tag_type(moved_content_tag) == "p" && internalSource[moved_parent_and_location[0]]["sourcetag"] == "li") { + // check if that p is the only thing inside the li (so the li is empty when we move the p), and if so, + // remove that li from internalSource and also the HTML, and the reverence to it in internalSource + if (moving_object.parentElement.getElementsByTagName("p").length == 1) { + moving_object_replace = moving_object.parentElement + var now_empty_li_id = moved_parent_and_location[0]; + editorLog("list item now empty:", now_empty_li_id); + var now_empty_li_parent_and_location = internalSource[now_empty_li_id]["parent"]; + var where_it_was = internalSource[now_empty_li_parent_and_location[0]][ now_empty_li_parent_and_location[1] ]; + var object_in_parent = '<&>' + now_empty_li_id + '<;>'; + var where_it_is = where_it_was.replace(object_in_parent, ""); + delete internalSource[now_empty_li_id]; + editorLog("where_it_is II" + where_it_is + "OO"); + internalSource[now_empty_li_parent_and_location[0]][ now_empty_li_parent_and_location[1] ] = where_it_is; + } + } + moving_object_replace.replaceWith(the_phantomobject) + document.getElementById("phantomobject").focus(); +} + +function move_object(e) { + // we have alread set movement_location_options and movement_location + editorLog("movement_location",movement_location); + if ((e.code == "Tab" && e.shiftKey) || e.code == "ArrowUp") { // Shift-Tab up the page + e.preventDefault(); + if (movement_location == 0) { + alert("can't move past the top") + } else { + if (first_move) { first_move = false; } + movement_location -= 1 + } + } else if ((e.code == "Tab" || e.code == "ArrowDown") && !e.shiftKey) { + e.preventDefault(); + if (first_move) { first_move = false; editorLog("did first move") } + if (movement_location == movement_location_options.length - 1) { + alert("can't move past the bottom") + } else { + movement_location += 1 + } + } else if (e.code == "Escape" || e.code == "Enter" || e.code == "ArrowRight") { + e.preventDefault(); + editorLog(" decided where to put moving_object", moving_object); + var id_of_moving_object = document.getElementById('phantomobject').getAttribute("data-moving_id"); + editorLog("moving object started as", internalSource[id_of_moving_object]); + var handle_of_moving_object = document.getElementById('phantomobject').getAttribute("data-handle_id"); + document.getElementById('phantomobject').remove(); + var new_anchor_and_position = movement_location_options[movement_location] + editorLog("new_location_anchor",new_anchor_and_position); + new_anchor_and_position[0].insertAdjacentElement(new_anchor_and_position[1], moving_object); + + // the html appears to be updated, but we still need to update both the internal source: + var new_neighbor_id = new_anchor_and_position[0].id; + editorLog("new_neighbor_id", new_neighbor_id); + editorLog("which has source", internalSource[new_neighbor_id]); + var new_neighbor_rel_pos = new_anchor_and_position[1]; + var [new_neighbor_parent, new_neighbor_location] = internalSource[new_neighbor_id]["parent"]; + var new_neighbor_in_context = internalSource[new_neighbor_parent][new_neighbor_location]; + editorLog("new_neighbor_in_context was", new_neighbor_in_context); + var neighbor_tag = '<&>' + new_neighbor_id + '<;>'; + var neighbor_tag_re = new RegExp(neighbor_tag); + + var moving_object_tag = '<&>' + id_of_moving_object + '<;>'; + if (new_neighbor_rel_pos == "beforebegin") { + // new_neighbor_in_context.replace(neighbor_tag, moving_object_tag + "\n" + neighbor_tag) +// RegExp + new_neighbor_in_context = new_neighbor_in_context.replace(neighbor_tag_re, moving_object_tag + "\n" + neighbor_tag) + } else { + new_neighbor_in_context = new_neighbor_in_context.replace(neighbor_tag_re, neighbor_tag + "\n" + moving_object_tag) + } + editorLog("new_neighbor_in_context is", new_neighbor_in_context); + internalSource[new_neighbor_parent][new_neighbor_location] = new_neighbor_in_context; + internalSource[id_of_moving_object]["parent"] = [new_neighbor_parent, new_neighbor_location]; + editorLog("moving object ended as", internalSource[id_of_moving_object]); + + save_edits(); + + // and the navigation information + make_current_editing_tree_from_id(handle_of_moving_object); + + var most_recent_edit = ongoing_editing_actions.pop(); + recent_editing_actions.unshift(most_recent_edit); + + edit_menu_from_current_editing("entering"); + return + + } else { + editorLog("don't know how to move with", e.code) + } + + editorLog("now movement_location", movement_location); + var the_phantomobject = document.getElementById('phantomobject'); + movement_location_options[movement_location][0].insertAdjacentElement(movement_location_options[movement_location][1], the_phantomobject); + document.getElementById("phantomobject").focus(); +} + +function delete_by_id(theid, thereason) { + // reasons to delete something: author wants it deleted, it is empty, ... + // first delete the specific object + final_added_object = ""; + editorLog("deleting by theid", theid, "with content", internalSource[theid]); + var deleted_content = internalSource[theid]; + var parent_and_location = deleted_content["parent"]; + delete internalSource[theid]; + editorLog("deleted", theid, "so", theid in internalSource, "now", internalSource); + // and save what was deleted + if (theid in old_content) { + old_content[theid].push(deleted_content) + } else { + old_content[theid] = [deleted_content] + } + if (thereason != "newempty") { // not sure newempty can still happen + ongoing_editing_actions.push(["deleted ", deleted_content["sourcetag"], theid]); + } + // update the parent of the object + var current_level = current_editing["level"]; + var where_it_was = internalSource[parent_and_location[0]][ parent_and_location[1] ]; + var object_in_parent = '<&>' + theid + '<;>'; + var where_it_is = where_it_was.replace(object_in_parent, ""); + editorLog("where_it_is ZZ" + where_it_is + "EE"); + internalSource[parent_and_location[0]][ parent_and_location[1] ] = where_it_is; + // if the parent is empty, delete it + if (!(where_it_is.trim()) && (parent_and_location[1] == "content" || parent_and_location[1] == "statement")) { + editorLog(" deleted from within", internalSource[parent_and_location[0]]); + document.getElementById(theid).removeAttribute("data-editable"); // so it is invisible to next-editable-of as we delete its parent + if (internalSource[parent_and_location[0]][ "sourcetag" ] == "li") { + editorLog("not going up a level, because it is a list element") + } else { + current_editing["level"] -= 1; + } + delete_by_id(parent_and_location[0], thereason) + } else { // else, because the parent is going to be deleted, so no need to delete the child + // delete from the html + var current_index = current_editing["location"][current_level] + 1; // +1 because the deleted item is still in the current_editing tree + if (thereason == "empty" || thereason == "newempty") { + editorLog("removing from DOM", document.getElementById(theid)); + document.getElementById(theid).remove(); + current_index -= 1; + editorLog("current_index", current_index, "current_level", current_level); + editorLog(current_editing["tree"][ current_level ]); + // hack because adding a list (inside a defn?) is not updating the tree properly + if (current_index > current_editing["tree"][ current_level ].length - 1) { + current_index = current_editing["tree"][ current_level ].length - 1 + } + if (current_editing["tree"][ current_level ][ current_index ].id == theid) { + current_editing["tree"][ current_level ].splice(current_index, 1) + } + editorLog("empty or newempty", thereason) + } else { + editorLog("deleting for another reason", thereason) + document.getElementById("edit_menu_holder").remove() + document.getElementById(theid).setAttribute("id", "deleting"); + document.getElementById("deleting").removeAttribute("data-editable"); // so it is invisible to next-editable-of + setTimeout(() => { document.getElementById("deleting").remove(); }, 600); + } + + if (current_index >= current_editing["tree"][ current_level ].length) { + current_index = current_editing["tree"][ current_level ].length - 2; + } + editorLog("current_index", current_index, "in", current_editing["tree"][ current_level ]); + editorLog("object of interest", current_editing["tree"][ current_level ][ current_index ]); + editorLog("current_level", current_level, "on", current_editing["tree"]); + make_current_editing_tree_from_id(current_editing["tree"][ current_level ][ current_index].id); + edit_menu_from_current_editing("entering"); + } + save_edits() +} + + +var internalSource = { // currently the key is the HTML id + "root_data": {"id": "page-1", "number_base": "0.1" } +} + +/* top_level_id is a mistake: just use internalSource.root_data.id */ +var top_level_id = internalSource["root_data"]["id"]; + +var current_editing = { + "level": 0, + "location": [0], + "tree": [ [document.getElementById(top_level_id)] ] +} + + +function create_local_menu() { + +// this does not work, but local menu navigator would not have worked either + + editorLog("make local edit menu for", this_obj_id); + var local_menu_holder = document.createElement('div'); + local_menu_holder.setAttribute('id', 'local_menu_holder'); + local_menu_holder.setAttribute('tabindex', '-1'); + editorLog("adding local menu for", this_obj_id); + document.getElementById(this_obj_id).insertAdjacentElement("afterbegin", local_menu_holder); + + var enter_option = document.createElement('ol'); + enter_option.setAttribute('id', 'edit_menu'); + enter_option.setAttribute('class', 'edit_menu'); + + enter_option.innerHTML = menu_options_for(this_obj_id, "XunusedX", "base"); + + document.getElementById("local_menu_holder").insertAdjacentElement("afterbegin", enter_option); + +} + +function extract_internal_contents(some_text) { + + // some_text must be a paragraph with mixed content only contining + // non-nested tags + the_text = some_text; + editorLog(" xxxxxxxxxx the_text is", the_text); + editorLog("extract_internal_contents"); + + // delete class information + the_text = the_text.replace(/ class="[^"]"/g, ""); + + // inline from previous editing + editorLog("extracting where 'data-editable...'"); + the_text = the_text.replace(/<([^<]+) data-editable="[^"]+" tabindex="-1">(.*?)<[^<]+>/g, save_internal_cont); + editorLog("extracting where 'contenteditable...'"); + the_text = the_text.replace(/<([^<]+) contenteditable="false">(.*?)<[^<]+>/g, save_internal_cont); + // new $math$ + editorLog("extracting new $math$"); + the_text = the_text.replace(/(^|\s)\$([^\$]+)\$(\s|$|[.,!?;:])/mg, extract_new_math); + // new \\(math\\) + editorLog("extracting new \\(math\\)"); + the_text = the_text.replace(/(^|.)\\\(([^\$]+)\\\)(.|$)/g, extract_new_math); + // new math + editorLog("extracting new math"); + the_text = the_text.replace(/(^|.)<m>(.*?)<\/m>(.|$)/g, extract_new_math); + + // "..." to , which will then be processed + the_text = the_text.replace(/\.\.\./g, '<ellipsis\/>'); + // same for etc + the_text = the_text.replace(/(\s)etc\.?([^a-zA-Z])/g, '$1<etc\/>$2'); + + for (var j=0; j < inline_abbrev.length; ++j) { + var this_tag = inline_abbrev[j]; + editorLog("this_tag", this_tag); + var this_tag_search = "<(" + this_tag + ")\\/>"; + editorLog("searching for", this_tag_search); + var this_tag_search_re = new RegExp(this_tag_search,"g"); + the_text = the_text.replace(this_tag_search_re, extract_new_inline) + } + + // "quote" to quote, which will then be processed + the_text = the_text.replace(/(^|\s)"([^"]+)"(\s|$|[.,!?;:])/g, '$1<q>$2<\/q>$3'); + the_text = the_text.replace(/(^|\s)“([^”]+)”(\s|$|[.,!?;:])/g, '$1<q>$2<\/q>$3'); + the_text = the_text.replace(/(^|\s)‘([^’]+)’(\s|$|[.,!?;:])/g, '$1<q>$2<\/q>$3'); + + for (var j=0; j < inline_tags.length; ++j) { + var this_tag = inline_tags[j]; + editorLog("this_tag", this_tag); + var this_tag_search = "<(" + this_tag + ") *>" + "(.*?)" + "<\\/" + this_tag + ">"; + editorLog("searching for", this_tag_search); + var this_tag_search_re = new RegExp(this_tag_search,"g"); + the_text = the_text.replace(this_tag_search_re, extract_new_inline) + } + + return the_text +} + +function extract_new_math(match, sp_before, math_content, sp_after) { + var new_math_id = randomstring(); + internalSource[new_math_id] = { "xml:id": new_math_id, "sourcetag": "m", + "content": math_content} + return sp_before + "<&>" + new_math_id + "<;>" + sp_after +} +function extract_new_inline(match, the_tag, the_content) { + var new_id = randomstring(); + editorLog("extracting", the_content, "inside", the_tag); + internalSource[new_id] = { "xml:id": new_id, "sourcetag": the_tag, + "content": the_content}; + return "<&>" + new_id + "<;>" +} +function extract_new_abbrev(match, the_tag) { + var new_id = randomstring(); + editorLog("extracting", the_content, "inside", the_tag); + internalSource[new_id] = { "xml:id": new_id, "sourcetag": the_tag}; + return "<&>" + new_id + "<;>" +} + +// rename this next function +function save_internal_cont(match, contains_id, the_contents) { + this_id = contains_id.replace(/.*id="(.+?)".*/, '$1'); + + editorLog("id", this_id, "now has contents", the_contents); + if ("content" in internalSource[this_id]) { // not all objects have content + internalSource[this_id]["content"] = the_contents; + } else if (internalSource[this_id]["sourcetag"] == "xref") { + // this needs work once we have text="custom" references + internalSource[this_id]["ref"] = the_contents; + } + editorLog("all of it is now", internalSource[this_id]); + return "<&>" + this_id + "<;>" +} +function assemble_internal_version_changes(object_being_edited) { + editorLog("in assemble_internal_version_changes"); + editorLog("current active element to be saved", object_being_edited); + editorLog("which has parent", object_being_edited.parentElement); + editorLog("whose age is", object_being_edited.parentElement.getAttribute("data-age")); + + var oldornew = object_being_edited.parentElement.getAttribute("data-age"); + if (!oldornew) { oldornew = object_being_edited.getAttribute("data-age") } + editorLog(" OLDorNEW", oldornew); + + var possibly_changed_ids_and_entry = []; + var nature_of_the_change = ""; + +// var object_being_edited = document.activeElement; + var location_of_change = object_being_edited.parentElement; + var this_arrangement_of_objects = ""; + + if (object_being_edited.classList.contains("paragraph_input")) { + editorLog("found paragraph_input"); + nature_of_the_change = "replace"; + var paragraph_content = object_being_edited.innerHTML; + // editorLog("paragraph_content from innerHTML", paragraph_content); + paragraph_content = paragraph_content.trim(); + + var cursor_location = object_being_edited.selectionStart; + + editorLog("cursor_location", cursor_location, "out of", paragraph_content.length, "paragraph_content", paragraph_content); + + var parent_and_location = [object_being_edited.getAttribute("data-parent_id"), object_being_edited.getAttribute("data-parent_component")]; + editorLog("parent_and_location", parent_and_location); + editorLog("of ", object_being_edited); + + var prev_id = object_being_edited.getAttribute("data-source_id"); + editorLog("prev_id", prev_id); + editorLog("which contains", internalSource[prev_id]); + + // need to replace the below by split_paragraphs + + // does the textbox contain more than one paragraph? + var paragraph_content_list = paragraph_content.split("

    "); + editorLog("there were", paragraph_content_list.length, "paragraphs, but some may be empty"); + for (var j=0; j < paragraph_content_list.length; ++j) { + editorLog("paragraph", j, "begins", paragraph_content_list[j].substring(0,20)) + } + + var paragraph_content_list_trimmed = []; + + for (var j=0; j < paragraph_content_list.length; ++j) { + // probably each paragraph is wrapped in meaningless div tags + var this_paragraph_contents_raw = paragraph_content_list[j]; + this_paragraph_contents_raw = this_paragraph_contents_raw.replace(/<\/div>
    /g, "\n"); + this_paragraph_contents_raw = this_paragraph_contents_raw.replace(/
    /g, ""); + this_paragraph_contents_raw = this_paragraph_contents_raw.replace(/<\/div>/g, ""); + this_paragraph_contents_raw = this_paragraph_contents_raw.replace(/ /g, " "); + this_paragraph_contents_raw = this_paragraph_contents_raw.replace(/ +
    /g, "\n"); + this_paragraph_contents_raw = this_paragraph_contents_raw.replace(/
    /g, "\n"); + this_paragraph_contents_raw = this_paragraph_contents_raw.trim(); + if (!this_paragraph_contents_raw) { editorLog("empty paragraph") } + else { paragraph_content_list_trimmed.push(this_paragraph_contents_raw) } + // editorLog("this_paragraph_contents_raw", this_paragraph_contents_raw); + editorLog("done transforming paragraph", j, "with object_being_edited",object_being_edited); + editorLog("which has contents", this_paragraph_contents_raw) + } + + if (!paragraph_content_list_trimmed.length ) { + // empty, so insert it and delete it later + paragraph_content_list_trimmed = [""]; + } + + for (var j=0; j < paragraph_content_list_trimmed.length; ++j) { + editorLog("_trimmed paragraph", j, "begins", paragraph_content_list_trimmed[j].substring(0,20)) + } + for (var j=0; j < paragraph_content_list_trimmed.length; ++j) { + var this_paragraph_contents = paragraph_content_list_trimmed[j]; + editorLog("this_paragraph_contents", this_paragraph_contents.substring(0,20)); + if (j == 0 && prev_id) { + if (prev_id in internalSource) { + // the content is referenced, so we update the referenced content + // need to check internal content, such as em or math + this_paragraph_contents = extract_internal_contents(this_paragraph_contents); + if (internalSource[prev_id]["content"] != this_paragraph_contents) { + if (internalSource[prev_id]["content"]) { + ongoing_editing_actions.push(["changed", "p", prev_id]); + } else if (this_paragraph_contents) { + ongoing_editing_actions.push(["new", "p", prev_id]); + } + internalSource[prev_id]["content"] = this_paragraph_contents; + editorLog("changed content of", prev_id) + } else if (!this_paragraph_contents) { // adding an empty paragraph + ongoing_editing_actions.push(["empty", "p", prev_id]); + } else { + // this means the contents are nonempty and unchanged, so don't record is as a change + } + possibly_changed_ids_and_entry.push([prev_id, "content", oldornew]); + this_arrangement_of_objects = internalSource[parent_and_location[0]][parent_and_location[1]]; + } else { + errorLog("error: existing tag from input", prev_id, "not in internalSource") + } + } else { // a newly created paragraph + var this_object_internal = {"sourcetag": "p", "title": ""}; //p don't have title + this_object_label = randomstring(); + this_object_internal["xml:id"] = this_object_label; + this_object_internal["parent"] = parent_and_location; + + // put the new p after the previous p in the string describing the neighboring contents + var object_before = new RegExp('(<&>' + prev_id + '<;>)'); + this_arrangement_of_objects = this_arrangement_of_objects.replace(object_before, '$1' + '\n<&>' + this_object_label + '<;>'); + prev_id = this_object_label; + + this_paragraph_contents = extract_internal_contents(this_paragraph_contents); + this_object_internal["content"] = this_paragraph_contents; + internalSource[this_object_label] = this_object_internal + editorLog("just inserted at label", this_object_label, "content starting", this_paragraph_contents.substring(0,11), "which is now", internalSource[this_object_label]); + ongoing_editing_actions.push(["added", "p", this_object_label]); +// here is where we can record that somethign is empty, hence should be deleted + possibly_changed_ids_and_entry.push([this_object_label, "content", "new"]); + } + } + editorLog("parent_and_location", parent_and_location); + editorLog("this_arrangement_of_objects was", internalSource[parent_and_location[0]][parent_and_location[1]]); + internalSource[parent_and_location[0]][parent_and_location[1]] = this_arrangement_of_objects; + editorLog("this_arrangement_of_objects is", this_arrangement_of_objects); + } else if (object_being_edited.classList.contains("displaymath_input")) { + editorLog("found displaymath_input"); + nature_of_the_change = "replace"; + var paragraph_content = object_being_edited.innerHTML; + // editorLog("paragraph_content from innerHTML", paragraph_content); + paragraph_content = paragraph_content.trim(); + + var cursor_location = object_being_edited.selectionStart; + + editorLog("cursor_location", cursor_location, "out of", paragraph_content.length, "paragraph_content", paragraph_content); + + var parent_and_location = [object_being_edited.getAttribute("data-parent_id"), object_being_edited.getAttribute("data-parent_component")]; + + editorLog("parent_and_location", parent_and_location); + editorLog("of ", object_being_edited); + + var prev_id = object_being_edited.getAttribute("data-source_id"); + editorLog("prev_id", prev_id); + editorLog("which contains", internalSource[prev_id]); + + // textbox may contain more than one paragraph + var paragraph_content_list = split_paragraphs(paragraph_content); + var this_paragraph_contents = paragraph_content_list.join("\n\\cr\n"); + this_paragraph_contents = extract_internal_contents(this_paragraph_contents); + + if (!internalSource[prev_id]["content"]) { + ongoing_editing_actions.push(["new", "md", prev_id]); + } else { + ongoing_editing_actions.push(["changed", "md", prev_id]); + } + + internalSource[prev_id]["content"] = this_paragraph_contents + possibly_changed_ids_and_entry.push([prev_id, "content", oldornew]); + + } else if (object_being_edited.getAttribute('data-component') == "title" || + object_being_edited.getAttribute('data-component') == "caption") { + + var this_content_type = object_being_edited.getAttribute('data-component'); + nature_of_the_change = "replace"; + var line_being_edited = object_being_edited; + var line_content = line_being_edited.innerHTML; + line_content = line_content.trim(); + editorLog("the content (is it a title or caption?) is", line_content); + var owner_of_change = object_being_edited.getAttribute("data-source_id"); + var component_being_changed = object_being_edited.getAttribute("data-component"); + editorLog("component_being_changed", component_being_changed, "within", owner_of_change); + if (internalSource[owner_of_change][component_being_changed]) { + ongoing_editing_actions.push(["changed", this_content_type, owner_of_change]); + } else { + ongoing_editing_actions.push(["added", this_content_type, owner_of_change]); + } + // update the title of the object + internalSource[owner_of_change][component_being_changed] = line_content; + possibly_changed_ids_and_entry.push([owner_of_change, this_content_type]); + + } else if (object_being_edited.classList.contains("image_source")) { + // currently this only handles images by URL. + // later do the case of uploading an image. + var image_src = object_being_edited.innerHTML; + + // what is the right way to do this? + image_src = image_src.replace(/
    /g, ""); + image_src = image_src.replace(/<\/div>/g, ""); + image_src = image_src.trim(); + editorLog("changing image src to", image_src); + + var image_being_changed = object_being_edited.getAttribute("data-source_id"); + editorLog("image_being_changed ", image_being_changed); + + if (internalSource[image_being_changed]["source"]) { + ongoing_editing_actions.push(["changed", "source", image_being_changed]); + } else { + ongoing_editing_actions.push(["added", "source", image_being_changed]); + } + internalSource[image_being_changed]["source"] = image_src; + editorLog("image being changed is", internalSource[image_being_changed]); + // possibly_changed_ids_and_entry.push([owner_of_change, "image"]); + possibly_changed_ids_and_entry.push([image_being_changed, "image"]); + + + } else if (inline_tags.includes(object_being_edited.tagName.toLowerCase())) { + editorLog(object_being_edited, "is inline, so processing parent"); + return assemble_internal_version_changes(object_being_edited.parentElement) + } else if (object_being_edited.classList.contains("edit_math_row")) { + nature_of_the_change = "replace"; + var line_being_edited = object_being_edited; + var line_content = line_being_edited.innerHTML; + line_content = line_content.trim(); + editorLog("the content (is it a title?) is", line_content); + var owner_of_change = object_being_edited.getAttribute("id"); + // var component_being_changed = object_being_edited.getAttribute("data-component"); + var component_being_changed = "content"; + editorLog("component_being_changed", component_being_changed, "within", owner_of_change); + // update the title of the object + if (internalSource[owner_of_change][component_being_changed]) { + ongoing_editing_actions.push(["changed", "mrow", owner_of_change]); + } else { + ongoing_editing_actions.push(["added", "mrow", owner_of_change]); + } + internalSource[owner_of_change][component_being_changed] = line_content; + possibly_changed_ids_and_entry.push([owner_of_change, "mrow"]); + + } else { + errorLog("trouble editing", object_being_edited, "AAA", object_being_edited.tagName.toLowerCase(), "not in", inline_tags); + alert("don;t know how to assemble internal_version_changes of " + object_being_edited.tagName) + } + editorLog("finished assembling internal version, which is now:",internalSource); + editorLog(" NUMBER of things chagnged:", possibly_changed_ids_and_entry.length); + return [nature_of_the_change, location_of_change, possibly_changed_ids_and_entry] +} + +function wrap_tag(tag, content, attribute_values) { + // tag is either an XML tag name, or [opening_tag, closing_tag] + + // layout: inline or block or title + // is this the right place to handle empty content? +// editorLog("calling wrap_tag", "tag", tag, "content", content, "attribute_values", attribute_values); + if (!content && !tag) { return "" } + if (!content && !always_empty_tags.includes(tag) && !allowed_empty_tags.includes(tag)) { return "" } + if (!tag) { return content } + + var opening_tag = closing_tag = ""; + + if (typeof tag == "string") { + if (tag) { + opening_tag = "<" + tag; + for (var j=0; j < attribute_values.length; ++j) { + opening_tag += ' ' + attribute_values[j] + } + closing_tag = ""; + } + if (!content && (always_empty_tags.includes(tag) || allowed_empty_tags.includes(tag))) { + opening_tag += "/>"; + closing_tag = ""; + } else { + opening_tag += ">"; + } + + if (tag_display["inline"].includes(tag)) { + // do nothing + } else if (tag_display["title"].includes(tag)) { + opening_tag = "\n" + opening_tag; + closing_tag = closing_tag + "\n" + } else if (tag_display["block-tight"].includes(tag)) { + // do nothing + } else { //the default + opening_tag = "\n" + opening_tag + "\n"; + if (closing_tag) { + closing_tag = "\n" + closing_tag + "\n" + } + } + } else if (tag) { + [opening_tag, closing_tag] = tag + for (var j=0; j < attribute_values.length; ++j) { + opening_tag += ' ' + attribute_values[j] + } + } else { + opening_tag = ""; + closing_tag = ""; + } + if (!opening_tag) { opening_tag = "" } + if (!closing_tag) { closing_tag = "" } + + if (content.includes("N.m")) { editorLog("3 ----- content",content); editorLog("opening_tag", opening_tag) } + + if (opening_tag.startsWith("(.*?)<;>/g, function (match, newid) { + if (newid.startsWith("{")) { + newid = newid.slice(1,-1); + if (newid in output_structure) { + return output_structure[newid] + } else { // don't want to return 'undefined' + return "" + } + } else if (newid.startsWith("(")) { + var this_piece = newid.slice(1,-1); + var this_fcn; + [this_fcn, this_piece] = this_piece.split(","); + return process_value_from_source(this_fcn, this_piece, the_object) + } else { + if (newid in the_object) { + return the_object[newid] + } else { + return "" + } + } + }); + editorLog(" attr_val", attr_val); + if (attr_val && !attr_val.includes('""')) { + output_attributes_values.push(attr_val) + } + } + + editorLog("output_structure", output_tag, "is", output_structure); + editorLog("output_attributes_values", output_attributes_values); + editorLog("output_structure.pieces", output_structure.pieces); + for (var j=0; j < output_structure.pieces.length; ++j) { + var this_piece_output = ""; + var [this_piece, this_tag] = output_structure.pieces[j]; + editorLog("output_structure", output_structure); + // when this_piece is provisional, then this_tag is actually the key for the required content + editorLog(j, "this_piece", this_piece, "this_tag", this_tag, "output_tag", output_tag); + if (this_piece.startsWith("{")) { + this_piece = this_piece.slice(1,-1); + this_piece_output += output_from_source(the_object, objectStructure[this_piece][format], format); + editorLog("wrapping in bracketed tag", this_tag); + the_answer += wrap_tag(this_tag, this_piece_output, []) + } else if (this_piece.startsWith("%")) { + // need to distinguish between the case where this object exists, + // and when it does not exist and we want a placeholder + this_piece = this_piece.slice(1,-1); + editorLog("% % % % % % % % % ", this_piece, "this_tag", this_tag, "the_object", the_object); + editorLog("% % % % % % % % % ", the_object[this_piece]); + editorLog("% % % % % % % % % ", the_object[this_tag]); + if (the_object[this_tag]) { + // var sub_object = {}; + +// Object.assign(sub_object, the_object); + + // sub_object['type-contained'] = this_tag; + // editorLog("sub_object", sub_object); + // this_piece_output = output_from_source(sub_object, objectStructure[this_piece][format], format); + this_piece_output = expand_condensed_source_html(the_object[this_tag],"html?"); + // this_piece_output = output_from_source(sub_object, objectStructure[this_piece][format], format); + the_answer += this_piece_output; + editorLog("this piece exists", this_piece, "this_piece_output", this_piece_output) + } else { + editorLog("making placeholder for", this_piece); + the_answer += wrap_tag("div", "", ['class="placeholder ' + this_piece + '"', 'data-parent_id="' + the_object['xml:id'] + '"', 'data-has="' + this_piece + '"', 'tabindex="-1"', 'data-editable="123456"', 'data-placeholder=""']) + } + } else if (this_piece.startsWith("(")) { + editorLog("whole this_piece", this_piece); + this_piece = this_piece.slice(1,-1); + var this_fcn; + [this_fcn, this_piece] = this_piece.split(","); + var this_content = process_value_from_source(this_fcn, this_piece, the_object) + editorLog(j, "parenthesized content", this_content, "this_piece", this_piece, "XX", this_tag) + the_answer += wrap_tag(this_tag, this_content, []) + } else if (this_piece in the_object) { + this_piece_output = output_from_text(the_object[this_piece], format); + if (format == "pretext" && output_tag == "md" && this_piece == "content") { + // convert \cr to mrow + this_piece_output = this_piece_output.replace(/\s*\\cr\b\s*/g, "\n\n"); + this_piece_output = "\n" + this_piece_output + "\n"; + } + the_answer += wrap_tag(this_tag, this_piece_output, []) + } else { + editorLog("missing piece:", this_tag, "with no", this_piece) + } + } + + // pretty print the output + if (format == "pretext" && !(inline_tags.includes(output_tag)) && !(inline_math.includes(output_tag))) { + the_answer = the_answer.replace(/(^|\n)( *(\w|<))/g, "$1 $2"); + } + the_answer = wrap_tag(output_tag, the_answer, output_attributes_values) +// editorLog("now the answer is", the_answer); + + return the_answer +} + +function output_from_text(text, format) { + editorLog("output_from_text of ", text, "with format", format); + if (text.includes("<&>")) { + // return text.replace(/\s*<&>(.*?)<;>\s*/g, function (match, newid) { return output_from_id(match, newid, format)}) + return text.replace(/<&>(.*?)<;>/g, function (match, newid) { return output_from_id(match, newid, format)}) + } else { + return text + } +} + +function output_from_id(match, the_id, format) { + var the_answer = ""; + editorLog("expanding the_id", the_id); + debugLog("expanding the_id", the_id); + var the_object = internalSource[the_id]; + if (!the_object) { + errorLog("error: no content for", the_id); + return the_id + } + var src_tag = the_object.sourcetag; + editorLog("the_object",the_object); + var output_structure; + if (src_tag in objectStructure) { + output_structure = objectStructure[src_tag][format]; + } else { + errorLog("error: unknown structure:", src_tag); + // so make reasonable assumptions about the structure + output_structure = { + "tag": src_tag, + "pieces": [["content", ""]] + } + } + + editorLog("output_structure", output_structure); + // var output_tag = output_structure.tag; + + the_answer = output_from_source(the_object, output_structure, format); + + return the_answer +} + +function expand_condensed_source_html(text, context) { + editorLog("iiiiiii in expand_condensed_source_html"); + if (text.includes("<&>")) { + editorLog(" qqqqq expand_condensed_source_html", text); + if (context == "edit") { + return text.replace(/<&>(.*?)<;>/g,expand_condensed_src_edit) + } else { + return text.replace(/<&>(.*?)<;>/g,expand_condensed_src_html) + } + } else { + editorLog("returning text XX" + text.substring(0,17) + "YY"); + editorLog("returning from expand_condensed_source_html"); + return text + } +} +function expand_condensed_src_html(match, the_id) { + return html_from_internal_id(the_id, "inner") +} +function expand_condensed_src_edit(match, the_id) { + return html_from_internal_id(the_id, "edit") +} + +function html_from_internal_id(the_id, is_inner) { + // the outer element needs to be constructed as document.createElement + // but the inner content is just plain text + // maybe saying it better: sometimes we want to create an object and + // insert it into the DOM. Other times we just want to construct the + // HTML markup fo rthe object and return that. + var the_object = internalSource[the_id]; + editorLog("making html of", the_object, "is_inner", is_inner, "the_id", the_id); + var sourcetag = the_object["sourcetag"]; + editorLog("which has tag", sourcetag); + + editorLog("m in inline_math", inline_math.includes("m")); + + var html_of_this_object; + var the_html_objects = []; + + if (sourcetag == "image") { + html_of_this_object = output_from_id("", the_id, "html"); + editorLog("html_of_this_object", html_of_this_object); + the_html_objects.push(html_of_this_object); + } else if (tag_type(sourcetag) == "p") { + html_of_this_object = output_from_id("", the_id, "html"); + editorLog("html_of_this_object", html_of_this_object); + the_html_objects.push(html_of_this_object); + } else if (inline_math.includes(sourcetag) && is_inner == "edit") { + // here we are assuming the tag is 'm' + var opening_tag = ''; + return opening_tag + spacemath_to_tex(the_object["content"]) + closing_tag + } else if (["me","men"].includes(sourcetag) && is_inner == "edit") { + var opening_tag = '
    '; + return opening_tag + spacemath_to_tex(the_object["content"]) + closing_tag + } else if (["md","mdn"].includes(sourcetag) && is_inner == "edit") { + var opening_tag = '
    '; + this_content = the_object["content"].replace(/<&>(.*?)<;>/g,expand_condensed_src_edit); + this_content = this_content.replace("MROWsepARATOR", "\n yyyy \n"); + return opening_tag + this_content + closing_tag + } else if (["mrow"].includes(sourcetag) && is_inner == "edit") { + var opening_tag = '
    '; + return opening_tag + "MROW" + spacemath_to_tex(the_object["content"]) + closing_tag + } else if (sourcetag == "xref" && is_inner == "edit") { + // here we are assuming the tag is 'm' + var opening_tag = ''; + return opening_tag + the_object["ref"] + closing_tag + } else { + html_of_this_object = output_from_id("", the_id, "html"); + editorLog("html_of_this_object", html_of_this_object); + the_html_objects.push(html_of_this_object); + + } + + editorLog(" RRRR returning the_html_objects", the_html_objects); + +// the_html_objects = the_html_objects.replace("MROWsepARATOR", "\n\\cr\n"); + return the_html_objects +} + +function insert_html_version(these_changes) { + + var nature_of_the_change = these_changes[0]; + var location_of_change = these_changes[1]; + var possibly_changed_ids_and_entry = these_changes[2]; + + editorLog("nature_of_the_change", nature_of_the_change); + editorLog("location_of_change", location_of_change); + editorLog("possibly_changed_ids_and_entry", possibly_changed_ids_and_entry); + + if (!possibly_changed_ids_and_entry.length) { + editorLog("nothing to change"); + // return "" + } + // we make HTML version of the objects with ids possibly_changed_ids_and_entry, + // and then insert those into the page. + +// here is where we detect deleting? +// or is that after this function is done? + if (nature_of_the_change != "replace") { + editorLog("should be replace, since it is the edit form we are replacing"); + } + + var object_as_html = ""; + var this_object_id, this_object_entry, this_object_oldornew, this_object; + + editorLog(" there are", possibly_changed_ids_and_entry.length, "items to process"); + + for (var j=0; j < possibly_changed_ids_and_entry.length; ++j) { + this_object_id = possibly_changed_ids_and_entry[j][0]; + this_object_entry = possibly_changed_ids_and_entry[j][1]; + this_object_oldornew = possibly_changed_ids_and_entry[j][2]; + editorLog("j=", j, "this thing", possibly_changed_ids_and_entry[j]); + this_object = internalSource[this_object_id]; + editorLog(j, "this_object", this_object); + if (tag_type(this_object["sourcetag"]) == "p" || this_object["sourcetag"] == "li" || tag_type(this_object["sourcetag"]) == "md") { + + var this_new_object = html_from_internal_id(this_object_id); + editorLog("inserting",this_new_object,"before",location_of_change); + location_of_change.insertAdjacentHTML('beforebegin', this_new_object[0]); + object_as_html = document.getElementById(this_object_id); + + } else if (this_object_entry == "title") { + var object_as_html = document.createElement('span'); + object_as_html.setAttribute("class", "title"); + object_as_html.setAttribute('data-editable', 20); + object_as_html.setAttribute('tabindex', -1); + // next line should apply a transform to the source + object_as_html.innerHTML = this_object[this_object_entry]; + editorLog("inserting",object_as_html,"before",location_of_change); + // location_of_change is the .header . We want it to be the .title + location_of_change = location_of_change.querySelector("#actively_editing"); + editorLog("now location_of_change",location_of_change); + location_of_change.insertAdjacentElement('beforebegin', object_as_html); + } else if (this_object_entry == "caption") { + console.log("Error: don't know what to do with 'caption'") + } else if (this_object_entry == "image") { + editorLog("image, this_object", this_object); + var this_new_object = html_from_internal_id(this_object_id); + editorLog("inserting",this_new_object,"before",location_of_change); + location_of_change.insertAdjacentHTML('beforebegin', this_new_object[0]); + object_as_html = document.getElementById(this_object_id); + + } else { + editorLog("trouble making", this_object); + } + MathJax.typesetPromise(); +// MathJax.Hub.Queue(['Typeset', MathJax.Hub, this_object_id]); + } + location_of_change.remove(); + + editorLog("returning from insert html version", object_as_html); + // call mathjax, in case the new content contains math + return object_as_html // the most recently added object, which we may want to + // do something, like add an editing menu +} // insert html version + +function save_edits() { + + var currentState = internalSource; + + editorLog("saving", currentState); + localStorage.setObject("savededits", currentState); + return ""; +} + +function previous_editing() { + var old_internal_source = localStorage.getObject("savededits"); + return (old_internal_source || "") +} + +function local_editing_action(e) { + editorLog("in local editing action for" ,e.code); + var most_recent_edit; + if (e.code == "Escape" || e.code == "Enter") { + editorLog("I saw a Rettttt"); + if (document.activeElement.getAttribute('data-component') == "title" || + document.activeElement.getAttribute('data-component') == "caption") { + editorLog("probably saving a ", document.activeElement.getAttribute('data-component')); + e.preventDefault(); + these_changes = assemble_internal_version_changes(document.activeElement); + final_added_object = insert_html_version(these_changes); + most_recent_edit = ongoing_editing_actions.pop(); + recent_editing_actions.unshift(most_recent_edit); + editorLog("most_recent_edit should be title change", most_recent_edit); + editorLog("final_added_object", final_added_object); + this_char = ""; + prev_char = ""; + save_edits(); + + // .title is in a .heading, and neither have an id + make_current_editing_tree_from_id(final_added_object.parentElement.parentElement.id); + edit_menu_from_current_editing("entering"); + +// editing_input_image + } else if (e.code == "Escape" || (prev_char.code == "Enter" && prev_prev_char.code == "Enter") || document.getElementById("editing_input_image")) { + editorLog("need to save"); +editorLog(" HHH current_editing", current_editing, "with active element", document.activeElement); + + e.preventDefault(); + this_char = ""; + prev_char = ""; + these_changes = assemble_internal_version_changes(document.activeElement); + editorLog(" CCC these_changes", these_changes); + editorLog(" CCC0 these_changes[0]", these_changes[0]); + editorLog("ongoing_editing_actions", ongoing_editing_actions); + editorLog("actively_editing", document.getElementById("actively_editing")); +editorLog(" III current_editing", current_editing, current_editing["tree"][current_editing["level"]]); + previous_added_object = final_added_object; + final_added_object = insert_html_version(these_changes); + editorLog("final_added_object, previous_added_object", final_added_object, previous_added_object); +editorLog(" LLL current_editing", current_editing, current_editing["tree"][current_editing["level"]]); + editorLog("the final_added_object", final_added_object); + editorLog("the actively_editing", document.getElementById("actively_editing")); + editorLog("OO", ongoing_editing_actions.length, " ongoing_editing_actions", ongoing_editing_actions); +/* + editorLog("ongoing_editing_actions[0]", ongoing_editing_actions[0]); + editorLog("ongoing_editing_actions[0][2]", ongoing_editing_actions[0][2]); +*/ + // maybe this next if only handles when we delete by removing the letters in a p? + if (these_changes[0] == "empty") { + editorLog("NN", ongoing_editing_actions.length, " ongoing_editing_actions", ongoing_editing_actions); + editorLog("ongoing_editing_actions[0]", ongoing_editing_actions[0]); + editorLog("ongoing_editing_actions[0][2]", ongoing_editing_actions[0][2]); + editorLog(" going to delete", these_changes[2][0]); + // this is sort-of a hack to detext the end of inserting li + if (ongoing_editing_actions.length == 2 && + ongoing_editing_actions[1][0] == "empty" && + ongoing_editing_actions[1][1] == "p" && + ongoing_editing_actions[0][0] == "new" && + ongoing_editing_actions[0][1] == "li") { + ongoing_editing_actions.pop(); // content was empty, so there is no editing action + ongoing_editing_actions.pop(); + delete_by_id(these_changes[2][0][0], "newempty"); + current_editing["location"][current_editing["level"]] -= 1 + final_added_object = previous_added_object; // this approach makes the updating of current_editing moot? + } else { + delete_by_id(these_changes[2][0][0], "empty"); + } + editorLog("MM", ongoing_editing_actions.length, " ongoing_editing_actions", ongoing_editing_actions); + for (var j=0; j= 0; --j) { + editorLog(" X", j, "the_whole_object[j]", the_whole_object[j]); + document.getElementById("actively_editing").insertAdjacentElement("afterend", the_whole_object[j]) + MathJax.typesetPromise(); + // MathJax.Hub.Queue(['Typeset', MathJax.Hub, the_whole_object[j]]); + } + + editorLog("here is where we need to update current_editing", "parent:", this_parent,"which is",document.getElementById(this_parent[0]), "level:", current_editing["level"], "loation:", current_editing["location"], "tree:", current_editing["tree"]); + $("#actively_editing").remove(); + +editorLog(" DDD current_editing", current_editing, current_editing["tree"][current_editing["level"]]); + + } + + most_recent_edit = ["","",""]; + while (ongoing_editing_actions.length) { + most_recent_edit = ongoing_editing_actions.pop(); + recent_editing_actions.unshift(most_recent_edit); + editorLog(" most_recent_edit", most_recent_edit); + } + editorLog(" 8888 final_added_object", final_added_object); + + save_edits() + + // is this in the right place? + editorLog("most_recent_edit", most_recent_edit); + + // sometimes, such as when adding items to a list, you want to + // automatically start adding something else. + // maybe refactor theorem to add proof after? + if (most_recent_edit[1] == "li") { // added to a list, so try adding again + // note that when adding an li, the neichbor is a p within the actual li neighbor + var new_obj = create_object_to_edit("li", document.getElementById(most_recent_edit[2]).firstElementChild, "afterend") + edit_in_place(new_obj, "new"); + editorLog("now editing the assumed new li"); +editorLog(" GGG current_editing", current_editing, current_editing["tree"][current_editing["level"]]); + + } else { + + editorLog("re-making the tree from final_added_object", final_added_object); + make_current_editing_tree_from_id(final_added_object.id); + editorLog("and then adding a menu"); + edit_menu_from_current_editing("entering"); + } + + } else if ( document.getElementById("actively_editing")) { + document.getElementById("actively_editing").remove(); + // were actively editing, and now just re-making the menu + edit_menu_from_current_editing("entering"); + } else { + // default makng the menu + edit_menu_from_current_editing("entering"); + } + } // esc or enter enter enter + editorLog ("processed an enter"); + } // esc or enter + else { + editorLog("e.code was not one of those we were looking for", e) + } + editorLog("leaving local editing action") +} + +function main_menu_navigator(e) { // we are not currently editing + // so we are building the menu, and possibly moving aroung the document, + //for the user to decide what/how to edit + +// There are 3 modes: +// #enter_choice, data-location="next" +// #enter_choice, data-location="stay" +// above means we are deciding whenter to edit/enter/leave and object, or to move on +// #choose_current +// 3rd option means we already have a menu + + if (document.getElementById("enter_choice")) { + theEnterChoice = document.getElementById("enter_choice"); + editorLog("enter_choice", e); + var theMotion = theEnterChoice.getAttribute("data-location"); + var object_of_interest; + if (theMotion == "stay") { + object_of_interest = theEnterChoice.parentElement.previousSibling; + } else { + object_of_interest = theEnterChoice.parentElement.parentElement; + } + editorLog(" MMN: want to", theMotion, "on", object_of_interest, "from", theEnterChoice) + + editorLog("current_editing", current_editing); + editorLog("theEnterChoice", theEnterChoice); + var current_level = current_editing["level"]; + var current_location = current_editing["location"][current_level]; + var current_siblings = current_editing["tree"][current_level]; + editorLog("current_level", current_level, "current_location", current_location, "current_siblings", current_siblings); + + if ((e.code == "Tab" || e.code == "ArrowDown") && !e.shiftKey) { + e.preventDefault(); + if (current_level == 0) { // at the top, so no "next" object + return "" + } + // go to next sibling, or stage to exit if on last sibling + if (current_location == (current_siblings.length - 1)) { // on last sibling + editorLog("on last sibling, level was", current_level,"siblings was", current_siblings, "tree", current_editing["tree"]); + editorLog("current_location was", current_location); + current_level -= 1; + current_location = current_editing["location"][current_level]; + current_editing["level"] = current_level; + current_editing["location"][current_level] = current_location; + current_siblings = current_editing["tree"][current_level]; + editorLog("current_location is", current_location); + editorLog("stay menu A"); + object_of_interest.classList.remove("may_leave"); + object_of_interest.classList.remove("may_elect"); + edit_menu_from_current_editing("leaving") + } else { + editorLog("moving to the next editable sibling"); + editorLog("level was", current_level,"siblings was", current_siblings, "tree", current_editing["tree"]); + editorLog("current_location was", current_location); + editorLog(current_location, "was", current_editing); + current_location += 1; + object_of_interest.classList.remove("may_leave"); + object_of_interest.classList.remove("may_elect"); + editorLog("current_location is", current_location); + editorLog("stay menu B"); + current_editing["location"][current_level] = current_location; + editorLog(current_location, "is", current_editing); + edit_menu_from_current_editing("entering") + } + } else if ((e.code == "Tab" && e.shiftKey) || e.code == "ArrowUp") { // Shift-Tab to prevous object + e.preventDefault(); + // go to previous sibling, or up one if on first sibling + editorLog("Arrow Up:", "current_location", current_location, "current_level", current_level); + if (theMotion == "stay") { // about to leave, to return to the top of that region + edit_menu_from_current_editing("entering") + } else if (current_location == 0) { + if (!current_level) { // already at the top, so nowhere to go, so do nothing + editorLog("at the top, so can't go up"); + return "" + } + current_level -= 1; + current_editing["level"] = current_level; + current_location = current_editing["location"][current_level]; + editorLog("AA new current_location", current_location, " current_editing['tree']", current_editing["tree"]); + editorLog(" current_editing['tree'][0]", current_editing["tree"][0]); + current_siblings = current_editing["tree"][current_level]; + editorLog("current_siblings", current_siblings); + edit_menu_from_current_editing("entering") + } else { + current_location -= 1; + current_editing["location"][current_level] = current_location; + editorLog("current_siblings", current_siblings); + editorLog("BB new current_location", current_location, "at level", current_level, " current_editing['tree']", current_editing["tree"]); + edit_menu_from_current_editing("entering") + } + } else if (e.code == "Escape" || e.code == "ArrowLeft") { + e.preventDefault(); + if (current_level == 0) { return "" } // already at the top, so nowhere to go, so do nothing +// copied from A1 + editorLog("At ArrowLeft, level was", current_level, "with location", current_editing["location"], "and tree", current_editing["tree"][current_level]); + current_level -= 1; + current_editing["level"] = current_level; + current_location = current_editing["location"][current_level]; + current_siblings = current_editing["tree"][current_level]; + editorLog("now level id", current_level, "with location", current_editing["location"], "and tree", current_editing["tree"][current_level]); + edit_menu_from_current_editing("entering") + } else if (e.code == "Enter" || e.code == "ArrowRight") { + e.preventDefault(); + if (theMotion == "stay") { + edit_menu_from_current_editing("entering"); + return "" + } + var edit_submenu = document.createElement('ol'); + edit_submenu.setAttribute('id', 'edit_menu'); + edit_submenu.setAttribute('class', 'edit_menu'); + + var to_be_edited = object_of_interest; + editorLog("to_be_edited", to_be_edited); + edit_submenu.innerHTML = top_menu_options_for(to_be_edited); + $("#enter_choice").replaceWith(edit_submenu); + document.getElementById('choose_current').focus(); + } + editorLog(" Just handled the case of enter_choice"); + return "" + + } else if (document.getElementById("choose_current")) { + var theChooseCurrent = document.getElementById("choose_current"); + var dataLocation = theChooseCurrent.getAttribute("data-location"); // may be null + var dataAction = theChooseCurrent.getAttribute("data-action"); // may be null + var dataModifier = theChooseCurrent.getAttribute("data-modifier"); // may be null + var dataEnv = theChooseCurrent.getAttribute("data-env"); // may be null + // a hack because of how the menus were originally set up + if (dataEnv == "save") { dataAction = "save" } + var dataEnvParent = theChooseCurrent.getAttribute("data-env-parent"); // may be null + var object_of_interest; + if (document.getElementById("edit_menu_holder")) { + object_of_interest = document.getElementById("edit_menu_holder").parentElement + } else if (document.getElementById("local_menu_holder")) { + object_of_interest = document.getElementById("local_menu_holder").parentElement + } else { + editorLog("something is confused: should be a menu, but isn't"); + if (theChooseCurrent.id) { + make_current_editing_tree_from_id(theChooseCurrent.id); + object_of_interest = theChooseCurrent + } else { + make_current_editing_tree_from_id(theChooseCurrent.parentElement.id); + object_of_interest = theChooseCurrent.parentElement + } + editorLog("made entering menu for", object_of_interest); + edit_menu_from_current_editing("entering") + } + current_level = current_editing["level"]; + current_location = current_editing["location"][current_level]; + current_siblings = current_editing["tree"][current_level]; + editorLog("in choose_current", dataLocation, "of", object_of_interest); + editorLog("dataAction ", dataAction); + editorLog("dataLocation ", dataLocation); + // we have an active menu, and have selected an item + // there are three main sub-cases, depending on whether there is a data-location attribute, + // a data-action attribute, or a data-env attribute. + // That is the primary order in which those attributes are considered + // however, some actions (such as Tab and shift-Tab) are the same + // in each sub-case (because all we are doing is moving up and down the + // current list of options), so we handle those first. + + if ((e.code == "Tab" || e.code == "ArrowDown") && !e.shiftKey) { + e.preventDefault(); + next_menu_item = theChooseCurrent.nextSibling; + editorLog("theChooseCurrent", theChooseCurrent, "next_menu_item", next_menu_item); + if (!next_menu_item) { next_menu_item = theChooseCurrent.parentNode.firstChild } + editorLog("theChooseCurrent", theChooseCurrent, "next_menu_item", next_menu_item); + if (theChooseCurrent == next_menu_item) { //only one item on menu, so Tab shold move to the next editable item + // if last editable child, go up one + if (current_location == (current_siblings.length - 1)) { + current_level -= 1; + current_location = current_editing["location"][current_level]; + current_editing["level"] = current_level; + current_editing["location"][current_level] = current_location; + edit_menu_from_current_editing("leaving") + } else { + current_location += 1; + editorLog("single item menu, current_location now", current_location); + current_editing["location"][current_level] = current_location; + edit_menu_from_current_editing("entering"); + } + } + theChooseCurrent.removeAttribute("id"); + editorLog("theChooseCurrent", theChooseCurrent, "next_menu_item", next_menu_item); + next_menu_item.setAttribute("id", "choose_current"); + editorLog("setting focus on",next_menu_item); + next_menu_item.focus(); + } // Tab + else if ((e.code == "Tab" && e.shiftKey) || e.code == "ArrowUp") { // Shift-Tab to prevous object + e.preventDefault(); + editorLog("just saw a", e.code); + editorLog("focus is on", $(":focus")); + editorLog("saw an",e.code); + next_menu_item = theChooseCurrent.previousSibling; + editorLog("W1 theChooseCurrent", theChooseCurrent, "next_menu_item", next_menu_item); + if (!next_menu_item) { next_menu_item = theChooseCurrent.parentNode.lastChild } + editorLog("W2 theChooseCurrent", theChooseCurrent, "next_menu_item", next_menu_item); + if (theChooseCurrent == next_menu_item) { //only one item on menu, so Shift-Tab shold move to previous or up one level + if (current_editing["location"][ current_editing["level"] ] == 0) { + current_editing["level"] -= 1; + } else { + current_editing["location"][ current_editing["level"] ] -= 1; + } + editorLog("single item menu, current_level now", current_level); + edit_menu_from_current_editing("entering"); + } else { + theChooseCurrent.removeAttribute("id"); + editorLog("W3 theChooseCurrent", theChooseCurrent, "next_menu_item", next_menu_item); + theChooseCurrent.classList.remove("chosen"); + next_menu_item.setAttribute("id", "choose_current"); + editorLog("setting focus on",next_menu_item); + next_menu_item.focus(); + } + } + else if (e.code == "Escape" || e.code == "ArrowLeft") { + editorLog("processing ESC"); + editorLog("At ArrowLeft, level was", current_level, "xx", current_editing["level"], "with location", current_editing["location"], "and tree", current_editing["tree"][current_level]); + // I think the next if can never be true, because of how to route keystrokes + if (document.getElementById("local_menu_holder")) { // hack for when the interface gets confused + document.getElementById("local_menu_holder").remove() + } else { + editorLog("W4 theChooseCurrent", theChooseCurrent); + // need to go up one level in the menu + var previous_menu_item = theChooseCurrent.parentElement.parentElement; // li > ol > li + if (previous_menu_item.id == "edit_menu_holder") { + var thenewchoice = ''; + thenewchoice += 'edit or add nearby'; + thenewchoice += ''; + previous_menu_item.innerHTML = thenewchoice + } else { + theChooseCurrent.parentElement.remove(); + previous_menu_item.classList.remove('chosen'); + previous_menu_item.parentElement.classList.remove('past'); + previous_menu_item.setAttribute("id", "choose_current"); + previous_menu_item.focus(); + } + } + } + else if (keyletters.includes(e.code)) { + key_hit = e.code.toLowerCase().substring(3); // remove forst 3 characters, i.e., "key" + editorLog("key_hit", key_hit); + theChooseCurrent = document.getElementById('choose_current'); + editorLog('theChooseCurrent', theChooseCurrent ); + editorLog( $(theChooseCurrent) ); + // there can be multiple data-jump, so use ~= to find if the one we are looking for is there + // and start from the beginning in case the match is earlier (make the second selector better) + if ((next_menu_item = $(theChooseCurrent).nextAll('[data-jump~="' + key_hit + '"]:first')[0]) || + (next_menu_item = $(theChooseCurrent).prevAll('[data-jump~="' + key_hit + '"]:last')[0])) { // check there is a menu item with that key + theChooseCurrent.removeAttribute("id", "choose_current"); + next_menu_item.setAttribute("id", "choose_current"); + next_menu_item.focus(); + } else { + // not sure what to do if an irrelevant key was hit + editorLog("that key does not match any option") + } + } + +// Now only Enter and ArrowRight are meaningful in this context. +// The effect will depend on the other attributes of #choose_current: +// dataLocation, dataAction, dataEnv + + else if (e.code == "Enter" || e.code == "ArrowRight") { + e.preventDefault(); + if (dataLocation) { + if (dataLocation == "enter") { // we are moving down into an object + + editorLog("theChooseCurrent", theChooseCurrent); + var object_to_be_entered = object_of_interest; + editorLog("object_to_be_entered", object_to_be_entered); + object_to_be_entered.classList.remove("may_select"); + object_to_be_entered.classList.remove("may_enter"); + object_to_be_entered.classList.remove("may_leave"); + editorLog('next_editable_of(object_to_be_entered, "children")'); + editableChildren = next_editable_of(object_to_be_entered, "children"); + current_level += 1; + current_editing["level"] = current_level; + current_editing["location"][current_level] = 0; + current_editing["tree"][current_level] = editableChildren; + editorLog("current_editing", current_editing); + + editorLog("object_to_be_entered", object_to_be_entered); + editorLog("with some children", editableChildren); + // put menu on the item at the top of the block_we_are_reentering + // this is a repeat of a Tab case, so consolidate + editorLog("menu place 10"); + editorLog("document.activeElement", document.activeElement); + + editorLog("menu on", editableChildren[0]); + edit_menu_for(editableChildren[0], "entering"); + + return "" + // combine the next with previous, because the ony difference is which object receives focus + } else if (dataLocation == "end") { // move to the end of an object + + editorLog("theChooseCurrent", theChooseCurrent); + var object_to_be_entered = object_of_interest; + editorLog("object_to_be_entered", object_to_be_entered); + object_to_be_entered.classList.remove("may_select"); + object_to_be_entered.classList.remove("may_enter"); + object_to_be_entered.classList.remove("may_leave"); + editorLog('next_editable_of(object_to_be_entered, "children")'); + editableChildren = next_editable_of(object_to_be_entered, "children"); + var num_editableChildren = editableChildren.length; + current_level += 1; + current_editing["level"] = current_level; + // current_editing["location"][current_level] = 0; + current_editing["location"][current_level] = num_editableChildren - 1; + current_editing["tree"][current_level] = editableChildren; + editorLog("current_editing", current_editing); + + editorLog("object_to_be_entered", object_to_be_entered); + editorLog("with some children", editableChildren); + // put menu on the item at the top of the block_we_are_reentering + // this is a repeat of a Tab case, so consolidate + editorLog("menu place 10end"); + editorLog("document.activeElement", document.activeElement); + + editorLog("menu on", editableChildren[num_editableChildren - 1]); + edit_menu_for(editableChildren[num_editableChildren - 1], "entering"); + + return "" + } else if ((dataLocation == "beforebegin") || (dataLocation == "afterend") || (dataLocation == "afterbegin")) { // should be the only other options + theChooseCurrent.parentElement.classList.add("past"); + theChooseCurrent.removeAttribute("id"); + theChooseCurrent.classList.add("chosen"); + + var parent_id = document.getElementById('edit_menu_holder').parentElement.parentElement.id; + if (!parent_id) { + editorLog(document.getElementById('edit_menu_holder').parentElement.parentElement, "has no id, so going down one level"); + parent_id = document.getElementById('edit_menu_holder').parentElement.id; + } + editorLog("making a menu for", parent_id); + var edit_submenu = document.createElement('ol'); + edit_submenu.innerHTML = menu_options_for(parent_id, "", "base"); + editorLog("just inserted inner menu_options_for(" + parent_id + ")", menu_options_for(parent_id, "", "base")); + theChooseCurrent.insertAdjacentElement("beforeend", edit_submenu); + document.getElementById('choose_current').focus(); + editorLog("focus is on", $(":focus")); + + return "" + } else { + editorLog("Error: unknown dataLocation:", dataLocation) + } + } // dataLocation + + else if (dataAction) { + if (dataAction == "edit") { + editorLog("going to edit", object_of_interest); + edit_in_place(object_of_interest, "old"); + } else if (dataAction == "replace") { // no longer used? + editorLog("replace", object_of_interest, "by id", object_of_interest.id); + replace_by_id(object_of_interest.id, "html") + } else if (dataAction == "stop_editing") { + editorLog("stop_editing", object_of_interest, "by id", object_of_interest.id); + replace_by_id(object_of_interest.id, "html") + eraseCookie(chosen_edit_option_key) + } else if (dataAction == "save") { + save_source(); + edit_menu_from_current_editing("entering"); + } else if (dataAction == "resume") { + editorLog("resuming previous editing session"); + resume_editing() + } else if (dataAction == "change-env-to") { + // shoudl use dataEnv ? + var new_env = theChooseCurrent.getAttribute("data-env"); + editorLog("changing environment to", new_env); + // #edit_menu_holder is in span.type, inside .heading, inside article + to_be_edited = document.getElementById('edit_menu_holder').parentElement.parentElement.parentElement; + editorLog("to_be_edited", to_be_edited); + var id_of_object = to_be_edited.id; + var this_object_source = internalSource[id_of_object]; + editorLog("current envoronemnt", this_object_source); + var old_env = internalSource[id_of_object]["sourcetag"]; + internalSource[id_of_object]["sourcetag"] = new_env; + recent_editing_actions.push([old_env, new_env, id_of_object]); + editorLog("the change was", "changed " + old_env + " to " + new_env + " " + id_of_object); + var the_whole_object = html_from_internal_id(id_of_object); + editorLog("B: the_whole_object", the_whole_object); + $("#" + id_of_object).replaceWith(the_whole_object[0]); // later handle multiple additions + editorLog("just edited", $("#" + id_of_object)); + // since we changed an object which is in + editorLog("curent_editing level", current_editing["level"], "with things", current_editing["tree"][current_editing["level"]]); + current_editing["level"] -= 1; + current_editing["tree"][current_editing["level"]] = next_editable_of(document.getElementById(id_of_object).parentElement, "children"); + editorLog("now curent_editing level", current_editing["level"], "with things", current_editing["tree"][current_editing["level"]]); + edit_menu_from_current_editing("entering"); + return "" + + } else if (dataAction == 'change-env') { + // #edit_menu_holder is in span.type, inside .heading, inside article + current_env = document.getElementById('edit_menu_holder').parentElement.parentElement.parentElement; + current_env_id = current_env.id; + + theChooseCurrent.parentElement.classList.add("past"); + theChooseCurrent.removeAttribute("id"); + theChooseCurrent.classList.add("chosen"); + + var edit_submenu = document.createElement('ol'); + editorLog("J1 lookinh for menu options for", current_env_id); + edit_submenu.innerHTML = menu_options_for(current_env_id, "", "change"); + editorLog("just inserted inner menu_options_for(parent_type)", menu_options_for(current_env_id, "", "change")); + theChooseCurrent.insertAdjacentElement("beforeend", edit_submenu); + document.getElementById('choose_current').focus(); + editorLog("focus is on", $(":focus")); + } else if (dataAction == 'modify') { + // #edit_menu_holder is in span.type, inside .heading, inside article + current_env = document.getElementById('edit_menu_holder').parentElement; + current_env_id = current_env.id; + + if (!dataModifier) { + theChooseCurrent.parentElement.classList.add("past"); + theChooseCurrent.removeAttribute("id"); + theChooseCurrent.classList.add("chosen"); + + var edit_submenu = document.createElement('ol'); + // this may only hapen when adjusting workspace: + if (!current_env_id) { + edit_submenu.setAttribute('id', 'edit_menu'); + edit_submenu.setAttribute('class', 'edit_menu'); + current_env_id = current_env.getAttribute("data-parent_id"); + editorLog("current_env_id", current_env_id); + } + editorLog("J2a looking for menu options for", current_env_id); + edit_submenu.innerHTML = menu_options_for(current_env_id, "", "modify"); + editorLog("just inserted inner menu_options_for(parent_type)", menu_options_for(current_env_id, "", "modify")); + if (theChooseCurrent.tagName == "SPAN") { // when adjusting workspace + theChooseCurrent.replaceWith(edit_submenu); + } else { + theChooseCurrent.insertAdjacentElement("beforeend", edit_submenu); + } + document.getElementById('choose_current').focus(); + editorLog("focus is on", $(":focus")); + } else if (dataModifier == "done") { + edit_menu_from_current_editing("entering"); + } else if (dataModifier == "arrows") { + // setup_arrow_modify() // is different for images and SBSs + } else { + // this may only hapen when adjusting workspace: + if (!current_env_id) { + current_env_id = current_env.getAttribute("data-parent_id"); + editorLog("current_env_id from parent", current_env_id); + } + modify_by_id(current_env_id, dataModifier) + } + } else if (dataAction == "move-or-delete") { + // almost all repeats from dataAction == 'change-env' + // except for current_env and menu options for. Consolidate + // maybe also separate actions which give anotehr menu, from actions which change content + current_env = document.getElementById('edit_menu_holder').parentElement; + editorLog("current_env", current_env); + current_env_id = current_env.id; + + theChooseCurrent.parentElement.classList.add("past"); + theChooseCurrent.removeAttribute("id"); + theChooseCurrent.classList.add("chosen"); + + var edit_submenu = document.createElement('ol'); + editorLog("J3 looking for menu options for", current_env_id); + edit_submenu.innerHTML = menu_options_for(current_env_id, "", "move-or-delete"); + editorLog("just inserted inner menu_options_for(parent_type)", menu_options_for(current_env_id, "", "move-or-delete")); + theChooseCurrent.insertAdjacentElement("beforeend", edit_submenu); + document.getElementById('choose_current').focus(); + editorLog("focus is on", $(":focus")); + } else if (dataAction == "delete") { + current_env = document.getElementById('edit_menu_holder').parentElement; + editorLog("current_env", current_env); + current_env_id = current_env.id; + delete_by_id(current_env_id, "choice") + } else if (["move-local", "move-local-p", "move-local-li"].includes(dataAction)) { + current_env = document.getElementById('edit_menu_holder').parentElement; + current_env_id = current_env.id; + handle_env_id = current_env_id; // we were focused on that p, even though + // we are moving an li. Later refocus on p + if (dataAction == "move-local-li") { + current_env.classList.remove("may_select"); + current_env = current_env.parentElement; + current_env.classList.add("may_select"); + current_env_id = current_env.id; + } + editorLog("current_env", current_env); + move_by_id_local(current_env_id, handle_env_id) + } else if (dataAction == "change-title") { + var this_heading = document.getElementById('edit_menu_holder').parentElement.parentElement; + var this_env_id = this_heading.getAttribute("data-parent_id"); + var new_title_form = standard_title_form(this_env_id); + document.getElementById('edit_menu_holder').parentElement.insertAdjacentHTML("afterend",new_title_form); + document.getElementById('edit_menu_holder').parentElement.remove(); + editorLog("change-title in progress") + document.getElementById('actively_editing').focus(); + } else if (dataAction == "change-caption") { + alert("editing captions not implemented yet"); + return ; + var this_caption = document.getElementById('edit_menu_holder').parentElement; + var this_env_id = this_caption.getAttribute("data-source_id"); + var new_title_form = standard_caption_form(this_env_id); + document.getElementById('edit_menu_holder').parentElement.insertAdjacentHTML("afterend",new_title_form); + document.getElementById('edit_menu_holder').parentElement.remove(); + editorLog("change-caption in progress") + document.getElementById('actively_editing').focus(); + } else { + editorLog("unknown dataAction", dataAction); + alert("I don;t know what to do llllllll dataAction " + dataAction) + } + } // dataAction + else if (dataEnv) { // this has to come after dataAction, because if both occur, + // dataAction says to do something, and dataEnv says what to do + e.preventDefault(); // was this handled earlier? + editorLog("in dataEnv", dataEnv); + editorLog("selected a menu item with no action and no location"); + $("#choose_current").parent().addClass("past"); + editorLog("apparently selected", theChooseCurrent); + theChooseCurrent.removeAttribute("id"); + theChooseCurrent.setAttribute('class', 'chosen'); + + // if (dataEnv in inner_menu_for()) { // object names a collection, so make submenu + if (dataEnv in submenu_options) { // object names a collection, so make submenu + editorLog("making a menu for", dataEnv); + var edit_submenu = document.createElement('ol'); + edit_submenu.innerHTML = menu_options_for("", dataEnv, "inner"); + theChooseCurrent.insertAdjacentElement("beforeend", edit_submenu); + document.getElementById('choose_current').focus(); + + // determine whether both of these next cases can occur + } else if (dataEnv in objectStructure || dataEnvParent in objectStructure) { + // we just selected an action, so do it + // that probably involves adding something before or after a given object + editorLog("making a new", dataEnv, "within", dataEnvParent); + + var before_after = $("#edit_menu_holder > #edit_menu > .chosen").attr("data-location"); + + if (dataEnv == "source") { + alert(" making source"); + show_source(object_of_interest, before_after); + edit_menu_from_current_editing("entering"); + return + // } else if (dataEnv == "save") { + // save_source(); + // edit_menu_from_current_editing("entering"); + } + editorLog("create object to edit",dataEnv, object_of_interest, before_after); + var new_obj = create_object_to_edit(dataEnv, object_of_interest, before_after); + if (!new_obj) { edit_menu_from_current_editing("entering"); return "" } + editorLog("new_obj", new_obj); + edit_in_place(new_obj, "new"); + var new_obj_id = new_obj.id; + editorLog("are we editing id", new_obj_id); + editorLog("are we editing", new_obj); + editorLog(" JJJ current_editing", current_editing["level"], current_editing["location"].length, current_editing["tree"].length, current_editing["tree"][current_editing["level"]]); + editorLog(" current_editing", current_editing); + object_of_interest.classList.remove("may_select"); + object_of_interest.classList.remove("may_enter"); + if (tmp = document.getElementById('edit_menu_holder')) { tmp.remove() } + if (dataEnv.startsWith("sbs")) { + editorLog("added sbs, now add to it", new_obj_id); + editorLog("document.getElementById(new_obj_id)", document.getElementById(new_obj_id)); + var first_panel_id = document.getElementById(new_obj_id).firstElementChild.firstElementChild.id; + editorLog("first_panel_id", first_panel_id, document.getElementById(first_panel_id)); + make_current_editing_tree_from_id(first_panel_id); + edit_menu_from_current_editing("entering"); + } + } else { + editorLog("Error: unknown dataEnv", dataEnv); + editorLog("Or maybe unknown dataEnvParent", dataEnvParent); + editorLog("moving up the menu -- not"); + alert("Sorry, not implemented yet!"); + theChooseCurrent.classList.remove("chosen"); + theChooseCurrent.parentElement.classList.remove("past"); + theChooseCurrent.setAttribute("id", "choose_current"); + } + } + } // // dataEnv + else { + editorLog("key that is not meaningful when navigating a menu:", e.code) + } + } +} // main menu navigator + +editorLog("adding tab listener"); + +document.addEventListener('keydown', logKeyDown); + +function logKeyDown(e) { + if (e.code == "ShiftLeft" || e.code == "ShiftRight" || e.code == "Shift") { return } + prev_prev_char = prev_char; + prev_char = this_char; + this_char = e; + editorLog("logKey",e,"XXX",e.code); + editorLog("are we editing", document.getElementById('actively_editing')); + editorLog("is there already an edit menu?", document.getElementById('edit_menu_holder')); + + var input_region = document.activeElement; + editorLog("input_region", input_region); + // if we are writing something, keystrokes usually are just text input + if (document.getElementById('actively_editing')) { + editorLog(" we are actively editing"); + + if (e.code == "Tab" && !document.getElementById('local_menu_holder')) { + // disabled for now + e.preventDefault(); + return "" + create_local_menu() + } else if (document.getElementById('local_menu_holder')) { + main_menu_navigator(e); + } else { + local_editing_action(e) + } + + } else if (document.getElementById('phantomobject')) { + var the_phantomobject = document.getElementById('phantomobject'); + + if (the_phantomobject.classList.contains('move')) { + move_object(e) + } else { + alert("do not know what to do with that") + } + } else { + main_menu_navigator(e); + } +} + +document.addEventListener('focus', function() { +// editorLog('focused:', document.activeElement) +// editorLog('which has content XX' + document.activeElement.innerHTML + "VV") + prev_prev_focused_element = prev_focused_element; + prev_focused_element = this_focused_element; + this_focused_element = document.activeElement; + $('.in_edit_tree').removeClass('in_edit_tree'); +// $(':focus').parent().addClass('in_edit_tree'); + $('#edit_menu_holder:first-child').parent().addClass('in_edit_tree'); + $('#edit_menu_holder').prev().addClass('in_edit_tree'); +/* + var edit_tree = $(':focus').parents(); + // put little lines on teh right, to show the local heirarchy + for (var i=0; i < edit_tree.length; ++i) { + if (edit_tree[i].getAttribute('id') == "content") { break } + edit_tree[i].classList.add('in_edit_tree') + } +*/ +}, true); + +function initialize_editing(xml_st) { + + createCookie(chosen_edit_option_key,1,0.01); + // console.log("QQQQQQQQ", xml_st, "PPPPPPPPPP"); + // Space Math uses a blank line to indicate mrows + xml_st = xml_st.replace(/<\/mrow>\s*/g, " \n\\cr\n "); + xml_st = xml_st.replace(//g, " "); + xml_st = xml_st.replace(/<\/mrow>/g, " "); + + xmlToObject(xml_st); + record_children(sourceobj); + internalSource = re_transform_source(); + + console.log("mostly done initializing"); + console.log(internalSource); + + current_editing = { + "level": 0, + "location": [0], + // "tree": [ [document.getElementById(top_level_id)] ] + "tree": [ [internalSource.root_data.id] ] + } + console.log("initial current_editing", current_editing); + + e_tree = current_editing["tree"]; + editorLog("e_tree", e_tree); + e_level = current_editing["level"]; + editorLog("e_level", e_level); + e_location = current_editing["location"]; + editorLog("e_location", e_location); + console.log(" making the initial menu for", e_tree[e_level][e_location]); + +// document.getElementById("content").firstElementChild.setAttribute("id", internalSource.root_data.id); + + console.log("replacing by id", internalSource.root_data.id); + replace_by_id(internalSource.root_data.id, "html"); + + edit_menu_for(e_tree[e_level][e_location], "entering") + + console.log("internalSource internalSource internalSource internalSource internalSource internalSource", internalSource); + + // done rebulding HTML, so now process math + // NOT CURRENTLY DOING ANYTHING???? + console.log("ready to edit, so typeset the math"); + document.getElementById("content").classList.add("canedit"); + MathJax.typesetPromise(); +} + +var this_source_txt; +var source_url = window.location.href; +source_url = source_url.replace(/(#|\?).*/, ""); +source_url = source_url.replace(/html$/, "ptx"); +fetch(source_url).then( + function(u){ return u.text();} + ).then( + function(text){ + this_source_txt = text; + // console.log("ppppppppppp this_source_txt",this_source_txt) + if (this_source_txt.includes("404 Not")) { + console.log("Error: source unavailable") + } else if (this_source_txt.includes(" 0) { +// bad code because I dopied and was too lazy to rewrite + parseLog(xml.nodeName, "has attributes", xml.attributes); + for (var j = 0; j < xml.attributes.length; j++) { + var attribute = xml.attributes.item(j); + if (attribute.nodeName == "permid") { this_id = attribute.nodeValue } + // these look backward, but that seems to be how PTX does it currently + if (!this_id && attribute.nodeName == "xml:id") { this_id = attribute.nodeValue } + } + } + if (!this_id) { + this_id = randomstring() + } + return this_id +} + +var sourceobj = {}; +var new_top_id = ""; + +function xmlToObject(xml_st) { + var xml; + if (typeof xml_st == "string") { + // parseLog("xml starts", xml_st.slice(0,50)); + parser = new DOMParser(); + xml = parser.parseFromString(xml_st, "text/xml"); +// xml = $.parseXML(xml_st); + } else { + xml = xml_st + } + + parseLog("xml", xml); + parseLog("xml.nodeName", xml.nodeName, "xml.nodeType", xml.nodeType); +// var obj = {}; + var this_id = ""; + var this_node_content = xml.nodeValue; + + if (xml.nodeType == 9) { // document + xml = xml.documentElement; + } + + parseLog("this_node_content", this_node_content); + + if (xml.nodeType == 1) { // element + this_id = xml_id_of(xml); + if (!new_top_id) { + new_top_id = this_id; + top_level_id = new_top_id; + sourceobj["root_data"] = {"id": new_top_id, "number_base": "X.Y"} + } + parseLog("found this_id", this_id); + var this_entry = {}; + this_entry["xml:id"] = this_id; + if (["ol", "ul", "dl"].includes(xml.nodeName)) { + this_entry["sourcetag"] = "list" +/* + } else if (["me", "men"].includes(xml.nodeName)){ + this_entry["sourcetag"] = "displaymath" +*/ + } else { + this_entry["sourcetag"] = xml.nodeName; + } + + this_node_content = ""; + if (xml.hasChildNodes()) { + for (var i = 0; i < xml.childNodes.length; i++) { + var item = xml.childNodes.item(i); + if (item.nodeType == 8) { + //comment, so skip + } else if (item.nodeType == 3) { // text + this_node_content += item.nodeValue + } else if (item.nodeType == 1) { // element + var sub_node_id = xmlToObject(item); // the contents, in certain cases + if (contained_objects.includes(item.nodeName)) { + this_entry[item.nodeName] = sub_node_id + } else { + this_node_content += "<&>" + sub_node_id + "<;>" + } + } else { + parseLog("what to do with this node?", item) + } + } + } + if (this_node_content) { + this_entry["content"] = this_node_content.trim(); + } + + if (xml.attributes.length > 0) { + // parseLog(xml.nodeName, "has attributes", xml.attributes); + for (var j = 0; j < xml.attributes.length; j++) { + var attribute = xml.attributes.item(j); + if (attribute.nodeName == "source") { this_entry["source"] = attribute.nodeValue } + else if (attribute.nodeName == "ref") { this_entry["ref"] = attribute.nodeValue } + else if (attribute.nodeName == "text") { this_entry["text"] = attribute.nodeValue } + // width for images, widths for sbs + else if (attribute.nodeName == "width") { + var widthvalue = attribute.nodeValue; + if (widthvalue.endsWith("%")) { widthvalue = widthvalue.slice(0,-1); } + this_entry["width"] = widthvalue + } else if (attribute.nodeName == "margins") { + var marginsvalues = attribute.nodeValue.split(' '); + if (marginsvalues.length == 1) { + var margin = marginsvalues[0]; + if (margin.endsWith("%")) { margin = margin.slice(0,-1); } + this_entry["marginleft"] = margin; + this_entry["marginright"] = margin; + } else if (marginsvalues.length == 2) { + var [marginleft, marginright] = marginsvalues; + if (marginleft.endsWith("%")) { marginleft = marginleft.slice(0,-1); } + if (marginright.endsWith("%")) { marginright = marginright.slice(0,-1); } + this_entry["marginleft"] = marginleft; + this_entry["marginright"] = marginright; + } else { + console.log("Error: too many margins in", xml) + } + } + } + } + // The CAT expects a width and margins on an image, but those myght not be + // specified in the source. So add those if not present. + if (xml.nodeName == "image") { + console.log("image attributes", xml.attributes, "ddd", xml.attributes["source"]); + if (!xml.attributes["width"]) { + console.log("no width"); + this_entry["width"] = 100; + } + if (!xml.attributes["margins"]) { + console.log("no margins"); + this_entry["marginleft"] = 0; + this_entry["marginright"] = 0; + } + } + + if (xml.attributes) { + parseLog(xml, "has attributes", xml.attributes) + } +// parseLog("item", item); + if (contained_objects.includes(xml.nodeName)) { + return this_node_content + } else { + sourceobj[this_id] = this_entry + return this_id + } + } else if (xml.nodeType == 8) { + // comment node, so do nothing + this_node_content = "" + } else if (xml.nodeType == 3) { // text + // can this, or the previous case, actually happen? + } else { + console.log("failed to deal with", xml) + } +} + +function record_children(internal_src) { + for (key in internal_src) { + var this_item = internal_src[key]; + if ("content" in this_item) { // skip empty tags + var this_content = this_item["content"]; + parseLog("this_content", this_content); + var child_items = this_content.match(/<&>.*?<;>/g) || ""; + for (var j=0; j < child_items.length; ++j) { + var this_child = child_items[j].slice(3,-3); + parseLog("this_child", this_child, "has a parent", key); + internal_src[this_child]["parent"] = [key, "content"] + } + } + // need to handle content and statement better + if ("statement" in this_item) { // skip empty tags + var this_statement = this_item["statement"]; + // parseLog("this_statement", this_statement); + var child_items = this_statement.match(/<&>.*?<;>/g) || ""; + for (var j=0; j < child_items.length; ++j) { + var this_child = child_items[j].slice(3,-3); + // parseLog("this_child", this_child, "has a parent", key); + internal_src[this_child]["parent"] = [key, "statement"] + } + } + if ("proof" in this_item) { // skip empty tags + var this_proof = this_item["proof"]; + // parseLog("this_statement", this_statement); + var child_items = this_proof.match(/<&>.*?<;>/g) || ""; + for (var j=0; j < child_items.length; ++j) { + var this_child = child_items[j].slice(3,-3); + // parseLog("this_child", this_child, "has a parent", key); + internal_src[this_child]["parent"] = [key, "proof"] + } + } + } + return internal_src +} + +// transofrm again, to un-wrap list in p + +/* rewrite with sourceobj not global */ +function re_transform_source() { + var ids_to_delete = []; + for (var id in sourceobj) { + var this_item = sourceobj[id]; + if (this_item["sourcetag"] == "list") { + // I think this takes the list out of its parent p, + // but I forgot to write this comment when I first wrote the code. + parseLog("found a list", this_item); + var [parent_id, parent_content] = this_item["parent"]; + parseLog("with parent", sourceobj[parent_id]); + if (sourceobj[parent_id]["sourcetag"] == "p") { + var [parent_parent_id, parent_parent_content] = sourceobj[parent_id]["parent"]; + parseLog("with parents parent", sourceobj[parent_parent_id]); + // need to skip the intermediate parent + var old_p_p_content = sourceobj[parent_parent_id][parent_parent_content]; + var new_p_p_content = old_p_p_content.replace("<&>" + parent_id + "<;>", "<&>" + id + "<;>"); + sourceobj[parent_parent_id][parent_parent_content] = new_p_p_content; + sourceobj[id]["parent"] = [parent_parent_id, parent_parent_content]; + // then eliminate the intermediate parent + parseLog("deleting", parent_id); + // delete sourceobj[parent_id]; + ids_to_delete.push(parent_id); + parseLog("now sourceobj[parent_parent_id]", sourceobj[parent_parent_id]) + } + } else if (this_item["sourcetag"] == "image") { + if ("width" in this_item && !("marginleft" in this_item)) { + parseLog("no width in" + this_item["xml:id"]) + var width = parseInt(this_item["width"]); + var margins = (100 - width)*0.5; + this_item["marginleft"] = margins; + this_item["marginright"] = margins; + } + } else if (["md", "mdn", "me", "men"].includes(this_item["sourcetag"])) { + // to handle the case of more than one displaymath in a p, + // we do it in two passes. First we just record the ids of the me/men's. + // Then we simplify the problem by handling the displaymath in the + // order they occur. + parseLog("found displaymath", this_item); + parseLog("with parent", sourceobj[this_item["parent"][0]]); + var displaymath_id= this_item["xml:id"]; + if ("includedmath" in sourceobj[this_item["parent"][0]]) { + sourceobj[this_item["parent"][0]]["includedmath"].push("<&>" + displaymath_id + "<;>") + } else { + sourceobj[this_item["parent"][0]]["includedmath"] = ["<&>" + displaymath_id + "<;>"] + } + } else if (["caption"].includes(this_item["sourcetag"])) { + parseLog("found a caption", this_item); + var [parent_id, parent_content] = this_item["parent"]; + parseLog("with parent", sourceobj[parent_id]); + if (sourceobj[parent_id]["sourcetag"] == "figure") { + var old_p_content = sourceobj[parent_id][parent_content]; + var new_p_content = old_p_content.replace("<&>" + id + "<;>", ""); + sourceobj[parent_id][parent_content] = new_p_content; + sourceobj[parent_id]["captiontext"] = sourceobj[id]["content"]; + // then eliminate the caption object, because now it is an attribute of a figure + // delete sourceobj[id]; + ids_to_delete.push(id); + parseLog("now sourceobj[parent_id]", sourceobj[parent_id]) +alert("testing") + } else { alert("error: caption not in figure") } + } + } + // now go through and fix the "p" containing displaymath + for (var id in sourceobj) { + var this_item = sourceobj[id]; + if (this_item["sourcetag"] == "p") { + if ("includedmath" in this_item) { + console.log("found includedmath", this_item); + var outer_parent = this_item["parent"]; + var context_of_outer_parent = sourceobj[outer_parent[0]][outer_parent[1]]; + console.log("this item id", id, "with parent", outer_parent, "in context", context_of_outer_parent); + var these_includedmath = this_item["includedmath"]; + var this_content = this_item["content"]; + var these_includedmath_index = []; + for (var j=0; j < these_includedmath.length; ++j) { + var this_math_tag = these_includedmath[j]; + these_includedmath_index.push([this_content.indexOf(this_math_tag),this_math_tag]); + console.log("this_math_tag", this_math_tag, "has index", this_content.indexOf(this_math_tag)) + } + these_includedmath_index.sort(); + parseLog("sorted list", these_includedmath_index) + + // var displaymath_parent_original_content = sourceobj[outer_parent[0]]["content"]; + parseLog("ocntent before splitting up", context_of_outer_parent); + this_math_tag = these_includedmath_index[0][1]; + parseLog("this_math_tag", this_math_tag); + var this_math_id = this_math_tag.slice(3,-3); + parseLog("this_math_id", this_math_id); + parseLog("with source", sourceobj[this_math_id]); + // move punctuation inside the display math + // (for easier editing. move it back out later) + var find_char_after = new RegExp('^(.*' + this_math_tag + ")(.)\s*(.*)$", "s"); + var char_after = this_content.replace(find_char_after, "$2"); + if ([".", ",", ";", ":"].includes(char_after)) { + console.log("found punctuation", char_after); + this_content = this_content.replace(find_char_after, "$1$3"); // go back an omit white space + sourceobj[this_math_id]["content"] += char_after + } + // the original p ends at the first displaymath + var displaymath_id_and_before = new RegExp('^.*' + this_math_tag, "s"); // s = dotAll + var displaymath_id_and_after = new RegExp(this_math_tag + '.*$', "s"); + sourceobj[id]["content"] = + this_content.replace(displaymath_id_and_after, ""); + sourceobj[id]["sourcetag"] = "ip" + this_content = this_content.replace(displaymath_id_and_before, ""); + parseLog("updated this_content", this_content); + // the first displaymath now has a different parent + sourceobj[this_math_id]["parent"] = outer_parent; + // that parent needs to know where to put the first displaymath + context_of_outer_parent = context_of_outer_parent.replace("<&>" + id + "<;>", "<&>" + id + "<;>" + "\n" + this_math_tag); + parseLog("updated context_of_outer_parent", context_of_outer_parent); + sourceobj[outer_parent[0]][outer_parent[1]] = context_of_outer_parent; + // here need to check if this_content is nonempty (after removing trailing white space + var new_id = "XXXX" + randomstring(); + sourceobj[new_id] = {"xml:id":new_id, "sourcetag": "mp"}; + sourceobj[new_id]["content"] = this_content; + sourceobj[new_id]["parent"] = outer_parent; + context_of_outer_parent = context_of_outer_parent.replace(this_math_tag, this_math_tag + "\n" + "<&>" + new_id + "<;>"); + sourceobj[outer_parent[0]][outer_parent[1]] = context_of_outer_parent; + for (var j=1; j < these_includedmath_index.length; ++j) { + this_math_tag = these_includedmath_index[j][1]; + this_math_id = this_math_tag.slice(3,-3); + + // move punctuation inside the display math + // (for easier editing. move it back out later) + var find_char_after = new RegExp('^(.*' + this_math_tag + ")(.)\s*(.*)$", "s"); + var char_after = this_content.replace(find_char_after, "$2"); + if ([".", ",", ";", ":"].includes(char_after)) { + console.log("found punctuation", char_after); + this_content = this_content.replace(find_char_after, "$1$3"); // go back an omit white space + sourceobj[this_math_id]["content"] += char_after + } + + displaymath_id_and_before = new RegExp('^.*' + this_math_tag, "s"); // s = dotAll + displaymath_id_and_after = new RegExp(this_math_tag + '.*$', "s"); + sourceobj[new_id]["content"] = this_content.replace(displaymath_id_and_after, ""); + this_content = this_content.replace(displaymath_id_and_before, ""); + // this displaymath now has a different parent + sourceobj[this_math_id]["parent"] = outer_parent; + // that parent needs to know where to put the first displaymath + context_of_outer_parent = context_of_outer_parent.replace("<&>" + new_id + "<;>", "<&>" + new_id + "<;>" + "\n" + this_math_tag); + sourceobj[outer_parent[0]][outer_parent[1]] = context_of_outer_parent; + // omit next if this_content is only white space + new_id = "XXXX" + randomstring(); + sourceobj[new_id] = {"xml:id":new_id, "sourcetag": "mp"}; + sourceobj[new_id]["content"] = this_content; + sourceobj[new_id]["parent"] = outer_parent; + context_of_outer_parent = context_of_outer_parent.replace(this_math_tag, this_math_tag + "\n" + "<&>" + new_id + "<;>"); + sourceobj[outer_parent[0]][outer_parent[1]] = context_of_outer_parent; + } + sourceobj[new_id]["sourcetag"] = "fp" + } + } + } + // trim leading white space in paragraph content + for (var id in sourceobj) { + var this_item = sourceobj[id]; + if (["p", "ip", "mp", "fp"].includes(this_item["sourcetag"])) { + var this_content = sourceobj[id]["content"]; + this_content = this_content.replace(/\n +/g, "\n"); + sourceobj[id]["content"] = this_content; + } else if (["me", "men", "md", "mdn"].includes(this_item["sourcetag"])) { + var this_content = sourceobj[id]["content"]; + this_content = this_content.replace(/\n +/g, "\n "); + sourceobj[id]["content"] = this_content; + } + // next also needs table (or whatever it is that has a caption) + if (["figure"].includes(this_item["sourcetag"])) { + editorLog("adjusting a figure", this_item); + // ??? next line should involve captiontext instead of caption ? + var this_caption = sourceobj[id]["caption"]; + this_caption = this_caption.replace(/\n +/g, "\n"); + sourceobj[id]["captiontext"] = this_caption; + } + } + + for (var j=1; j < ids_to_delete.length; ++j) { + // delete sourceobj[ids_to_delete[j]] + } + + return sourceobj; +} + diff --git a/slides-annotated/_static/pretext/js/highlight.js b/slides-annotated/_static/pretext/js/highlight.js new file mode 100755 index 0000000..98f906a --- /dev/null +++ b/slides-annotated/_static/pretext/js/highlight.js @@ -0,0 +1,480 @@ + +/* +highlight_css = document.createElement('style'); +highlight_css.type = "text/css"; +highlight_css.id = "highlight_css"; +document.head.appendChild(highlight_css); +var css_for_hl = 'span.hl { background: yellow; }\n'; +css_for_hl += '#hlmenu { position: absolute; top: 300px; left: 200px;}\n'; +css_for_hl += '#hlmenu { padding: 8px; background: #FFF; }\n'; +css_for_hl += '#hlmenu { box-shadow: 8px 10px 5px #888; border: 1px solid #aaa;}\n'; +css_for_hl += '#hlmenu .hldelete { background: #fdd; }'; +css_for_hl += '#hlmenu .hldelete:hover { background: #fbb; }'; +css_for_hl += '#hlmenu .hlcopy { background: #ddf; }'; +css_for_hl += '#hlmenu .hlcopy:hover { background: #bbf; }'; +css_for_hl += '#hlmenu .dismiss:hover { background: #ff9; }'; +css_for_hl += '#hlmenu > div { padding: 4px; font-size: 90%}'; +highlight_css.innerHTML = css_for_hl; +*/ + +hlmenu = document.createElement('div'); +hlmenu.id = "hlmenu"; +hlmenu.style.display = "none"; +hlmenu_contents = '
    copy to clipboard
    \n'; +hlmenu_contents += '
    delete highlight
    \n'; +hlmenu_contents += '
    dismiss menu
    '; +hlmenu.innerHTML = hlmenu_contents; +document.body.appendChild(hlmenu); + +var all_highlights = localStorage.getObject("all_highlights"); +if (!all_highlights) { + console.log("no highlights on this page",all_highlights); + all_highlights= {}; +} else { + console.log("highlights already",all_highlights); +} + +console.log("all_highlights.keys()", Object.keys(all_highlights)); +console.log("waiting for MathJax"); +MathJax.Hub.Register.StartupHook("End",function () { + // need to wait for MathJax, because MathJax changes the number of nodes in a paragraph + console.log("MathJax is done"); + display_all_highlights(all_highlights, Object.keys(all_highlights), 0); +}); + + +/* +for (var key in all_highlights) { + var this_key = key; + var these_highlights = all_highlights[key]; + console.log("adding highlights to", key); +// await display_highlights_on(key, all_highlights[key], 0) + for (var i=0; i< these_highlights.length; ++i) { + hl = these_highlights[i]; + console.log("inserting highlight",i,"which is",hl, "on", key); + // display_one_highlight(key, hl); +//fails because + setTimeout(function() { display_one_highlight(this_key, hl)}, i*2000); + + } +} +*/ + +function index_of_child(child) { + var i = 0; + while( (child = child.previousSibling) != null ) + {i++ } + return i +} + +function increment_id(list_of_things_with_ids) { // assume ids are of the form xxxxNN with N a digit and x a non-digit + console.log("incrementing id on", list_of_things_with_ids); + if (list_of_things_with_ids.length == 0) { + return 0 + } + + id_start = list_of_things_with_ids[0]["id"].replace(/^(.*?)[0-9]+$/, "$1"); + current_endings = []; + for (var i=0; i < list_of_things_with_ids.length; ++i) { + current_endings.push(list_of_things_with_ids[i]["id"].replace(/^.*?([0-9]+)$/, "$1")); + } + console.log("existing id endings", current_endings); + current_max = Math.max(...current_endings); + return id_start + (parseInt(current_max) + 1) +} + +function enclosing_p_or_li(obj) { + console.log("obj.tagName", obj.tagName, "ggg",obj); + if (!obj) { + console.log("problem with previous object"); + return null + } + else if (obj.tagName == 'P' || obj.tagName == 'LI') { + return obj + } + return enclosing_p_or_li(obj.parentNode) +} + +// If you just loop ofer the highlights to add them, +// you end up with a race condition because each highlight changes +// the structure of the paragraph. +async function display_one_highlight(parent_id, hl) { + var the_parent = document.getElementById(parent_id); + if (!the_parent) { + console.log("this id not on this page:", parent_id); + return + } + console.log("setting", hl, "on", parent_id); + var st_node_ind = hl['start_nn']; + var st_offset = hl['start_offset']; + var end_node_ind = hl['end_nn']; + var end_offset = hl['end_offset']; + console.log("st_node_ind", st_node_ind, "st_offset", st_offset, "end_node_ind", end_node_ind, "end_offset", end_offset); + // other error checks: same parent + if (st_offset < 0 || st_node_ind > the_parent.childNodes.length || end_node_ind > the_parent.childNodes.length || st_offset > the_parent.childNodes[st_node_ind].textContent.length || end_offset < 0 || end_offset > the_parent.childNodes[end_node_ind].textContent.length) { + console.log("highlight data inconsistent with paragraph structure that contains", the_parent.childNodes.length, "nodes"); + return + } + + let this_range = document.createRange(); + console.log("setting this_range.setStart", the_parent.childNodes[st_node_ind], "with offset", st_offset, "out of", the_parent.childNodes[st_node_ind].textContent.length); + this_range.setStart(the_parent.childNodes[st_node_ind], st_offset); + this_range.setEnd(the_parent.childNodes[end_node_ind], end_offset); + var inside_part = document.createElement("span") + inside_part.classList.add("hl"); + inside_part.id = hl['id']; + console.log("inside_part", inside_part, "going inside this_range",this_range); + console.log("in the_parent",the_parent); + this_range.surroundContents(inside_part); + return; +} + +//var display_highlights_on = function(parent_id, highlights_on, ind) { +async function display_highlights_on(parent_id, highlights_on, ind) { + console.log("display_highlights_on", parent_id, "xxxx", ind, "out of", highlights_on.length); + if (ind < highlights_on.length) { + console.log("about to display one highlight, number", ind, "on", parent_id); + await display_one_highlight(parent_id, highlights_on[ind]); + ++ind; + await display_highlights_on(parent_id, highlights_on, ind) + } + return; +} + +async function display_all_highlights(every_highlight, hl_p_keys, i) { + console.log("making hl on", hl_p_keys[i]); + if (i < hl_p_keys.length) { + await display_highlights_on(hl_p_keys[i], every_highlight[hl_p_keys[i]], 0); + ++i; + await display_all_highlights(every_highlight, hl_p_keys, i) + } + return; +} + +var DELAY = 500, clicks = 0, timer = null; +$("p[id], li[id]").on("click", function(e) { + clicks++; //count clicks + + if(clicks === 1) { + + if (e.target.classList.contains("hl")) { + modifyhighlight(e); + clicks = 0; + } else { + timer = setTimeout(function() { + newhighlight() + clicks = 0; //after action performed, reset counter + }, DELAY); + + } + + + } else if (clicks === 2) { + timer2 = setTimeout(function() { + clearTimeout(timer); + // alert("Double Click"); + clicks = 0; + }, DELAY/2); + } else { + clearTimeout(timer); //prevent single-click action + clearTimeout(timer2); //prevent double-click action + // alert("Triple Click"); //perform triple-click action + clicks = 0; //after action performed, reset counter + } +}); + +function save_highlights() { + hl_data = {"action": "save", "user": uname, "pw": emanu, "bookID": bodyID, "type": "highlights", "hl": JSON.stringify(all_highlights)} + $.ajax({ + url: "https://aimath.org/cgi-bin/u/highlights.py", + type: "post", + data: JSON.stringify(hl_data), + dataType: "json", + success: function(data) { + console.log("something", data, "back from highlight"); + // alert(data); + }, + error: function(errMsg) { + console.log("seems to be an error?",errMsg); + // alert("Error\n" + errMsg); + } + }); + + console.log("just ajax sent", JSON.stringify(reading_questions_object)); +} + +function newhighlight() { + var this_selection = window.getSelection(); + console.log("UUUUUUUUUUUUUUUUUUUUUUUUUUU"); + console.log("selection", this_selection, "as string", this_selection.toString(), "length", this_selection.toString().length); + if(this_selection.toString().length >= 3) { + console.log("this_selection", this_selection); + console.log("this_selection parentNode", this_selection.parentNode); + + num_selected_ranges = this_selection.rangeCount; + console.log("this_selection.rangeCount", this_selection.rangeCount); + console.log("this_selection.getRangeAt(0)", this_selection.getRangeAt(0)); + console.log("this_selection.getRangeAt(num_selected_ranges)", this_selection.getRangeAt(num_selected_ranges-1)); + console.log("this_selection.getRangeAt(0).startContainer", this_selection.getRangeAt(0).startContainer); + console.log("this_selection.getRangeAt(0).endContainer", this_selection.getRangeAt(0).endContainer); + console.log("this_selection.getRangeAt(-1).startContainer", this_selection.getRangeAt(num_selected_ranges-1).startContainer); + console.log("this_selection.getRangeAt(-1).endContainer", this_selection.getRangeAt(num_selected_ranges-1).endContainer); + console.log("start equals end", this_selection.getRangeAt(0).startContainer == this_selection.getRangeAt(0).endContainer); + console.log("this_selection.getRangeAt(0).startContainer.parent", this_selection.getRangeAt(0).startContainer.parentNode); + console.log("this_selection.getRangeAt(0).endContainer.parent", this_selection.getRangeAt(0).endContainer.parentNode); + starting_parent = enclosing_p_or_li(this_selection.getRangeAt(0).startContainer); + starting_parent_id = starting_parent.id; + ending_parent_id = enclosing_p_or_li(this_selection.getRangeAt(num_selected_ranges-1).endContainer).id; + console.log("starting parent", enclosing_p_or_li(this_selection.getRangeAt(0).startContainer)); + console.log("starting_parent.childNodes", starting_parent.childNodes); + console.log("starting_parent.childNodes.length", starting_parent.childNodes.length); + console.log("ending parent", enclosing_p_or_li(this_selection.getRangeAt(0).endContainer)); + console.log("index of starting node", index_of_child(this_selection.getRangeAt(0).startContainer)); + console.log("which is", starting_parent.childNodes[index_of_child(this_selection.getRangeAt(0).startContainer)]); + console.log("index of ending node", index_of_child(this_selection.getRangeAt(num_selected_ranges-1).endContainer)); + console.log("which is", starting_parent.childNodes[index_of_child(this_selection.getRangeAt(num_selected_ranges-1).endContainer)]); + console.log("XXXXXXXXXXXXXXXXXXXXXXX"); + starting_node = this_selection.getRangeAt(0); + starting_node_container = starting_node.startContainer; + starting_node_number = index_of_child(starting_node_container); + starting_node_offset = starting_node.startOffset; + console.log("starting_node", starting_node, "starting_node_container", starting_node_container); + console.log("starting_parent.childNodes[0]", starting_parent.childNodes[0]); + ending_node = this_selection.getRangeAt(num_selected_ranges-1); + ending_node_container = ending_node.endContainer; + console.log("ending_node", ending_node, "ending_node_container", ending_node_container); + ending_node_number = index_of_child(ending_node_container); + ending_node_offset = ending_node.endOffset; + console.log("selection starts at character number", starting_node.startOffset, "in node number", index_of_child(starting_node), "of node", starting_node.startContainer, "within", starting_node.startContainer.parentNode,"which is", starting_node); + console.log("selection ends at character number", ending_node.endOffset, "in node number", index_of_child(ending_node_container), "of node", ending_node.endContainer, "within", ending_node.endContainer.parentNode, "which is", ending_node); + +// let this_range = document.createRange(); +// this_range.setStart(starting_parent.childNodes[starting_node_number], starting_node_offset); +// this_range.setEnd(document.getElementById(starting_parent_id).childNodes[ending_node_number], ending_node_offset); +// test_inside_part=document.createElement("span") +// test_inside_part.classList.add("gggg"); +// this_range.surroundContents(test_inside_part); + console.log("num_selected_ranges", num_selected_ranges, "starting_parent_id", starting_parent_id, "ending_parent_id", ending_parent_id); + this_selection.empty(); + console.log("starting_parent_id", starting_parent_id, "ending_parent_id", ending_parent_id); + if (starting_parent_id != ending_parent_id) { + alert("Highlights must be within\none paragraph or list item."); + return "" + } + if (starting_parent != starting_node_container.parentNode) { + starting_node_number = index_of_child(starting_node_container.parentNode) - 1; + console.log("new starting node",starting_node_number, " which is", starting_parent.childNodes[starting_node_number]); + starting_node_offset = starting_parent.childNodes[starting_node_number].length; + } + if (starting_parent != ending_node_container.parentNode) { + ending_node_number = index_of_child(ending_node_container.parentNode) + 1; + console.log("new edngin node",ending_node_number, " which is", starting_parent.childNodes[ending_node_number]); + ending_node_offset = 0; + } + console.log("starting_parent eq st cont parent", starting_parent == starting_node_container.parentNode, "starting_parent eq end cont parent", starting_parent == ending_node_container.parentNode); +// display_one_highlight(starting_parent_id, starting_node_number, starting_node_offset, ending_node_number, ending_node_offset); +// console.log("this_range", this_range); + this_highlight = {"start_nn": starting_node_number, + "start_offset": starting_node_offset, + "end_nn": ending_node_number, + "end_offset": ending_node_offset}; + if (starting_parent_id in all_highlights) { + console.log("the starting_parent_id", starting_parent_id, "is already in all_highlights", all_highlights); + new_id = increment_id(all_highlights[starting_parent_id]); + this_highlight['id'] = new_id; + all_highlights[starting_parent_id].push(this_highlight) + } else { + this_highlight['id'] = starting_parent_id + "-" + "hl" + 1; + all_highlights[starting_parent_id] = [this_highlight] + } + display_one_highlight(starting_parent_id, this_highlight); + localStorage.setObject("all_highlights", all_highlights); + + if (uname != "guest" && role=="student") { + console.log("saving highlights"); + save_highlights(); + } + console.log("all_highlights", all_highlights); + return ""; + } +} + +function modifyhighlight(e) { + this_hl = e.target; + console.log("clicked hl", this_hl.id); + + var x = e.clientX, y = e.clientY; + console.log("x", x, "y", y); + document.getElementById("hlmenu").style.top = (y - 10 + $(window).scrollTop()) + 'px'; + document.getElementById("hlmenu").style.left = (x + 20) + 'px'; + document.getElementById("hlmenu").style.display = 'block'; + document.getElementsByClassName("hlcopy")[0].setAttribute("data-hlid", this_hl.id); + document.getElementsByClassName("hldelete")[0].setAttribute("data-hlid", this_hl.id); +// tooltipSpan.style.left = (x + 20) + 'px'; + + var parent_id = this_hl.id.replace(/^(.*)-[^\-]*$/, "$1"); + var number_of_this_highlight = this_hl.id.slice(-1); + var these_highlights = all_highlights[parent_id]; + var this_highlight = these_highlights[number_of_this_highlight-1]; + console.log("parent id", parent_id, "nunber",number_of_this_highlight); + var num_child_nodes = this_hl.childNodes.length; + console.log("which has", this_hl.childNodes.length, "child nodes"); + for (var i=0; i < num_child_nodes; ++i) { + console.log(i, "node is", this_hl.childNodes[i]) + } + console.log("highlights on this item",all_highlights[parent_id], "of which we clicked", these_highlights[number_of_this_highlight - 1], +"number", number_of_this_highlight, "out of", these_highlights.length ); + for (var i=0; i 0) { + all_highlights[parent_id] = new_highlights + } else { + delete all_highlights[parent_id] + } + + localStorage.setObject("all_highlights", all_highlights); + + + // hide the current highlight + $(hl_to_delete).replaceWith(hl_to_delete.innerHTML); + + document.getElementById(parent_id).normalize(); // because a previous step creates adjacent text nodes + + document.getElementById("hlmenu").style.display = 'none'; +}); + +$('body').on('click','.dismiss', function(e){ + console.log(".dismiss of",this); + the_parent = this.closest("[id]"); + the_parent.style.display = 'none'; +}); diff --git a/slides-annotated/_static/pretext/js/instructor.js b/slides-annotated/_static/pretext/js/instructor.js new file mode 100755 index 0000000..df5a43c --- /dev/null +++ b/slides-annotated/_static/pretext/js/instructor.js @@ -0,0 +1,131 @@ + +var icon = { + "warning": "⚠️ ", + "commentary": "☕", + "media": "▶", + "tip": "📣", + "worksheet": "📄", + "assesment": "A+", + "slides": "📻", + "outcomes": "✓" +}; + +var html_words_of = { + "warning": "Common pitfall", + "commentary": "Alert", + "media": "An amusing demo", + "tip": "Tip: discussion point", + "worksheet": "Worksheet:", + "assesment": "Sample exam:", + "slides": "Slides:", + "outcomes": "Learning outcomes" +} + +//
       Word PTX PDF
    ", + +var type_name = { + "warning": "warning", + "commentary": "commentary", + "media": "media", + "tip": "tip", + "worksheet": "worksheet", + "assesment": "assesment", + "slides": "slides", + "outcomes": "outcomes" +}; + +console.log("in instructor.js", role, "role", logged_in, "logged_in"); + +if (role=="instructor") { + console.log(" loading instructor resources"); + + var instructor_resources = document.querySelectorAll(".instructor"); + + console.log('instructor_resources.length', instructor_resources.length); + + var icons_on_this_page = []; + + for (var j=0; j < instructor_resources.length; ++j) { + var instructor_resource = instructor_resources[j]; + var instructor_resource_parent_id = instructor_resource.parentNode.id; + console.log(" XXXXXXXXXXXX instructor_resource.parentNode", instructor_resource.parentNode); + console.log("instructor_resource_parent_id", instructor_resource_parent_id); + if(instructor_resource_parent_id) { + } else { + instructor_resource_parent_id = instructor_resource.parentNode.parentNode.id; + } + + this_type = instructor_resource.getAttribute("data-resource"); + this_icon = icon[this_type]; + var id_of_this_icon = "resourceid" + j; + icons_on_this_page.push([type_name[this_type], this_icon, id_of_this_icon]); + + console.log("this_type", this_type, "this_icon", this_icon, "html_words_of[this_type]", html_words_of[this_type]); + this_item_with_resource = document.getElementById(instructor_resource_parent_id); + console.log("instructor_resource_parent_id", instructor_resource_parent_id, "this_item_with_resource", this_item_with_resource); + + var this_margin_resource = document.createElement('div'); + this_margin_resource.setAttribute('class', 'marginresource'); + this_margin_resource.setAttribute('id', id_of_this_icon); + this_margin_resource.innerHTML = "" + this_icon + ""; + if(!(this_title = instructor_resource.getAttribute("title"))) { + this_title = html_words_of[this_type] + } + if(instructor_resource.hasAttribute("data-content")) { + this_content = instructor_resource.getAttribute("data-content"); + this_title = '' + this_title + ''; + } + var links_html = "" + if(instructor_resource.hasAttribute("data-links")) { + var this_links = instructor_resource.getAttribute("data-links"); + these_links = this_links.split(";"); + links_html = '' + console.log(" OOOOOOOOOOOOOO these_links", these_links, "these_links.length", these_links.length); + if(these_links.length > 0) { + console.log("these_links[0]", these_links[0]); + } + tmpJ = these_links.length; + console.log("tmpJ", tmpJ); + for(var jj=0; jj < these_links.length; ++jj) { + console.log( " UUUUUUUUUUUUUu these_links[jj]", these_links[jj]); + this_type_and_link = these_links[jj].split(","); + if(jj>0) { links_html += ", " } + links_html += '' + this_type_and_link[0] + ''; + } + links_html += 'XX'; + console.log("done adding links to title"); + } + this_title = '' + this_title + if(links_html) { + this_title += links_html + } + this_title += ''; + this_margin_resource.innerHTML += this_title; +// this_item_with_resource.insertBefore(this_margin_resource); + this_item_with_resource.insertBefore(this_margin_resource, this_item_with_resource.firstChild); + console.log("appended to ",this_item_with_resource); + } + + icons_on_this_page.sort(); + + var icon_legend_for_this_page = document.createElement('div'); + var prev_icon = ""; + var icon_list = '' + "" + icons_on_this_page[0][0] + ": "; + for (var j=0; j < icons_on_this_page.length; ++j) { + next_icon = icons_on_this_page[j][1]; + if(j > 0 && next_icon !== prev_icon) { + icon_list += '' + "
    "; + icon_list += '' + "" + icons_on_this_page[j][0] + ": "; + icon_list += '' + next_icon + ''; + } else { + icon_list += '' + next_icon + ''; + } + prev_icon = next_icon; + } + icon_list += ''; + + icon_legend_for_this_page.setAttribute('class', 'iconlegend'); + icon_legend_for_this_page.innerHTML = icon_list + document.body.appendChild(icon_legend_for_this_page); + +} // if logged in as instructor diff --git a/slides-annotated/_static/pretext/js/lib/jquery.espy.min.js b/slides-annotated/_static/pretext/js/lib/jquery.espy.min.js new file mode 100755 index 0000000..31cffd1 --- /dev/null +++ b/slides-annotated/_static/pretext/js/lib/jquery.espy.min.js @@ -0,0 +1,195 @@ + +(function(b, u, v) { + function x(b, f, e) { + b = (b + "").match(/^(-?[0-9]+)(%)?$/); + if (!b) return !1; + var c = parseInt(b[1], 10); + b[2] && (c *= f / 100); + return 0 > c ? f + c + (e || 0) : c + } + + function y(k, f) { + function e() { + function b() { + g = +new Date; + f.apply(e, t); + c && (c = clearTimeout(c)) + } + var e = this, + q = +new Date - g, + t = arguments; + c && (c = clearTimeout(c)); + q > k ? b() : c = setTimeout(b, k - q) + } + var c, g = 0; + b.guid && (e.guid = f.guid = f.guid || b.guid++); + return e + } + b.Espy = function(k, f, e) { + function c(a, d) { + b.isPlainObject(a) && (d = a, a = null); + b.extend(t.prototype, d); + null !== a && (w = a) + } + + function g(a) { + if (a = + q(a)) { + var d = a.$el.offset()[a.settings.horizontal ? "left" : "top"] - p.offset[a.settings.horizontal ? "left" : "top"], + h = a.$el[a.settings.horizontal ? "outerWidth" : "outerHeight"](); + b.extend(a, { + start: d, + elSize: h, + end: d + h + }) + } + } + + function r(a) { + // console.log("r(a) with a = ", a); + if (a === v) b.each(m, r); + else if (a = q(a)) { + var d = p[a.settings.horizontal ? "width" : "height"], + h = x(a.settings.size, d), + d = p[a.settings.horizontal ? "left" : "top"] + x(a.settings.offset, d, -h), + c = d + h, + h = a.settings.contain ? d <= a.start && c >= a.end ? "inside" : d + h / 2 > a.start + a.elSize / 2 ? a.settings.horizontal ? "left" : + "up" : a.settings.horizontal ? "right" : "down" : d > a.start && d < a.end || c > a.start && c < a.end || d <= a.start && c >= a.start || d <= a.end && c >= a.end ? "inside" : d > a.end ? a.settings.horizontal ? "left" : "up" : a.settings.horizontal ? "right" : "down"; + a.state !== h && (a.state = h, "function" === typeof w && w.call(a.el, "inside" === h, h), "function" === typeof a.callback && a.callback.call(a.el, "inside" === h, h)) + } + } + + function s(a) { + if (m.hasOwnProperty(a)) return a; + if (b.isPlainObject(a) && m.hasOwnProperty(a.id)) return a.id; + a = b(a)[0]; + var d = !1; + b.each(m, function(b, + c) { + c.el === a && (d = b) + }); + return d + } + + // console.log("nothing yet"); + function q(a) { + return (a = s(a)) ? m[a] : !1 + } + "function" !== typeof f && (e = f, f = 0); + // console.log("k was", k); + var t = function(a) { + b.extend(this, a) + }, + u = function(a, d, c, e) { + this.id = a; + this.el = d; + this.$el = b(d); + this.callback = c; + this.settings = new t(e); + this.configure = function(a, d) { + b.isPlainObject(a) && (d = a, a = null); + b.extend(this.settings, d); + null !== a && (this.callback = a) + } + }, + n = this, + l = b(k); + // console.log("l", l, "l==window", l[0]==window); + k = b.fn.espy.defaults; + // console.log("k is", k); + var w, m = {}, + z = 0; + // because window.offset() is not defined in jQuery 3 (why?!?!?!), + // we have to treat that as a special case (DF 1/28/19) + if (l[0]==window) { offSET = { top: 0, left: 0 } } + else { offSET = l.offset() } + var p = { + top: l.scrollTop(), + left: l.scrollLeft(), + width: l.innerWidth(), + height: l.innerHeight(), + offset: offSET +/* + offset: l.offset() || { + top: 0, + left: 0 + } +*/ + }; + // console.log("p", p); + c(f, b.extend({}, k, e)); + n.add = function(a, d, c) { + b.isPlainObject(d) && (c = d, d = 0); + b(a).each(function(a, b) { + var e = s(b) || "s" + z++; + m[e] = new u(e, b, d, c); + g(e); + r(e) + }) + }; + n.configure = function(a, d, e) { + "function" === typeof a ? (d = a, a = null, b.isPlainObject(d) && (e = d, d = null)) : b.isPlainObject(a) ? (e = a, d = a = null) : b.isPlainObject(d) && (e = d, d = null); + null === a ? (c(d, e), b.each(m, function(a, b) { + g(b) + })) : b(a).each(function(a, b) { + var c = q(b); + c && (c.configure(d, e), g(b)) + }) + }; + n.reload = function(a) { + a === v ? b.each(m, function() { + g(this.id) + }) : + b(a).each(function(a, b) { + var c = s(b); + c && (g(c), r(c)) + }) + }; + n.remove = function(a) { + b(a).each(function(a, b) { + var c = s(b); + c && delete m[c] + }) + }; + n.destroy = function() { + l.off(".espy"); + m = {}; + n = v + }; + n.resize = function() { + b.each(m, function() { + this.reloadOnResize && g(this) + }); + p.width = l.innerWidth(); + p.height = l.innerHeight(); + r() + }; + l.on("scroll.espy", y(k.delay, function() { + p.top = l.scrollTop(); + p.left = l.scrollLeft(); + r() + })); + l.on("resize.espy", y(k.delay, function() { + n.resize() + })) + }; + b.fn.espy = function(k, f) { + var e, c; + e = f && f.context || u; + var g = b.data(e, + "espy") || b.data(e, "espy", new b.Espy(e)); + "string" !== typeof k ? g.add(this, k, f) : (e = k, c = Array.prototype.slice.call(arguments), c[0] = this, "function" === typeof g[e] && g[e].apply(g, c)); + return this + }; + b.fn.espy.defaults = { + delay: 100, + context: window, + horizontal: 0, + offset: 0, + size: "100%", + contain: 0, + reloadOnResize: !0 + } +})(jQuery, window); + + diff --git a/slides-annotated/_static/pretext/js/lib/jquery.min.js b/slides-annotated/_static/pretext/js/lib/jquery.min.js new file mode 100755 index 0000000..4d9b3a2 --- /dev/null +++ b/slides-annotated/_static/pretext/js/lib/jquery.min.js @@ -0,0 +1,2 @@ +/*! jQuery v3.3.1 | (c) JS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(e,t){"use strict";var n=[],r=e.document,i=Object.getPrototypeOf,o=n.slice,a=n.concat,s=n.push,u=n.indexOf,l={},c=l.toString,f=l.hasOwnProperty,p=f.toString,d=p.call(Object),h={},g=function e(t){return"function"==typeof t&&"number"!=typeof t.nodeType},y=function e(t){return null!=t&&t===t.window},v={type:!0,src:!0,noModule:!0};function m(e,t,n){var i,o=(t=t||r).createElement("script");if(o.text=e,n)for(i in v)n[i]&&(o[i]=n[i]);t.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?l[c.call(e)]||"object":typeof e}var b="3.3.1",w=function(e,t){return new w.fn.init(e,t)},T=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;w.fn=w.prototype={jquery:"3.3.1",constructor:w,length:0,toArray:function(){return o.call(this)},get:function(e){return null==e?o.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=w.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return w.each(this,e)},map:function(e){return this.pushStack(w.map(this,function(t,n){return e.call(t,n,t)}))},slice:function(){return this.pushStack(o.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(n>=0&&n0&&t-1 in e)}var E=function(e){var t,n,r,i,o,a,s,u,l,c,f,p,d,h,g,y,v,m,x,b="sizzle"+1*new Date,w=e.document,T=0,C=0,E=ae(),k=ae(),S=ae(),D=function(e,t){return e===t&&(f=!0),0},N={}.hasOwnProperty,A=[],j=A.pop,q=A.push,L=A.push,H=A.slice,O=function(e,t){for(var n=0,r=e.length;n+~]|"+M+")"+M+"*"),z=new RegExp("="+M+"*([^\\]'\"]*?)"+M+"*\\]","g"),X=new RegExp(W),U=new RegExp("^"+R+"$"),V={ID:new RegExp("^#("+R+")"),CLASS:new RegExp("^\\.("+R+")"),TAG:new RegExp("^("+R+"|[*])"),ATTR:new RegExp("^"+I),PSEUDO:new RegExp("^"+W),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+P+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},G=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Q=/^[^{]+\{\s*\[native \w/,J=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,K=/[+~]/,Z=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),ee=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},te=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ne=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},re=function(){p()},ie=me(function(e){return!0===e.disabled&&("form"in e||"label"in e)},{dir:"parentNode",next:"legend"});try{L.apply(A=H.call(w.childNodes),w.childNodes),A[w.childNodes.length].nodeType}catch(e){L={apply:A.length?function(e,t){q.apply(e,H.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function oe(e,t,r,i){var o,s,l,c,f,h,v,m=t&&t.ownerDocument,T=t?t.nodeType:9;if(r=r||[],"string"!=typeof e||!e||1!==T&&9!==T&&11!==T)return r;if(!i&&((t?t.ownerDocument||t:w)!==d&&p(t),t=t||d,g)){if(11!==T&&(f=J.exec(e)))if(o=f[1]){if(9===T){if(!(l=t.getElementById(o)))return r;if(l.id===o)return r.push(l),r}else if(m&&(l=m.getElementById(o))&&x(t,l)&&l.id===o)return r.push(l),r}else{if(f[2])return L.apply(r,t.getElementsByTagName(e)),r;if((o=f[3])&&n.getElementsByClassName&&t.getElementsByClassName)return L.apply(r,t.getElementsByClassName(o)),r}if(n.qsa&&!S[e+" "]&&(!y||!y.test(e))){if(1!==T)m=t,v=e;else if("object"!==t.nodeName.toLowerCase()){(c=t.getAttribute("id"))?c=c.replace(te,ne):t.setAttribute("id",c=b),s=(h=a(e)).length;while(s--)h[s]="#"+c+" "+ve(h[s]);v=h.join(","),m=K.test(e)&&ge(t.parentNode)||t}if(v)try{return L.apply(r,m.querySelectorAll(v)),r}catch(e){}finally{c===b&&t.removeAttribute("id")}}}return u(e.replace(B,"$1"),t,r,i)}function ae(){var e=[];function t(n,i){return e.push(n+" ")>r.cacheLength&&delete t[e.shift()],t[n+" "]=i}return t}function se(e){return e[b]=!0,e}function ue(e){var t=d.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function le(e,t){var n=e.split("|"),i=n.length;while(i--)r.attrHandle[n[i]]=t}function ce(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function fe(e){return function(t){return"input"===t.nodeName.toLowerCase()&&t.type===e}}function pe(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function de(e){return function(t){return"form"in t?t.parentNode&&!1===t.disabled?"label"in t?"label"in t.parentNode?t.parentNode.disabled===e:t.disabled===e:t.isDisabled===e||t.isDisabled!==!e&&ie(t)===e:t.disabled===e:"label"in t&&t.disabled===e}}function he(e){return se(function(t){return t=+t,se(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}function ge(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}n=oe.support={},o=oe.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return!!t&&"HTML"!==t.nodeName},p=oe.setDocument=function(e){var t,i,a=e?e.ownerDocument||e:w;return a!==d&&9===a.nodeType&&a.documentElement?(d=a,h=d.documentElement,g=!o(d),w!==d&&(i=d.defaultView)&&i.top!==i&&(i.addEventListener?i.addEventListener("unload",re,!1):i.attachEvent&&i.attachEvent("onunload",re)),n.attributes=ue(function(e){return e.className="i",!e.getAttribute("className")}),n.getElementsByTagName=ue(function(e){return e.appendChild(d.createComment("")),!e.getElementsByTagName("*").length}),n.getElementsByClassName=Q.test(d.getElementsByClassName),n.getById=ue(function(e){return h.appendChild(e).id=b,!d.getElementsByName||!d.getElementsByName(b).length}),n.getById?(r.filter.ID=function(e){var t=e.replace(Z,ee);return function(e){return e.getAttribute("id")===t}},r.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&g){var n=t.getElementById(e);return n?[n]:[]}}):(r.filter.ID=function(e){var t=e.replace(Z,ee);return function(e){var n="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return n&&n.value===t}},r.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&g){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),r.find.TAG=n.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):n.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},r.find.CLASS=n.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&g)return t.getElementsByClassName(e)},v=[],y=[],(n.qsa=Q.test(d.querySelectorAll))&&(ue(function(e){h.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&y.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||y.push("\\["+M+"*(?:value|"+P+")"),e.querySelectorAll("[id~="+b+"-]").length||y.push("~="),e.querySelectorAll(":checked").length||y.push(":checked"),e.querySelectorAll("a#"+b+"+*").length||y.push(".#.+[+~]")}),ue(function(e){e.innerHTML="";var t=d.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&y.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&y.push(":enabled",":disabled"),h.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&y.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),y.push(",.*:")})),(n.matchesSelector=Q.test(m=h.matches||h.webkitMatchesSelector||h.mozMatchesSelector||h.oMatchesSelector||h.msMatchesSelector))&&ue(function(e){n.disconnectedMatch=m.call(e,"*"),m.call(e,"[s!='']:x"),v.push("!=",W)}),y=y.length&&new RegExp(y.join("|")),v=v.length&&new RegExp(v.join("|")),t=Q.test(h.compareDocumentPosition),x=t||Q.test(h.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return f=!0,0;var r=!e.compareDocumentPosition-!t.compareDocumentPosition;return r||(1&(r=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!n.sortDetached&&t.compareDocumentPosition(e)===r?e===d||e.ownerDocument===w&&x(w,e)?-1:t===d||t.ownerDocument===w&&x(w,t)?1:c?O(c,e)-O(c,t):0:4&r?-1:1)}:function(e,t){if(e===t)return f=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===d?-1:t===d?1:i?-1:o?1:c?O(c,e)-O(c,t):0;if(i===o)return ce(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?ce(a[r],s[r]):a[r]===w?-1:s[r]===w?1:0},d):d},oe.matches=function(e,t){return oe(e,null,null,t)},oe.matchesSelector=function(e,t){if((e.ownerDocument||e)!==d&&p(e),t=t.replace(z,"='$1']"),n.matchesSelector&&g&&!S[t+" "]&&(!v||!v.test(t))&&(!y||!y.test(t)))try{var r=m.call(e,t);if(r||n.disconnectedMatch||e.document&&11!==e.document.nodeType)return r}catch(e){}return oe(t,d,null,[e]).length>0},oe.contains=function(e,t){return(e.ownerDocument||e)!==d&&p(e),x(e,t)},oe.attr=function(e,t){(e.ownerDocument||e)!==d&&p(e);var i=r.attrHandle[t.toLowerCase()],o=i&&N.call(r.attrHandle,t.toLowerCase())?i(e,t,!g):void 0;return void 0!==o?o:n.attributes||!g?e.getAttribute(t):(o=e.getAttributeNode(t))&&o.specified?o.value:null},oe.escape=function(e){return(e+"").replace(te,ne)},oe.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},oe.uniqueSort=function(e){var t,r=[],i=0,o=0;if(f=!n.detectDuplicates,c=!n.sortStable&&e.slice(0),e.sort(D),f){while(t=e[o++])t===e[o]&&(i=r.push(o));while(i--)e.splice(r[i],1)}return c=null,e},i=oe.getText=function(e){var t,n="",r=0,o=e.nodeType;if(o){if(1===o||9===o||11===o){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=i(e)}else if(3===o||4===o)return e.nodeValue}else while(t=e[r++])n+=i(t);return n},(r=oe.selectors={cacheLength:50,createPseudo:se,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(Z,ee),e[3]=(e[3]||e[4]||e[5]||"").replace(Z,ee),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||oe.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&oe.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return V.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=a(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(Z,ee).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=E[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&E(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=oe.attr(r,e);return null==i?"!="===t:!t||(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i.replace($," ")+" ").indexOf(n)>-1:"|="===t&&(i===n||i.slice(0,n.length+1)===n+"-"))}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,f,p,d,h,g=o!==a?"nextSibling":"previousSibling",y=t.parentNode,v=s&&t.nodeName.toLowerCase(),m=!u&&!s,x=!1;if(y){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===v:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?y.firstChild:y.lastChild],a&&m){x=(d=(l=(c=(f=(p=y)[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]||[])[0]===T&&l[1])&&l[2],p=d&&y.childNodes[d];while(p=++d&&p&&p[g]||(x=d=0)||h.pop())if(1===p.nodeType&&++x&&p===t){c[e]=[T,d,x];break}}else if(m&&(x=d=(l=(c=(f=(p=t)[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]||[])[0]===T&&l[1]),!1===x)while(p=++d&&p&&p[g]||(x=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===v:1===p.nodeType)&&++x&&(m&&((c=(f=p[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]=[T,x]),p===t))break;return(x-=i)===r||x%r==0&&x/r>=0}}},PSEUDO:function(e,t){var n,i=r.pseudos[e]||r.setFilters[e.toLowerCase()]||oe.error("unsupported pseudo: "+e);return i[b]?i(t):i.length>1?(n=[e,e,"",t],r.setFilters.hasOwnProperty(e.toLowerCase())?se(function(e,n){var r,o=i(e,t),a=o.length;while(a--)e[r=O(e,o[a])]=!(n[r]=o[a])}):function(e){return i(e,0,n)}):i}},pseudos:{not:se(function(e){var t=[],n=[],r=s(e.replace(B,"$1"));return r[b]?se(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),t[0]=null,!n.pop()}}),has:se(function(e){return function(t){return oe(e,t).length>0}}),contains:se(function(e){return e=e.replace(Z,ee),function(t){return(t.textContent||t.innerText||i(t)).indexOf(e)>-1}}),lang:se(function(e){return U.test(e||"")||oe.error("unsupported lang: "+e),e=e.replace(Z,ee).toLowerCase(),function(t){var n;do{if(n=g?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return(n=n.toLowerCase())===e||0===n.indexOf(e+"-")}while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===h},focus:function(e){return e===d.activeElement&&(!d.hasFocus||d.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:de(!1),disabled:de(!0),checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!r.pseudos.empty(e)},header:function(e){return Y.test(e.nodeName)},input:function(e){return G.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:he(function(){return[0]}),last:he(function(e,t){return[t-1]}),eq:he(function(e,t,n){return[n<0?n+t:n]}),even:he(function(e,t){for(var n=0;n=0;)e.push(r);return e}),gt:he(function(e,t,n){for(var r=n<0?n+t:n;++r1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function be(e,t,n){for(var r=0,i=t.length;r-1&&(o[l]=!(a[l]=f))}}else v=we(v===a?v.splice(h,v.length):v),i?i(null,a,v,u):L.apply(a,v)})}function Ce(e){for(var t,n,i,o=e.length,a=r.relative[e[0].type],s=a||r.relative[" "],u=a?1:0,c=me(function(e){return e===t},s,!0),f=me(function(e){return O(t,e)>-1},s,!0),p=[function(e,n,r){var i=!a&&(r||n!==l)||((t=n).nodeType?c(e,n,r):f(e,n,r));return t=null,i}];u1&&xe(p),u>1&&ve(e.slice(0,u-1).concat({value:" "===e[u-2].type?"*":""})).replace(B,"$1"),n,u0,i=e.length>0,o=function(o,a,s,u,c){var f,h,y,v=0,m="0",x=o&&[],b=[],w=l,C=o||i&&r.find.TAG("*",c),E=T+=null==w?1:Math.random()||.1,k=C.length;for(c&&(l=a===d||a||c);m!==k&&null!=(f=C[m]);m++){if(i&&f){h=0,a||f.ownerDocument===d||(p(f),s=!g);while(y=e[h++])if(y(f,a||d,s)){u.push(f);break}c&&(T=E)}n&&((f=!y&&f)&&v--,o&&x.push(f))}if(v+=m,n&&m!==v){h=0;while(y=t[h++])y(x,b,a,s);if(o){if(v>0)while(m--)x[m]||b[m]||(b[m]=j.call(u));b=we(b)}L.apply(u,b),c&&!o&&b.length>0&&v+t.length>1&&oe.uniqueSort(u)}return c&&(T=E,l=w),x};return n?se(o):o}return s=oe.compile=function(e,t){var n,r=[],i=[],o=S[e+" "];if(!o){t||(t=a(e)),n=t.length;while(n--)(o=Ce(t[n]))[b]?r.push(o):i.push(o);(o=S(e,Ee(i,r))).selector=e}return o},u=oe.select=function(e,t,n,i){var o,u,l,c,f,p="function"==typeof e&&e,d=!i&&a(e=p.selector||e);if(n=n||[],1===d.length){if((u=d[0]=d[0].slice(0)).length>2&&"ID"===(l=u[0]).type&&9===t.nodeType&&g&&r.relative[u[1].type]){if(!(t=(r.find.ID(l.matches[0].replace(Z,ee),t)||[])[0]))return n;p&&(t=t.parentNode),e=e.slice(u.shift().value.length)}o=V.needsContext.test(e)?0:u.length;while(o--){if(l=u[o],r.relative[c=l.type])break;if((f=r.find[c])&&(i=f(l.matches[0].replace(Z,ee),K.test(u[0].type)&&ge(t.parentNode)||t))){if(u.splice(o,1),!(e=i.length&&ve(u)))return L.apply(n,i),n;break}}}return(p||s(e,d))(i,t,!g,n,!t||K.test(e)&&ge(t.parentNode)||t),n},n.sortStable=b.split("").sort(D).join("")===b,n.detectDuplicates=!!f,p(),n.sortDetached=ue(function(e){return 1&e.compareDocumentPosition(d.createElement("fieldset"))}),ue(function(e){return e.innerHTML="","#"===e.firstChild.getAttribute("href")})||le("type|href|height|width",function(e,t,n){if(!n)return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),n.attributes&&ue(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||le("value",function(e,t,n){if(!n&&"input"===e.nodeName.toLowerCase())return e.defaultValue}),ue(function(e){return null==e.getAttribute("disabled")})||le(P,function(e,t,n){var r;if(!n)return!0===e[t]?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),oe}(e);w.find=E,w.expr=E.selectors,w.expr[":"]=w.expr.pseudos,w.uniqueSort=w.unique=E.uniqueSort,w.text=E.getText,w.isXMLDoc=E.isXML,w.contains=E.contains,w.escapeSelector=E.escape;var k=function(e,t,n){var r=[],i=void 0!==n;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&w(e).is(n))break;r.push(e)}return r},S=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},D=w.expr.match.needsContext;function N(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}var A=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,t,n){return g(t)?w.grep(e,function(e,r){return!!t.call(e,r,e)!==n}):t.nodeType?w.grep(e,function(e){return e===t!==n}):"string"!=typeof t?w.grep(e,function(e){return u.call(t,e)>-1!==n}):w.filter(t,e,n)}w.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?w.find.matchesSelector(r,e)?[r]:[]:w.find.matches(e,w.grep(t,function(e){return 1===e.nodeType}))},w.fn.extend({find:function(e){var t,n,r=this.length,i=this;if("string"!=typeof e)return this.pushStack(w(e).filter(function(){for(t=0;t1?w.uniqueSort(n):n},filter:function(e){return this.pushStack(j(this,e||[],!1))},not:function(e){return this.pushStack(j(this,e||[],!0))},is:function(e){return!!j(this,"string"==typeof e&&D.test(e)?w(e):e||[],!1).length}});var q,L=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(w.fn.init=function(e,t,n){var i,o;if(!e)return this;if(n=n||q,"string"==typeof e){if(!(i="<"===e[0]&&">"===e[e.length-1]&&e.length>=3?[null,e,null]:L.exec(e))||!i[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(i[1]){if(t=t instanceof w?t[0]:t,w.merge(this,w.parseHTML(i[1],t&&t.nodeType?t.ownerDocument||t:r,!0)),A.test(i[1])&&w.isPlainObject(t))for(i in t)g(this[i])?this[i](t[i]):this.attr(i,t[i]);return this}return(o=r.getElementById(i[2]))&&(this[0]=o,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):g(e)?void 0!==n.ready?n.ready(e):e(w):w.makeArray(e,this)}).prototype=w.fn,q=w(r);var H=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};w.fn.extend({has:function(e){var t=w(e,this),n=t.length;return this.filter(function(){for(var e=0;e-1:1===n.nodeType&&w.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(o.length>1?w.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?u.call(w(e),this[0]):u.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(w.uniqueSort(w.merge(this.get(),w(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}w.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return k(e,"parentNode")},parentsUntil:function(e,t,n){return k(e,"parentNode",n)},next:function(e){return P(e,"nextSibling")},prev:function(e){return P(e,"previousSibling")},nextAll:function(e){return k(e,"nextSibling")},prevAll:function(e){return k(e,"previousSibling")},nextUntil:function(e,t,n){return k(e,"nextSibling",n)},prevUntil:function(e,t,n){return k(e,"previousSibling",n)},siblings:function(e){return S((e.parentNode||{}).firstChild,e)},children:function(e){return S(e.firstChild)},contents:function(e){return N(e,"iframe")?e.contentDocument:(N(e,"template")&&(e=e.content||e),w.merge([],e.childNodes))}},function(e,t){w.fn[e]=function(n,r){var i=w.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=w.filter(r,i)),this.length>1&&(O[e]||w.uniqueSort(i),H.test(e)&&i.reverse()),this.pushStack(i)}});var M=/[^\x20\t\r\n\f]+/g;function R(e){var t={};return w.each(e.match(M)||[],function(e,n){t[n]=!0}),t}w.Callbacks=function(e){e="string"==typeof e?R(e):w.extend({},e);var t,n,r,i,o=[],a=[],s=-1,u=function(){for(i=i||e.once,r=t=!0;a.length;s=-1){n=a.shift();while(++s-1)o.splice(n,1),n<=s&&s--}),this},has:function(e){return e?w.inArray(e,o)>-1:o.length>0},empty:function(){return o&&(o=[]),this},disable:function(){return i=a=[],o=n="",this},disabled:function(){return!o},lock:function(){return i=a=[],n||t||(o=n=""),this},locked:function(){return!!i},fireWith:function(e,n){return i||(n=[e,(n=n||[]).slice?n.slice():n],a.push(n),t||u()),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!r}};return l};function I(e){return e}function W(e){throw e}function $(e,t,n,r){var i;try{e&&g(i=e.promise)?i.call(e).done(t).fail(n):e&&g(i=e.then)?i.call(e,t,n):t.apply(void 0,[e].slice(r))}catch(e){n.apply(void 0,[e])}}w.extend({Deferred:function(t){var n=[["notify","progress",w.Callbacks("memory"),w.Callbacks("memory"),2],["resolve","done",w.Callbacks("once memory"),w.Callbacks("once memory"),0,"resolved"],["reject","fail",w.Callbacks("once memory"),w.Callbacks("once memory"),1,"rejected"]],r="pending",i={state:function(){return r},always:function(){return o.done(arguments).fail(arguments),this},"catch":function(e){return i.then(null,e)},pipe:function(){var e=arguments;return w.Deferred(function(t){w.each(n,function(n,r){var i=g(e[r[4]])&&e[r[4]];o[r[1]](function(){var e=i&&i.apply(this,arguments);e&&g(e.promise)?e.promise().progress(t.notify).done(t.resolve).fail(t.reject):t[r[0]+"With"](this,i?[e]:arguments)})}),e=null}).promise()},then:function(t,r,i){var o=0;function a(t,n,r,i){return function(){var s=this,u=arguments,l=function(){var e,l;if(!(t=o&&(r!==W&&(s=void 0,u=[e]),n.rejectWith(s,u))}};t?c():(w.Deferred.getStackHook&&(c.stackTrace=w.Deferred.getStackHook()),e.setTimeout(c))}}return w.Deferred(function(e){n[0][3].add(a(0,e,g(i)?i:I,e.notifyWith)),n[1][3].add(a(0,e,g(t)?t:I)),n[2][3].add(a(0,e,g(r)?r:W))}).promise()},promise:function(e){return null!=e?w.extend(e,i):i}},o={};return w.each(n,function(e,t){var a=t[2],s=t[5];i[t[1]]=a.add,s&&a.add(function(){r=s},n[3-e][2].disable,n[3-e][3].disable,n[0][2].lock,n[0][3].lock),a.add(t[3].fire),o[t[0]]=function(){return o[t[0]+"With"](this===o?void 0:this,arguments),this},o[t[0]+"With"]=a.fireWith}),i.promise(o),t&&t.call(o,o),o},when:function(e){var t=arguments.length,n=t,r=Array(n),i=o.call(arguments),a=w.Deferred(),s=function(e){return function(n){r[e]=this,i[e]=arguments.length>1?o.call(arguments):n,--t||a.resolveWith(r,i)}};if(t<=1&&($(e,a.done(s(n)).resolve,a.reject,!t),"pending"===a.state()||g(i[n]&&i[n].then)))return a.then();while(n--)$(i[n],s(n),a.reject);return a.promise()}});var B=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;w.Deferred.exceptionHook=function(t,n){e.console&&e.console.warn&&t&&B.test(t.name)&&e.console.warn("jQuery.Deferred exception: "+t.message,t.stack,n)},w.readyException=function(t){e.setTimeout(function(){throw t})};var F=w.Deferred();w.fn.ready=function(e){return F.then(e)["catch"](function(e){w.readyException(e)}),this},w.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--w.readyWait:w.isReady)||(w.isReady=!0,!0!==e&&--w.readyWait>0||F.resolveWith(r,[w]))}}),w.ready.then=F.then;function _(){r.removeEventListener("DOMContentLoaded",_),e.removeEventListener("load",_),w.ready()}"complete"===r.readyState||"loading"!==r.readyState&&!r.documentElement.doScroll?e.setTimeout(w.ready):(r.addEventListener("DOMContentLoaded",_),e.addEventListener("load",_));var z=function(e,t,n,r,i,o,a){var s=0,u=e.length,l=null==n;if("object"===x(n)){i=!0;for(s in n)z(e,t,s,n[s],!0,o,a)}else if(void 0!==r&&(i=!0,g(r)||(a=!0),l&&(a?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(w(e),n)})),t))for(;s1,null,!0)},removeData:function(e){return this.each(function(){K.remove(this,e)})}}),w.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=J.get(e,t),n&&(!r||Array.isArray(n)?r=J.access(e,t,w.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=w.queue(e,t),r=n.length,i=n.shift(),o=w._queueHooks(e,t),a=function(){w.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return J.get(e,n)||J.access(e,n,{empty:w.Callbacks("once memory").add(function(){J.remove(e,[t+"queue",n])})})}}),w.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),arguments.length\x20\t\r\n\f]+)/i,he=/^$|^module$|\/(?:java|ecma)script/i,ge={option:[1,""],thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};ge.optgroup=ge.option,ge.tbody=ge.tfoot=ge.colgroup=ge.caption=ge.thead,ge.th=ge.td;function ye(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&N(e,t)?w.merge([e],n):n}function ve(e,t){for(var n=0,r=e.length;n-1)i&&i.push(o);else if(l=w.contains(o.ownerDocument,o),a=ye(f.appendChild(o),"script"),l&&ve(a),n){c=0;while(o=a[c++])he.test(o.type||"")&&n.push(o)}return f}!function(){var e=r.createDocumentFragment().appendChild(r.createElement("div")),t=r.createElement("input");t.setAttribute("type","radio"),t.setAttribute("checked","checked"),t.setAttribute("name","t"),e.appendChild(t),h.checkClone=e.cloneNode(!0).cloneNode(!0).lastChild.checked,e.innerHTML="",h.noCloneChecked=!!e.cloneNode(!0).lastChild.defaultValue}();var be=r.documentElement,we=/^key/,Te=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ce=/^([^.]*)(?:\.(.+)|)/;function Ee(){return!0}function ke(){return!1}function Se(){try{return r.activeElement}catch(e){}}function De(e,t,n,r,i,o){var a,s;if("object"==typeof t){"string"!=typeof n&&(r=r||n,n=void 0);for(s in t)De(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=ke;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return w().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=w.guid++)),e.each(function(){w.event.add(this,t,i,r,n)})}w.event={global:{},add:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,y=J.get(e);if(y){n.handler&&(n=(o=n).handler,i=o.selector),i&&w.find.matchesSelector(be,i),n.guid||(n.guid=w.guid++),(u=y.events)||(u=y.events={}),(a=y.handle)||(a=y.handle=function(t){return"undefined"!=typeof w&&w.event.triggered!==t.type?w.event.dispatch.apply(e,arguments):void 0}),l=(t=(t||"").match(M)||[""]).length;while(l--)d=g=(s=Ce.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=w.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=w.event.special[d]||{},c=w.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&w.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(e,r,h,a)||e.addEventListener&&e.addEventListener(d,a)),f.add&&(f.add.call(e,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),w.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,y=J.hasData(e)&&J.get(e);if(y&&(u=y.events)){l=(t=(t||"").match(M)||[""]).length;while(l--)if(s=Ce.exec(t[l])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){f=w.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,y.handle)||w.removeEvent(e,d,y.handle),delete u[d])}else for(d in u)w.event.remove(e,d+t[l],n,r,!0);w.isEmptyObject(u)&&J.remove(e,"handle events")}},dispatch:function(e){var t=w.event.fix(e),n,r,i,o,a,s,u=new Array(arguments.length),l=(J.get(this,"events")||{})[t.type]||[],c=w.event.special[t.type]||{};for(u[0]=t,n=1;n=1))for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&("click"!==e.type||!0!==l.disabled)){for(o=[],a={},n=0;n-1:w.find(i,this,null,[l]).length),a[i]&&o.push(r);o.length&&s.push({elem:l,handlers:o})}return l=this,u\x20\t\r\n\f]*)[^>]*)\/>/gi,Ae=/\s*$/g;function Le(e,t){return N(e,"table")&&N(11!==t.nodeType?t:t.firstChild,"tr")?w(e).children("tbody")[0]||e:e}function He(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Oe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Pe(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(J.hasData(e)&&(o=J.access(e),a=J.set(t,o),l=o.events)){delete a.handle,a.events={};for(i in l)for(n=0,r=l[i].length;n1&&"string"==typeof y&&!h.checkClone&&je.test(y))return e.each(function(i){var o=e.eq(i);v&&(t[0]=y.call(this,i,o.html())),Re(o,t,n,r)});if(p&&(i=xe(t,e[0].ownerDocument,!1,e,r),o=i.firstChild,1===i.childNodes.length&&(i=o),o||r)){for(u=(s=w.map(ye(i,"script"),He)).length;f")},clone:function(e,t,n){var r,i,o,a,s=e.cloneNode(!0),u=w.contains(e.ownerDocument,e);if(!(h.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||w.isXMLDoc(e)))for(a=ye(s),r=0,i=(o=ye(e)).length;r0&&ve(a,!u&&ye(e,"script")),s},cleanData:function(e){for(var t,n,r,i=w.event.special,o=0;void 0!==(n=e[o]);o++)if(Y(n)){if(t=n[J.expando]){if(t.events)for(r in t.events)i[r]?w.event.remove(n,r):w.removeEvent(n,r,t.handle);n[J.expando]=void 0}n[K.expando]&&(n[K.expando]=void 0)}}}),w.fn.extend({detach:function(e){return Ie(this,e,!0)},remove:function(e){return Ie(this,e)},text:function(e){return z(this,function(e){return void 0===e?w.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return Re(this,arguments,function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||Le(this,e).appendChild(e)})},prepend:function(){return Re(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Le(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return Re(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return Re(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(w.cleanData(ye(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return w.clone(this,e,t)})},html:function(e){return z(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!Ae.test(e)&&!ge[(de.exec(e)||["",""])[1].toLowerCase()]){e=w.htmlPrefilter(e);try{for(;n=0&&(u+=Math.max(0,Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-o-u-s-.5))),u}function et(e,t,n){var r=$e(e),i=Fe(e,t,r),o="border-box"===w.css(e,"boxSizing",!1,r),a=o;if(We.test(i)){if(!n)return i;i="auto"}return a=a&&(h.boxSizingReliable()||i===e.style[t]),("auto"===i||!parseFloat(i)&&"inline"===w.css(e,"display",!1,r))&&(i=e["offset"+t[0].toUpperCase()+t.slice(1)],a=!0),(i=parseFloat(i)||0)+Ze(e,t,n||(o?"border":"content"),a,r,i)+"px"}w.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Fe(e,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,s=G(t),u=Xe.test(t),l=e.style;if(u||(t=Je(s)),a=w.cssHooks[t]||w.cssHooks[s],void 0===n)return a&&"get"in a&&void 0!==(i=a.get(e,!1,r))?i:l[t];"string"==(o=typeof n)&&(i=ie.exec(n))&&i[1]&&(n=ue(e,t,i),o="number"),null!=n&&n===n&&("number"===o&&(n+=i&&i[3]||(w.cssNumber[s]?"":"px")),h.clearCloneStyle||""!==n||0!==t.indexOf("background")||(l[t]="inherit"),a&&"set"in a&&void 0===(n=a.set(e,n,r))||(u?l.setProperty(t,n):l[t]=n))}},css:function(e,t,n,r){var i,o,a,s=G(t);return Xe.test(t)||(t=Je(s)),(a=w.cssHooks[t]||w.cssHooks[s])&&"get"in a&&(i=a.get(e,!0,n)),void 0===i&&(i=Fe(e,t,r)),"normal"===i&&t in Ve&&(i=Ve[t]),""===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),w.each(["height","width"],function(e,t){w.cssHooks[t]={get:function(e,n,r){if(n)return!ze.test(w.css(e,"display"))||e.getClientRects().length&&e.getBoundingClientRect().width?et(e,t,r):se(e,Ue,function(){return et(e,t,r)})},set:function(e,n,r){var i,o=$e(e),a="border-box"===w.css(e,"boxSizing",!1,o),s=r&&Ze(e,t,r,a,o);return a&&h.scrollboxSize()===o.position&&(s-=Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-parseFloat(o[t])-Ze(e,t,"border",!1,o)-.5)),s&&(i=ie.exec(n))&&"px"!==(i[3]||"px")&&(e.style[t]=n,n=w.css(e,t)),Ke(e,n,s)}}}),w.cssHooks.marginLeft=_e(h.reliableMarginLeft,function(e,t){if(t)return(parseFloat(Fe(e,"marginLeft"))||e.getBoundingClientRect().left-se(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}))+"px"}),w.each({margin:"",padding:"",border:"Width"},function(e,t){w.cssHooks[e+t]={expand:function(n){for(var r=0,i={},o="string"==typeof n?n.split(" "):[n];r<4;r++)i[e+oe[r]+t]=o[r]||o[r-2]||o[0];return i}},"margin"!==e&&(w.cssHooks[e+t].set=Ke)}),w.fn.extend({css:function(e,t){return z(this,function(e,t,n){var r,i,o={},a=0;if(Array.isArray(t)){for(r=$e(e),i=t.length;a1)}});function tt(e,t,n,r,i){return new tt.prototype.init(e,t,n,r,i)}w.Tween=tt,tt.prototype={constructor:tt,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||w.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(w.cssNumber[n]?"":"px")},cur:function(){var e=tt.propHooks[this.prop];return e&&e.get?e.get(this):tt.propHooks._default.get(this)},run:function(e){var t,n=tt.propHooks[this.prop];return this.options.duration?this.pos=t=w.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):tt.propHooks._default.set(this),this}},tt.prototype.init.prototype=tt.prototype,tt.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=w.css(e.elem,e.prop,""))&&"auto"!==t?t:0},set:function(e){w.fx.step[e.prop]?w.fx.step[e.prop](e):1!==e.elem.nodeType||null==e.elem.style[w.cssProps[e.prop]]&&!w.cssHooks[e.prop]?e.elem[e.prop]=e.now:w.style(e.elem,e.prop,e.now+e.unit)}}},tt.propHooks.scrollTop=tt.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},w.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},w.fx=tt.prototype.init,w.fx.step={};var nt,rt,it=/^(?:toggle|show|hide)$/,ot=/queueHooks$/;function at(){rt&&(!1===r.hidden&&e.requestAnimationFrame?e.requestAnimationFrame(at):e.setTimeout(at,w.fx.interval),w.fx.tick())}function st(){return e.setTimeout(function(){nt=void 0}),nt=Date.now()}function ut(e,t){var n,r=0,i={height:e};for(t=t?1:0;r<4;r+=2-t)i["margin"+(n=oe[r])]=i["padding"+n]=e;return t&&(i.opacity=i.width=e),i}function lt(e,t,n){for(var r,i=(pt.tweeners[t]||[]).concat(pt.tweeners["*"]),o=0,a=i.length;o1)},removeAttr:function(e){return this.each(function(){w.removeAttr(this,e)})}}),w.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return"undefined"==typeof e.getAttribute?w.prop(e,t,n):(1===o&&w.isXMLDoc(e)||(i=w.attrHooks[t.toLowerCase()]||(w.expr.match.bool.test(t)?dt:void 0)),void 0!==n?null===n?void w.removeAttr(e,t):i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+""),n):i&&"get"in i&&null!==(r=i.get(e,t))?r:null==(r=w.find.attr(e,t))?void 0:r)},attrHooks:{type:{set:function(e,t){if(!h.radioValue&&"radio"===t&&N(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,i=t&&t.match(M);if(i&&1===e.nodeType)while(n=i[r++])e.removeAttribute(n)}}),dt={set:function(e,t,n){return!1===t?w.removeAttr(e,n):e.setAttribute(n,n),n}},w.each(w.expr.match.bool.source.match(/\w+/g),function(e,t){var n=ht[t]||w.find.attr;ht[t]=function(e,t,r){var i,o,a=t.toLowerCase();return r||(o=ht[a],ht[a]=i,i=null!=n(e,t,r)?a:null,ht[a]=o),i}});var gt=/^(?:input|select|textarea|button)$/i,yt=/^(?:a|area)$/i;w.fn.extend({prop:function(e,t){return z(this,w.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[w.propFix[e]||e]})}}),w.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&w.isXMLDoc(e)||(t=w.propFix[t]||t,i=w.propHooks[t]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=w.find.attr(e,"tabindex");return t?parseInt(t,10):gt.test(e.nodeName)||yt.test(e.nodeName)&&e.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),h.optSelected||(w.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),w.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){w.propFix[this.toLowerCase()]=this});function vt(e){return(e.match(M)||[]).join(" ")}function mt(e){return e.getAttribute&&e.getAttribute("class")||""}function xt(e){return Array.isArray(e)?e:"string"==typeof e?e.match(M)||[]:[]}w.fn.extend({addClass:function(e){var t,n,r,i,o,a,s,u=0;if(g(e))return this.each(function(t){w(this).addClass(e.call(this,t,mt(this)))});if((t=xt(e)).length)while(n=this[u++])if(i=mt(n),r=1===n.nodeType&&" "+vt(i)+" "){a=0;while(o=t[a++])r.indexOf(" "+o+" ")<0&&(r+=o+" ");i!==(s=vt(r))&&n.setAttribute("class",s)}return this},removeClass:function(e){var t,n,r,i,o,a,s,u=0;if(g(e))return this.each(function(t){w(this).removeClass(e.call(this,t,mt(this)))});if(!arguments.length)return this.attr("class","");if((t=xt(e)).length)while(n=this[u++])if(i=mt(n),r=1===n.nodeType&&" "+vt(i)+" "){a=0;while(o=t[a++])while(r.indexOf(" "+o+" ")>-1)r=r.replace(" "+o+" "," ");i!==(s=vt(r))&&n.setAttribute("class",s)}return this},toggleClass:function(e,t){var n=typeof e,r="string"===n||Array.isArray(e);return"boolean"==typeof t&&r?t?this.addClass(e):this.removeClass(e):g(e)?this.each(function(n){w(this).toggleClass(e.call(this,n,mt(this),t),t)}):this.each(function(){var t,i,o,a;if(r){i=0,o=w(this),a=xt(e);while(t=a[i++])o.hasClass(t)?o.removeClass(t):o.addClass(t)}else void 0!==e&&"boolean"!==n||((t=mt(this))&&J.set(this,"__className__",t),this.setAttribute&&this.setAttribute("class",t||!1===e?"":J.get(this,"__className__")||""))})},hasClass:function(e){var t,n,r=0;t=" "+e+" ";while(n=this[r++])if(1===n.nodeType&&(" "+vt(mt(n))+" ").indexOf(t)>-1)return!0;return!1}});var bt=/\r/g;w.fn.extend({val:function(e){var t,n,r,i=this[0];{if(arguments.length)return r=g(e),this.each(function(n){var i;1===this.nodeType&&(null==(i=r?e.call(this,n,w(this).val()):e)?i="":"number"==typeof i?i+="":Array.isArray(i)&&(i=w.map(i,function(e){return null==e?"":e+""})),(t=w.valHooks[this.type]||w.valHooks[this.nodeName.toLowerCase()])&&"set"in t&&void 0!==t.set(this,i,"value")||(this.value=i))});if(i)return(t=w.valHooks[i.type]||w.valHooks[i.nodeName.toLowerCase()])&&"get"in t&&void 0!==(n=t.get(i,"value"))?n:"string"==typeof(n=i.value)?n.replace(bt,""):null==n?"":n}}}),w.extend({valHooks:{option:{get:function(e){var t=w.find.attr(e,"value");return null!=t?t:vt(w.text(e))}},select:{get:function(e){var t,n,r,i=e.options,o=e.selectedIndex,a="select-one"===e.type,s=a?null:[],u=a?o+1:i.length;for(r=o<0?u:a?o:0;r-1)&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),w.each(["radio","checkbox"],function(){w.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=w.inArray(w(e).val(),t)>-1}},h.checkOn||(w.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})}),h.focusin="onfocusin"in e;var wt=/^(?:focusinfocus|focusoutblur)$/,Tt=function(e){e.stopPropagation()};w.extend(w.event,{trigger:function(t,n,i,o){var a,s,u,l,c,p,d,h,v=[i||r],m=f.call(t,"type")?t.type:t,x=f.call(t,"namespace")?t.namespace.split("."):[];if(s=h=u=i=i||r,3!==i.nodeType&&8!==i.nodeType&&!wt.test(m+w.event.triggered)&&(m.indexOf(".")>-1&&(m=(x=m.split(".")).shift(),x.sort()),c=m.indexOf(":")<0&&"on"+m,t=t[w.expando]?t:new w.Event(m,"object"==typeof t&&t),t.isTrigger=o?2:3,t.namespace=x.join("."),t.rnamespace=t.namespace?new RegExp("(^|\\.)"+x.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=void 0,t.target||(t.target=i),n=null==n?[t]:w.makeArray(n,[t]),d=w.event.special[m]||{},o||!d.trigger||!1!==d.trigger.apply(i,n))){if(!o&&!d.noBubble&&!y(i)){for(l=d.delegateType||m,wt.test(l+m)||(s=s.parentNode);s;s=s.parentNode)v.push(s),u=s;u===(i.ownerDocument||r)&&v.push(u.defaultView||u.parentWindow||e)}a=0;while((s=v[a++])&&!t.isPropagationStopped())h=s,t.type=a>1?l:d.bindType||m,(p=(J.get(s,"events")||{})[t.type]&&J.get(s,"handle"))&&p.apply(s,n),(p=c&&s[c])&&p.apply&&Y(s)&&(t.result=p.apply(s,n),!1===t.result&&t.preventDefault());return t.type=m,o||t.isDefaultPrevented()||d._default&&!1!==d._default.apply(v.pop(),n)||!Y(i)||c&&g(i[m])&&!y(i)&&((u=i[c])&&(i[c]=null),w.event.triggered=m,t.isPropagationStopped()&&h.addEventListener(m,Tt),i[m](),t.isPropagationStopped()&&h.removeEventListener(m,Tt),w.event.triggered=void 0,u&&(i[c]=u)),t.result}},simulate:function(e,t,n){var r=w.extend(new w.Event,n,{type:e,isSimulated:!0});w.event.trigger(r,null,t)}}),w.fn.extend({trigger:function(e,t){return this.each(function(){w.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return w.event.trigger(e,t,n,!0)}}),h.focusin||w.each({focus:"focusin",blur:"focusout"},function(e,t){var n=function(e){w.event.simulate(t,e.target,w.event.fix(e))};w.event.special[t]={setup:function(){var r=this.ownerDocument||this,i=J.access(r,t);i||r.addEventListener(e,n,!0),J.access(r,t,(i||0)+1)},teardown:function(){var r=this.ownerDocument||this,i=J.access(r,t)-1;i?J.access(r,t,i):(r.removeEventListener(e,n,!0),J.remove(r,t))}}});var Ct=e.location,Et=Date.now(),kt=/\?/;w.parseXML=function(t){var n;if(!t||"string"!=typeof t)return null;try{n=(new e.DOMParser).parseFromString(t,"text/xml")}catch(e){n=void 0}return n&&!n.getElementsByTagName("parsererror").length||w.error("Invalid XML: "+t),n};var St=/\[\]$/,Dt=/\r?\n/g,Nt=/^(?:submit|button|image|reset|file)$/i,At=/^(?:input|select|textarea|keygen)/i;function jt(e,t,n,r){var i;if(Array.isArray(t))w.each(t,function(t,i){n||St.test(e)?r(e,i):jt(e+"["+("object"==typeof i&&null!=i?t:"")+"]",i,n,r)});else if(n||"object"!==x(t))r(e,t);else for(i in t)jt(e+"["+i+"]",t[i],n,r)}w.param=function(e,t){var n,r=[],i=function(e,t){var n=g(t)?t():t;r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)};if(Array.isArray(e)||e.jquery&&!w.isPlainObject(e))w.each(e,function(){i(this.name,this.value)});else for(n in e)jt(n,e[n],t,i);return r.join("&")},w.fn.extend({serialize:function(){return w.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=w.prop(this,"elements");return e?w.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!w(this).is(":disabled")&&At.test(this.nodeName)&&!Nt.test(e)&&(this.checked||!pe.test(e))}).map(function(e,t){var n=w(this).val();return null==n?null:Array.isArray(n)?w.map(n,function(e){return{name:t.name,value:e.replace(Dt,"\r\n")}}):{name:t.name,value:n.replace(Dt,"\r\n")}}).get()}});var qt=/%20/g,Lt=/#.*$/,Ht=/([?&])_=[^&]*/,Ot=/^(.*?):[ \t]*([^\r\n]*)$/gm,Pt=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Mt=/^(?:GET|HEAD)$/,Rt=/^\/\//,It={},Wt={},$t="*/".concat("*"),Bt=r.createElement("a");Bt.href=Ct.href;function Ft(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(M)||[];if(g(n))while(r=o[i++])"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function _t(e,t,n,r){var i={},o=e===Wt;function a(s){var u;return i[s]=!0,w.each(e[s]||[],function(e,s){var l=s(t,n,r);return"string"!=typeof l||o||i[l]?o?!(u=l):void 0:(t.dataTypes.unshift(l),a(l),!1)}),u}return a(t.dataTypes[0])||!i["*"]&&a("*")}function zt(e,t){var n,r,i=w.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&w.extend(!0,e,r),e}function Xt(e,t,n){var r,i,o,a,s=e.contents,u=e.dataTypes;while("*"===u[0])u.shift(),void 0===r&&(r=e.mimeType||t.getResponseHeader("Content-Type"));if(r)for(i in s)if(s[i]&&s[i].test(r)){u.unshift(i);break}if(u[0]in n)o=u[0];else{for(i in n){if(!u[0]||e.converters[i+" "+u[0]]){o=i;break}a||(a=i)}o=o||a}if(o)return o!==u[0]&&u.unshift(o),n[o]}function Ut(e,t,n,r){var i,o,a,s,u,l={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)l[a.toLowerCase()]=e.converters[a];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!u&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u=o,o=c.shift())if("*"===o)o=u;else if("*"!==u&&u!==o){if(!(a=l[u+" "+o]||l["* "+o]))for(i in l)if((s=i.split(" "))[1]===o&&(a=l[u+" "+s[0]]||l["* "+s[0]])){!0===a?a=l[i]:!0!==l[i]&&(o=s[0],c.unshift(s[1]));break}if(!0!==a)if(a&&e["throws"])t=a(t);else try{t=a(t)}catch(e){return{state:"parsererror",error:a?e:"No conversion from "+u+" to "+o}}}return{state:"success",data:t}}w.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Ct.href,type:"GET",isLocal:Pt.test(Ct.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":$t,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":w.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?zt(zt(e,w.ajaxSettings),t):zt(w.ajaxSettings,e)},ajaxPrefilter:Ft(It),ajaxTransport:Ft(Wt),ajax:function(t,n){"object"==typeof t&&(n=t,t=void 0),n=n||{};var i,o,a,s,u,l,c,f,p,d,h=w.ajaxSetup({},n),g=h.context||h,y=h.context&&(g.nodeType||g.jquery)?w(g):w.event,v=w.Deferred(),m=w.Callbacks("once memory"),x=h.statusCode||{},b={},T={},C="canceled",E={readyState:0,getResponseHeader:function(e){var t;if(c){if(!s){s={};while(t=Ot.exec(a))s[t[1].toLowerCase()]=t[2]}t=s[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return c?a:null},setRequestHeader:function(e,t){return null==c&&(e=T[e.toLowerCase()]=T[e.toLowerCase()]||e,b[e]=t),this},overrideMimeType:function(e){return null==c&&(h.mimeType=e),this},statusCode:function(e){var t;if(e)if(c)E.always(e[E.status]);else for(t in e)x[t]=[x[t],e[t]];return this},abort:function(e){var t=e||C;return i&&i.abort(t),k(0,t),this}};if(v.promise(E),h.url=((t||h.url||Ct.href)+"").replace(Rt,Ct.protocol+"//"),h.type=n.method||n.type||h.method||h.type,h.dataTypes=(h.dataType||"*").toLowerCase().match(M)||[""],null==h.crossDomain){l=r.createElement("a");try{l.href=h.url,l.href=l.href,h.crossDomain=Bt.protocol+"//"+Bt.host!=l.protocol+"//"+l.host}catch(e){h.crossDomain=!0}}if(h.data&&h.processData&&"string"!=typeof h.data&&(h.data=w.param(h.data,h.traditional)),_t(It,h,n,E),c)return E;(f=w.event&&h.global)&&0==w.active++&&w.event.trigger("ajaxStart"),h.type=h.type.toUpperCase(),h.hasContent=!Mt.test(h.type),o=h.url.replace(Lt,""),h.hasContent?h.data&&h.processData&&0===(h.contentType||"").indexOf("application/x-www-form-urlencoded")&&(h.data=h.data.replace(qt,"+")):(d=h.url.slice(o.length),h.data&&(h.processData||"string"==typeof h.data)&&(o+=(kt.test(o)?"&":"?")+h.data,delete h.data),!1===h.cache&&(o=o.replace(Ht,"$1"),d=(kt.test(o)?"&":"?")+"_="+Et+++d),h.url=o+d),h.ifModified&&(w.lastModified[o]&&E.setRequestHeader("If-Modified-Since",w.lastModified[o]),w.etag[o]&&E.setRequestHeader("If-None-Match",w.etag[o])),(h.data&&h.hasContent&&!1!==h.contentType||n.contentType)&&E.setRequestHeader("Content-Type",h.contentType),E.setRequestHeader("Accept",h.dataTypes[0]&&h.accepts[h.dataTypes[0]]?h.accepts[h.dataTypes[0]]+("*"!==h.dataTypes[0]?", "+$t+"; q=0.01":""):h.accepts["*"]);for(p in h.headers)E.setRequestHeader(p,h.headers[p]);if(h.beforeSend&&(!1===h.beforeSend.call(g,E,h)||c))return E.abort();if(C="abort",m.add(h.complete),E.done(h.success),E.fail(h.error),i=_t(Wt,h,n,E)){if(E.readyState=1,f&&y.trigger("ajaxSend",[E,h]),c)return E;h.async&&h.timeout>0&&(u=e.setTimeout(function(){E.abort("timeout")},h.timeout));try{c=!1,i.send(b,k)}catch(e){if(c)throw e;k(-1,e)}}else k(-1,"No Transport");function k(t,n,r,s){var l,p,d,b,T,C=n;c||(c=!0,u&&e.clearTimeout(u),i=void 0,a=s||"",E.readyState=t>0?4:0,l=t>=200&&t<300||304===t,r&&(b=Xt(h,E,r)),b=Ut(h,b,E,l),l?(h.ifModified&&((T=E.getResponseHeader("Last-Modified"))&&(w.lastModified[o]=T),(T=E.getResponseHeader("etag"))&&(w.etag[o]=T)),204===t||"HEAD"===h.type?C="nocontent":304===t?C="notmodified":(C=b.state,p=b.data,l=!(d=b.error))):(d=C,!t&&C||(C="error",t<0&&(t=0))),E.status=t,E.statusText=(n||C)+"",l?v.resolveWith(g,[p,C,E]):v.rejectWith(g,[E,C,d]),E.statusCode(x),x=void 0,f&&y.trigger(l?"ajaxSuccess":"ajaxError",[E,h,l?p:d]),m.fireWith(g,[E,C]),f&&(y.trigger("ajaxComplete",[E,h]),--w.active||w.event.trigger("ajaxStop")))}return E},getJSON:function(e,t,n){return w.get(e,t,n,"json")},getScript:function(e,t){return w.get(e,void 0,t,"script")}}),w.each(["get","post"],function(e,t){w[t]=function(e,n,r,i){return g(n)&&(i=i||r,r=n,n=void 0),w.ajax(w.extend({url:e,type:t,dataType:i,data:n,success:r},w.isPlainObject(e)&&e))}}),w._evalUrl=function(e){return w.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},w.fn.extend({wrapAll:function(e){var t;return this[0]&&(g(e)&&(e=e.call(this[0])),t=w(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(e){return g(e)?this.each(function(t){w(this).wrapInner(e.call(this,t))}):this.each(function(){var t=w(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=g(e);return this.each(function(n){w(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(e){return this.parent(e).not("body").each(function(){w(this).replaceWith(this.childNodes)}),this}}),w.expr.pseudos.hidden=function(e){return!w.expr.pseudos.visible(e)},w.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},w.ajaxSettings.xhr=function(){try{return new e.XMLHttpRequest}catch(e){}};var Vt={0:200,1223:204},Gt=w.ajaxSettings.xhr();h.cors=!!Gt&&"withCredentials"in Gt,h.ajax=Gt=!!Gt,w.ajaxTransport(function(t){var n,r;if(h.cors||Gt&&!t.crossDomain)return{send:function(i,o){var a,s=t.xhr();if(s.open(t.type,t.url,t.async,t.username,t.password),t.xhrFields)for(a in t.xhrFields)s[a]=t.xhrFields[a];t.mimeType&&s.overrideMimeType&&s.overrideMimeType(t.mimeType),t.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest");for(a in i)s.setRequestHeader(a,i[a]);n=function(e){return function(){n&&(n=r=s.onload=s.onerror=s.onabort=s.ontimeout=s.onreadystatechange=null,"abort"===e?s.abort():"error"===e?"number"!=typeof s.status?o(0,"error"):o(s.status,s.statusText):o(Vt[s.status]||s.status,s.statusText,"text"!==(s.responseType||"text")||"string"!=typeof s.responseText?{binary:s.response}:{text:s.responseText},s.getAllResponseHeaders()))}},s.onload=n(),r=s.onerror=s.ontimeout=n("error"),void 0!==s.onabort?s.onabort=r:s.onreadystatechange=function(){4===s.readyState&&e.setTimeout(function(){n&&r()})},n=n("abort");try{s.send(t.hasContent&&t.data||null)}catch(e){if(n)throw e}},abort:function(){n&&n()}}}),w.ajaxPrefilter(function(e){e.crossDomain&&(e.contents.script=!1)}),w.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return w.globalEval(e),e}}}),w.ajaxPrefilter("script",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET")}),w.ajaxTransport("script",function(e){if(e.crossDomain){var t,n;return{send:function(i,o){t=w("' + + `` + + '' + + '' + + '' + + '' + + ''; + + // Determine javascript and css dependencies + const extra_css_files = []; + const extra_js_files = []; + //if (data.rh_result.flags.extra_js_files) { + // for (const jsFile of data.rh_result.flags.extra_js_files) { + // if (jsFile.file == "js/apps/GraphTool/graphtool.min.js" || jsFile.file == "js/apps/Scaffold/scaffold.js") { + // extra_css_files.push({ file: 'js/vendor/bootstrap/css/bootstrap.css', external: '0' }); + // extra_css_files.push({ file: 'themes/math4/math4.css', external: '0' }); + // extra_js_files.push({ file: 'js/vendor/bootstrap/js/bootstrap.js', external: '0' }); + // } + // } + //} + + if (data.rh_result.flags.extra_css_files) data.rh_result.flags.extra_css_files.unshift(...extra_css_files); + else data.rh_result.flags.extra_css_files = extra_css_files; + for (const cssFile of data.rh_result.flags.extra_css_files) { + iframeContents += ''; + } + + if (data.rh_result.flags.extra_js_files) data.rh_result.flags.extra_js_files.unshift(...extra_js_files); + else data.rh_result.flags.extra_js_files = extra_js_files; + for (const jsFile of data.rh_result.flags.extra_js_files) { + iframeContents += ''; + } + + iframeContents += + '' + + '' + + '' + + `` + + '
    ' + form.outerHTML + '
    ' + + ''; + + let iframe; + // If there is no action this is the initialization call. + if (!action) { + // Create the iframe. + iframe = document.createElement('iframe'); + iframe.style.width = '1px'; + iframe.style.minWidth = '100%'; + iframe.classList.add('problem-iframe'); + + // Hide the static problem + ww_container.querySelector('.problem-contents').classList.add('hidden-content'); + + if (activate_button != null) { + // Make sure the iframe follows the activate button in the DOM + activate_button.after(iframe); + } else { + ww_container.prepend(iframe); + } + + iFrameResize({ checkOrigin: false, scrolling: 'omit', heightCalculationMethod: 'min' }, iframe); + + iframe.addEventListener('load', () => { + // Set up form submission from inside the iframe. + const iframeForm = iframe.contentDocument.getElementById(ww_id + '-form'); + iframeForm.addEventListener('submit', (e) => { + handleWW(ww_id, "check"); + e.preventDefault(); + }); + + iframe.contentDocument.querySelectorAll('.collapse.in').forEach(collapse => collapse.classList.add('expanded')); + iframe.contentWindow.jQuery('.collapse').on('shown', function(e) { if (e.target != this) return; this.classList.add('expanded'); }); + iframe.contentWindow.jQuery('.collapse').on('hide', function(e) { if (e.target != this) return; this.classList.remove('expanded'); }); + + iframe.contentDocument.querySelectorAll("button.ww-feedback[data-content]").forEach(button => { + iframe.contentWindow.jQuery(button).popover("show"); + const content = iframe.contentDocument.getElementById(button.id.replace("-feedback-button", "-content")); + const popover = content.parentNode.parentNode; + popover.id = button.id.replace("-feedback-button", "-feedback") + button.setAttribute('aria-describedby', popover.id); + if (button.previousElementSibling) + button.previousElementSibling.setAttribute('aria-describedby', popover.id); + popover.querySelector('.arrow').remove(); + const title = popover.querySelector('.popover-title'); + if (button.dataset.emptyContent) { + title.style.borderBottomWidth = 0; + content.parentNode.remove(); + } + if (title.textContent == localize_correct + '!') title.classList.add('correct'); + }); + iframe.contentWindow.MathJax.startup.promise.then(() => iframe.contentWindow.MathJax.typesetPromise(['.popover', '.popover-content'])); + }); + } else { + iframe = ww_container.querySelector('.problem-iframe'); + } + + iframe.srcdoc = iframeContents; + + iframe.addEventListener('load', () => { + // Remove the loader overlay + loader.remove(); + }, { once: true }) + + // Place focus on the problem. + ww_container.focus() + }); +} + +function WWshowCorrect(ww_id, answers) { + const ww_container = document.getElementById(ww_id); + const iframe = ww_container.querySelector('.problem-iframe'); + + const body = iframe.contentDocument.getElementById(ww_id + '-body') + $("body").trigger("runestone_show_correct", { + "ww_id": ww_id, + "answers": answers + }); + + let inputs = body.querySelectorAll("input:not([type=hidden])"); + for (const input of inputs) { + const name = input.name; + const span_id = `${ww_id}-${name}-correct`; + + if (input.type == 'text' && answers[name] && !(iframe.contentDocument.getElementById(span_id))) { + const feedbackButton = iframe.contentDocument.getElementById(`${ww_id}-${name}-feedback-button`); + if (feedbackButton) { + feedbackButton.remove(); + iframe.contentWindow.jQuery(feedbackButton).popover("hide"); + } + label = iframe.contentDocument.getElementById(`${span_id}-label`); + if (label) { + label.parentElement.insertBefore(input, label); + label.remove(); + } + input.type = "hidden"; + // we need to convert things like < in answers to < + const correct_ans_text = iframe.contentDocument.createElement('div'); + correct_ans_text.innerHTML = answers[name].correct_ans; + input.value = correct_ans_text.textContent; + const show_span = iframe.contentDocument.createElement('span'); + show_span.id = span_id; + show_span.appendChild(answers[name].correct_ans_latex_string + ? iframe.contentDocument.createTextNode('\\(' + answers[name].correct_ans_latex_string + '\\)') + : iframe.contentDocument.createTextNode(answers[name].correct_ans)); + input.parentElement.insertBefore(show_span, input); + } + + if (input.type == 'radio' && answers[name]) { + const feedbackButton = iframe.contentDocument.getElementById(`${ww_id}-${name}-feedback-button`); + if (feedbackButton) { + feedbackButton.remove(); + iframe.contentWindow.jQuery(feedbackButton).popover("hide"); + } + correct_value = answers[name].correct_choice; + if (input.value == correct_value) input.checked = true; + } + } + + const hiddenInputs = body.querySelectorAll("input[type=hidden]"); + for (const input of hiddenInputs) { + const name = input.name; + if (!input.nextElementSibling) continue; + const graphtoolContainer = input.nextElementSibling.nextElementSibling; + if (graphtoolContainer && answers[name] && graphtoolContainer.classList.contains('graphtool-container')) { + const feedbackButton = iframe.contentDocument.getElementById(`${ww_id}-${name}-feedback-button`); + if (feedbackButton) { + feedbackButton.remove(); + iframe.contentWindow.jQuery(feedbackButton).popover("hide"); + } + const correct_ans_div = iframe.contentDocument.createElement('div'); + input.parentElement.insertBefore(correct_ans_div, graphtoolContainer); + graphtoolContainer.style.display = 'none'; + input.value = answers[name].correct_ans; + iframe.contentWindow.jQuery(correct_ans_div).html(answers[name].correct_ans_latex_string); + const script = iframe.contentDocument.createElement('script'); + script.textContent = correct_ans_div.querySelector('script').textContent + .replace('\nwindow.addEventListener("DOMContentLoaded",', '(') + .replace(/;\n$/, '();'); + iframe.contentDocument.body.appendChild(script); + } + } + + let selects = body.querySelectorAll("select:not([type=hidden])"); + for (const select of selects) { + const name = select.name; + const span_id = `${ww_id}-${name}-correct`; + if (answers[name] && !iframe.contentDocument.getElementById(span_id)) { + const feedbackButton = iframe.contentDocument.getElementById(`${ww_id}-${name}-feedback-button`); + if (feedbackButton) { + feedbackButton.remove(); + iframe.contentWindow.jQuery(feedbackButton).popover("hide"); + } + select.style.display = "none"; + select.value = answers[name].correct_ans; + const show_span = iframe.contentDocument.createElement('span'); + show_span.id = span_id; + show_span.appendChild(answers[name].correct_ans_latex_string + ? iframe.contentDocument.createTextNode('\\(' + answers[name].correct_ans_latex_string + '\\)') + : iframe.contentDocument.createTextNode(answers[name].correct_ans)); + select.parentElement.insertBefore(show_span, select); + } + } + + // run MathJax on our new rendering + // FIXME: We only need to typeset the added elements, not the entire body. + const mathjaxTypesetScript = iframe.contentDocument.createElement('script'); + mathjaxTypesetScript.textContent = 'MathJax.startup.promise.then(() => MathJax.typesetPromise([document.body]));'; + iframe.contentDocument.body.appendChild(mathjaxTypesetScript); +} + +function resetWW(ww_id) { + const ww_container = document.getElementById(ww_id); + const activate_button = document.getElementById(ww_id + '-button'); + + ww_container.dataset.current_seed = ww_container.dataset.seed; + + iframe = ww_container.querySelector('.problem-iframe'); + iframe.remove(); + + ww_container.querySelector('.problem-contents').classList.remove('hidden-content'); + + ww_container.querySelector('.problem-buttons.webwork').remove(); + ww_container.querySelector('.problem-buttons').classList.remove('hidden-content'); + // if the newer activate button is there (but hidden) bring it back too + if (activate_button != null) {activate_button.classList.remove('hidden-content');}; +} + +function adjustSrcHrefs(container,ww_domain) { + container.querySelectorAll('[href]').forEach((node) => { + const href = node.attributes.href.value; + if (href !== '#' && !href.match(/^[a-z]+:\/\//i)) node.href = ww_domain + '/' + href; + }); + container.querySelectorAll('[src]').forEach((node) => { + node.src = ww_domain + '/' + node.attributes.src.value; + }); +} + +function translateHintSol(ww_id, body_div, ww_domain, b_ptx_has_hint, b_ptx_has_solution, hint_label_text, solution_label_text) { + // the problem text may come with "hint"s and "solution"s + // each one is an "a" with content "Hint" or "Solution", and an attribute with base64-encoded HTML content + // the WeBWorK knowl js would normally handle this, but we want PreTeXt knowl js to handle it + // so we replace the "a" with the content that should be there for PTX knowl js + // also if hint/sol were missing from the static version, we want these removed here + + const ww_container = document.getElementById(ww_id); + + const hintsolnodes = document.evaluate("//p[a/b]", body_div, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null); + if (hintsolnodes) { + let solutionlikewrapper; + for (let i = 0; i < hintsolnodes.snapshotLength; i++) { + const hintsolp = hintsolnodes.snapshotItem(i); + if (!hintsolp) continue; + const hintSolType = hintsolp.textContent.trim().toLowerCase().replace(':', ''); + + if (hintsolp.previousElementSibling.textContent.trim() != 'Hint:' && hintsolp.previousElementSibling.textContent.trim() != 'Solution:') { + solutionlikewrapper = document.createElement('div'); + solutionlikewrapper.classList.add('webwork', 'solutions'); + hintsolp.parentNode.insertBefore(solutionlikewrapper, hintsolp); + } + + if ((hintSolType == 'solution' && !b_ptx_has_solution) || (hintSolType == 'hint' && !b_ptx_has_hint)) continue; + + const knowlDetails = document.createElement('details'); + knowlDetails.classList.add(hintSolType); + knowlDetails.classList.add('solution-like'); + knowlDetails.classList.add('born-hidden-knowl'); + + const knowlSummary = document.createElement('summary'); + const summaryLabel = document.createElement('span'); + summaryLabel.classList.add('type'); + summaryLabel.innerHTML = hintSolType == 'hint' ? hint_label_text : solution_label_text; + knowlSummary.appendChild(summaryLabel); + knowlDetails.appendChild(knowlSummary); + + const knowlContents = document.createElement('div'); + knowlContents.classList.add(hintSolType); + knowlContents.classList.add('solution-like'); + knowlContents.innerHTML = hintsolp.firstElementChild.dataset.knowlContents; + knowlDetails.appendChild(knowlContents); + + adjustSrcHrefs(knowlContents, ww_domain) + solutionlikewrapper.appendChild(knowlDetails); + } + } + for (let i = 0; i < hintsolnodes.snapshotLength; i++) { + hintsolnodes.snapshotItem(i).remove(); + } +} + +function cloneAttributes(target, source) { + [...source.attributes].forEach( attr => { target.setAttribute(attr.nodeName ,attr.nodeValue) }); +} + +function createFeedbackButton(id, title, content) { + const feedbackButton = document.createElement('button'); + feedbackButton.dataset.title = title; + feedbackButton.dataset.content = `
    ${content || ''}
    `; + if (!content) feedbackButton.dataset.emptyContent = '1'; + const contentSpan = document.createElement('span'); + contentSpan.style.fontWeight = 1000; + contentSpan.textContent = '\uD83D\uDDE9' + feedbackButton.appendChild(contentSpan); + feedbackButton.type = 'button'; + feedbackButton.classList.add('ww-feedback'); + feedbackButton.style.borderRadius = 0; + + feedbackButton.id = `${id}-feedback-button`; + feedbackButton.dataset.html = true; + feedbackButton.dataset.placement = 'bottom'; + feedbackButton.dataset.trigger = 'click'; + + return feedbackButton; +} diff --git a/slides-annotated/_static/pretext/js/pretext-webwork/2.17/pretext-webwork.js b/slides-annotated/_static/pretext/js/pretext-webwork/2.17/pretext-webwork.js new file mode 100755 index 0000000..9b809e6 --- /dev/null +++ b/slides-annotated/_static/pretext/js/pretext-webwork/2.17/pretext-webwork.js @@ -0,0 +1,1058 @@ +//Critical: +// TODO: Think about what should be done for an essay question. + +//Accessibility: + +//Enhancement: +// TODO: In a scaffold problem, adjust the green bar for when one part is correct. +// TODO: Have randomize check that new seed is actually producing new HTML. +// TODO: Don't offer randomize or button unless we know a new version can be produced after trying a few seeds. +// TODO: In a staged problem, Make Interactive button is adjacent to solution knowls; move it. +// TODO: MathQuill +// TODO: Image pop up. +// TODO: Deal with singleResult MultiAnswer problems. + +//Styling: +// TODO: Review all styling in all scenarios (staged/not, correct/partly-correct/incorrect/blank, single/multiple) +// TODO: Sean: I think I'd like to see a slightly larger font size on the buttons + +function handleWW(ww_id, action) { + const ww_container = document.getElementById(ww_id); + const ww_domain = ww_container.dataset.domain; + const ww_problemSource = ww_container.dataset.problemsource; + const ww_sourceFilePath = ww_container.dataset.sourcefilepath; + const ww_course_id = ww_container.dataset.courseid; + const ww_user_id = ww_container.dataset.userid; + const ww_course_password = ww_container.dataset.coursepassword || ww_container.dataset.passwd; + const localize_correct = ww_container.dataset.localizeCorrect || "Correct"; + const localize_incorrect = ww_container.dataset.localizeIncorrect || "Incorrect"; + const localize_blank = ww_container.dataset.localizeBlank || "Blank"; + const localize_submit = ww_container.dataset.localizeSubmit || "Submit"; + const localize_check_responses = ww_container.dataset.localizeCheckResponses || "Check Responses"; + const localize_reveal = ww_container.dataset.localizeReveal || "Reveal"; + const localize_randomize = ww_container.dataset.localizeRandomize || "Randomize"; + const localize_reset = ww_container.dataset.localizeReset || "Reset"; + const runestone_logged_in = (typeof eBookConfig !== 'undefined' && eBookConfig.username !== ''); + // will be null on pages generated prior to late December 2022 + const activate_button = document.getElementById(ww_id + '-button') + + // Set the current seed + if (!action) { + ww_container.dataset.current_seed = ww_container.dataset.seed; + if (runestone_logged_in) { + ww_container.dataset.current_seed = webworkSeedHash(eBookConfig.username + ww_container.dataset.current_seed); + } + } + else if (action == 'randomize') ww_container.dataset.current_seed = Number(ww_container.dataset.current_seed) + 100; + + let loader = document.createElement('div'); + loader.style.position = 'absolute'; + loader.style.left = 0; + loader.style.top = 0; + loader.style.backgroundColor = 'rgba(0.2, 0.2, 0.2, 0.4)'; + loader.style.color = 'white'; + loader.style.width = '100%'; + loader.style.height = '100%'; + loader.style.display = 'flex'; + loader.style.alignItems = 'center'; + loader.style.justifyContent = 'center'; + loader.style.marginTop = '0'; + loader.tabIndex = -1; + const loaderText = document.createElement('span'); + loaderText.textContent = 'Loading'; + loaderText.style.fontSize = '2rem'; + loader.appendChild(loaderText); + ww_container.appendChild(loader); + loader.focus(); + + if (!action) { + // Determine if static version shows hints, solutions, or answers and save that information in the container dataset for later runs. + ww_container.dataset.hasHint = ww_container.getElementsByClassName('hint').length > 0; + ww_container.dataset.hasSolution = ww_container.getElementsByClassName('solution').length > 0; + ww_container.dataset.hasAnswer = ww_container.getElementsByClassName('answer').length > 0; + // Get (possibly localized) label text for hints and solutions. + ww_container.dataset.hintLabelText = ww_container.dataset.hasHint == 'true' + ? ww_container.querySelectorAll('.hint-knowl span.type, details.hint span.type')[0].textContent : 'Hint'; + ww_container.dataset.solutionLabelText = ww_container.dataset.hasSolution == 'true' + ? ww_container.querySelectorAll('.solution-knowl span.type, details.solution span.type')[0].textContent : 'Solution'; + + ww_container.tabIndex = -1; + } + + let url; + + if (action == 'check') { + const iframe = ww_container.querySelector('.problem-iframe'); + const formData = new FormData(iframe.contentDocument.getElementById(ww_id + "-form")); + const params = new URLSearchParams(formData); + url = new URL(ww_domain + '/webwork2/html2xml?' + params.toString()) + url.searchParams.append("answersSubmitted", '1'); + url.searchParams.append('WWsubmit', "1"); + } else { + url = new URL(ww_domain + '/webwork2/html2xml'); + url.searchParams.append("problemSeed", ww_container.dataset.current_seed); + if (ww_problemSource) url.searchParams.append("problemSource", ww_problemSource); + else if (ww_sourceFilePath) url.searchParams.append("sourceFilePath", ww_sourceFilePath); + url.searchParams.append("answersSubmitted", '0'); + url.searchParams.append("displayMode", "MathJax"); + url.searchParams.append("courseID", ww_course_id); + url.searchParams.append("userID", ww_user_id); + url.searchParams.append("course_password", ww_course_password); + url.searchParams.append("outputformat", "raw"); + // note ww_container.dataset.hasSolution is a string, possibly 'false' which is true + url.searchParams.append("showSolutions", ww_container.dataset.hasSolution == 'true' ? '1' : '0'); + url.searchParams.append("showHints", ww_container.dataset.hasHint == 'true' ? '1' : '0'); + url.searchParams.append("problemUUID",ww_id); + } + + // get the json and do stuff with what we get + $.getJSON(url.toString(), (data) => { + // Create the form that will contain the text and input fields of the interactive problem. + const form = document.createElement("form"); + form.id = ww_id + "-form"; + + // Create a div for the problem text. + const body_div = document.createElement("div"); + body_div.id = ww_id + "-body"; + body_div.classList.add("exercise", "exercise-like"); + body_div.lang = data.lang; + body_div.dir = data.dir; + + // Dump the problem text, answer blanks, etc. + body_div.innerHTML = data.rh_result.text; + + // Replace all hn headings with h6 headings. + for (const tag_name of ['h6', 'h5', 'h4', 'h3', 'h2', 'h1']) { + const headings = body_div.getElementsByTagName(tag_name); + for (heading of headings) { + const new_heading = document.createElement("h6"); + new_heading.innerHTML = heading.innerHTML; + cloneAttributes(new_heading, heading); + new_heading.classList.add('webwork-part'); + heading.replaceWith(new_heading); + } + } + + adjustSrcHrefs(body_div, ww_domain); + + translateHintSol(ww_id, body_div, ww_domain, + ww_container.dataset.hasHint == 'true', ww_container.dataset.hasSolution == 'true', + ww_container.dataset.hintLabelText, ww_container.dataset.solutionLabelText) + + // insert previous answers + if (runestone_logged_in) { + const answersObject = (wwList[ww_id.replace(/-ww-rs$/,'')].answers ? wwList[ww_id.replace(/-ww-rs$/,'')].answers : {'answers' : [], 'mqAnswers' : []}); + const mqAnswers = answersObject.mqAnswers; + for (const mqAnswer in mqAnswers) { + const mqInput = body_div.querySelector('input[id=' + mqAnswer + ']'); + if (mqInput && mqInput.value == '') { + mqInput.setAttribute('value', mqAnswers[mqAnswer]); + } + } + const answers = answersObject.answers; + for (const answer in answers) { + const input = body_div.querySelector('input[id=' + answer + ']'); + if (input && input.value == '') { + input.setAttribute('value', answers[answer]); + } + if (input && input.type.toUpperCase() == 'RADIO') { + const buttons = body_div.querySelectorAll('input[name=' + answer + ']'); + for (const button of buttons) { + if (button.value == answers[answer]) { + button.setAttribute('checked', 'checked'); + } + } + } + if (input && input.type.toUpperCase() == 'CHECKBOX') { + const checkboxes = body_div.querySelectorAll('input[name=' + answer + ']'); + for (const checkbox of checkboxes) { + // This is not a bulletproof approach if the problem used input values that are weird + // For example, with commas in them + // However, we are stuck with WW providing answers[answer] as a string like `[value0, value1]` + // and note that it is not `["value0", "value1"]`, so we cannot cleanly parse it into an array + let checkbox_regex = new RegExp('(\\[|, )' + checkbox.value + '(, |\\])'); + if (answers[answer].match(checkbox_regex)) { + checkbox.setAttribute('checked', 'checked'); + } + } + } + var select = body_div.querySelector('select[id=' + answer + ']'); + if (select && answers[answer]) { + // answers[answer] may be wrapped in \text{...} that we want to remove, since value does not have this. + let this_answer = answers[answer]; + if (/^\\text\{.*\}$/.test(this_answer)) {this_answer = this_answer.match(/^\\text\{(.*)\}$/)[1]}; + let quote_escaped_answer = this_answer.replace(/"/g, '\\"'); + const option = body_div.querySelector(`select[id="${answer}"] option[value="${quote_escaped_answer}"]`); + if (option) {option.setAttribute('selected', 'selected')}; + } + } + } + + // insert our cleaned up problem text + form.appendChild(body_div); + + // Set up hidden input fields that the form uses + const wwInputs = { + problemSeed: data.inputs_ref.problemSeed, + problemUUID: data.inputs_ref.problemUUID, + psvn: data.inputs_ref.psvn, + courseName: ww_course_id, + courseID: ww_course_id, + userID: ww_user_id, + course_password: ww_course_password, + displayMode: "MathJax", + session_key: data.rh_result.session_key, + outputformat: "raw", + language: data.formLanguage, + showSummary: data.showSummary, + // note ww_container.dataset.hasSolution is a string, possibly 'false' which is true + showSolutions: ww_container.dataset.hasSolution == 'true' ? '1' : '0', + showHints: ww_container.dataset.hasHint == 'true' ? '1' : '0', + forcePortNumber: data.forcePortNumber + }; + + if (ww_sourceFilePath) wwInputs.sourceFilePath = ww_sourceFilePath; + else if (ww_problemSource) wwInputs.problemSource = ww_problemSource; + + for (const wwInputName of Object.keys(wwInputs)) { + const input = document.createElement('input'); + input.type = 'hidden'; + input.name = wwInputName; + input.value = wwInputs[wwInputName]; + form.appendChild(input); + } + + // Prepare answers object + const answers = {}; + // id the answers even if we won't populate them + Object.keys(data.rh_result.answers).forEach(function(id) { + answers[id] = {}; + }, data.rh_result.answers); + if (ww_container.dataset.hasAnswer == 'true') { + // Update answer data + Object.keys(data.rh_result.answers).forEach(function(id) { + answers[id] = { + correct_ans: this[id].correct_ans, + correct_ans_latex_string: this[id].correct_ans_latex_string, + correct_choice: this[id].correct_choice, + correct_choices: this[id].correct_choices, + }; + }, data.rh_result.answers); + } + + let buttonContainer = ww_container.querySelector('.problem-buttons.webwork'); + // Create the submission buttons if they have not yet been created. + if (!buttonContainer) { + // Hide the original div that contains the old make active button. + ww_container.querySelector('.problem-buttons').classList.add('hidden-content'); + // And the newer activate button if it is there + if (activate_button != null) {activate_button.classList.add('hidden-content');}; + + // Create a new div for the webwork buttons. + buttonContainer = document.createElement('div'); + buttonContainer.classList.add('problem-buttons', 'webwork'); + if (activate_button != null) { + // Make sure the button container follows the activate button in the DOM + activate_button.after(buttonContainer); + } else { + ww_container.prepend(buttonContainer); + } + + // Check button + const check = document.createElement("button"); + check.type = "button"; + check.id = ww_id + '-check'; + check.style.marginRight = "0.25rem"; + check.classList.add('webwork-button'); + + // Adjust if more than one answer to check + const answerCount = body_div.querySelectorAll("input:not([type=hidden])").length + + body_div.querySelectorAll("select:not([type=hidden])").length; + + check.textContent = runestone_logged_in ? localize_submit : localize_check_responses; + check.addEventListener('click', () => handleWW(ww_id, "check")); + + buttonContainer.appendChild(check); + + // Show correct answers button if original PTX has answer knowl. + if (ww_container.dataset.hasAnswer == 'true') { + const correct = document.createElement("button"); + correct.classList.add("show-correct", 'webwork-button'); + correct.type = "button"; + correct.style.marginRight = "0.25rem"; + correct.textContent = localize_reveal; + correct.addEventListener('click', () => WWshowCorrect(ww_id, answers)); + buttonContainer.appendChild(correct); + } + + // randomize button + const randomize = document.createElement("button") + randomize.type = "button"; + randomize.classList.add('webwork-button'); + randomize.style.marginRight = "0.25rem"; + randomize.textContent = localize_randomize; + randomize.addEventListener('click', () => handleWW(ww_id, 'randomize')); + buttonContainer.appendChild(randomize) + + // reset button + const reset = document.createElement("button") + reset.type = "button" + reset.classList.add('webwork-button'); + reset.textContent = localize_reset; + reset.addEventListener('click', () => resetWW(ww_id)); + buttonContainer.appendChild(reset) + } else { + // Update the click handler for the show correct button. + if (ww_container.dataset.hasAnswer == 'true') { + const correct = buttonContainer.querySelector('.show-correct'); + const correctNew = correct.cloneNode(true); + correctNew.addEventListener('click', () => WWshowCorrect(ww_id, answers)); + correct.replaceWith(correctNew); + } + } + + if (action == 'check') { + // Runestone trigger + // TODO: condition on platform=Runestone + $("body").trigger('runestone_ww_check', data) + + const inputs = body_div.querySelectorAll("input:not([type=hidden])"); + for (const input of inputs) { + const name = input.name; + if (input.type == 'text' && answers[name]) { + const score = data.rh_result.answers[name].score; + let headline = ''; + let correctClass = ''; + if (score >= 1) { + headline = localize_correct + '!'; + correctClass = 'correct'; + } else if (score > 0 && score < 1) { + headline = `${Math.round(score * 100)}% ${localize_correct}.`; + correctClass = 'partly-correct'; + } else if (data.rh_result.answers[name].student_ans == '') { + headline = localize_blank + '.'; + correctClass = 'blank'; + } else if (score <= 0) { + headline = localize_incorrect + '.'; + correctClass = 'incorrect'; + } + let title = `${headline}`; + let message = (data.rh_result.answers[name].ans_message ? data.rh_result.answers[name].ans_message : ''); + input.classList.add(correctClass); + // make a span that contains feedback + feedbackSpan = document.createElement('span'); + feedbackSpan.id = `${ww_id}-${name}-feedback`; + feedbackHeadline = document.createElement('span'); + feedbackHeadline.id = `${ww_id}-${name}-headline`; + feedbackHeadline.textContent = headline; + feedbackSpan.appendChild(feedbackHeadline); + feedbackMessage = document.createElement('span'); + feedbackMessage.id = `${ww_id}-${name}-message`; + feedbackMessage.textContent = message; + feedbackSpan.appendChild(feedbackMessage); + // make a non breaking span to surround input, sr span, and feedback button + let nbSpan = document.createElement('span'); + nbSpan.classList.add("nobreak"); + input.parentNode.insertBefore(nbSpan, input); + nbSpan.appendChild(input); + // put a visibly hidden (screen-reader only) span with feedback + let srSpan = document.createElement('span'); + srSpan.classList.add("visually-hidden"); + srSpan.appendChild(feedbackSpan); + nbSpan.appendChild(srSpan); + // give the feedback to sr users immediately + input.setAttribute('aria-describedby',`${ww_id}-${name}-feedback`); + // make the feedback button + srSpan.after(createFeedbackButton(`${ww_id}-${name}`, title, message)); + + } + + if (input.type.toUpperCase() == 'RADIO' && answers[name]) { + const score = data.rh_result.answers[name].score; + const student_ans = data.rh_result.answers[name].student_value || data.rh_result.answers[name].student_ans; + const correct_ans = data.rh_result.answers[name].correct_choice || data.rh_result.answers[name].correct_ans; + if (input.value == student_ans) { + if (score == 1) { + input.parentNode.classList.add('correct'); + } else { + input.parentNode.classList.add('incorrect'); + } + const feedbackButton = createFeedbackButton(`${ww_id}-${name}`, + (student_ans == correct_ans) + ? `${localize_correct}` : `${localize_incorrect}.`) + feedbackButton.style.marginRight = '0.25rem'; + input.after(feedbackButton); + } + } + + if (input.type.toUpperCase() == 'CHECKBOX' && answers[name]) { + const score = data.rh_result.answers[name].score; + const student_ans = data.rh_result.answers[name].student_ans; + const correct_ans = data.rh_result.answers[name].correct_ans; + if (input.value == data.rh_result.answers[name].firstElement) { + const checkbox_div = input.parentNode.parentNode; + if (score == 1) { + checkbox_div.classList.add('correct'); + } else { + checkbox_div.classList.add('incorrect'); + } + const feedbackButton = createFeedbackButton(`${ww_id}-${name}`, + (student_ans == correct_ans) + ? `${localize_correct}` : `${localize_incorrect}.`) + checkbox_div.insertBefore(feedbackButton, checkbox_div.firstChild); + } + } + } + + const hiddenInputs = body_div.querySelectorAll("input[type=hidden]"); + for (const input of hiddenInputs) { + const name = input.name; + if (!input.nextElementSibling) continue; + const graphtoolContainer = input.nextElementSibling.nextElementSibling; + if (graphtoolContainer && answers[name] && graphtoolContainer.classList.contains('graphtool-container')) { + graphtoolContainer.style.position = 'relative'; + const score = data.rh_result.answers[name].score; + let title = ''; + if (score == 1) { + title = `${localize_correct}`; + } else if (score > 0 && score < 1) { + title = `${Math.round(score * 100)}% ${localize_correct}.`; + } else if (data.rh_result.answers[name].student_ans == '') { + // do nothing if the submitted answer is blank and the problem has not already been scored as correct + continue; + } else if (score == 0) { + title = `${localize_incorrect}.`; + } + + const feedbackButton = createFeedbackButton(`${ww_id}-${name}`, title, data.rh_result.answers[name].ans_message); + feedbackButton.style.position = 'absolute'; + feedbackButton.style.left = '100%'; + feedbackButton.style.top = 0; + feedbackButton.style.marginLeft = '0.5rem'; + feedbackButton.dataset.bsContainer = 'body'; + graphtoolContainer.appendChild(feedbackButton); + if (score == 1) { + graphtoolContainer.classList.add('correct'); + } else { + graphtoolContainer.classList.add('incorrect'); + } + + } + } + + const selects = body_div.querySelectorAll("select:not([type=hidden])"); + for (const select of selects) { + const name = select.name; + const score = data.rh_result.answers[name].score; + let title = ''; + if (score == 1) { + headline = localize_correct + '!'; + correctClass = 'correct'; + } else { + headline = localize_incorrect + '.'; + correctClass = 'incorrect'; + } + select.classList.add(correctClass); + title = `${headline}`; + // make a span that contains feedback + feedbackSpan = document.createElement('span'); + feedbackSpan.id = `${ww_id}-${name}-feedback`; + feedbackHeadline = document.createElement('span'); + feedbackHeadline.id = `${ww_id}-${name}-headline`; + feedbackHeadline.textContent = headline; + feedbackSpan.appendChild(feedbackHeadline); + // make a non breaking span to surround select, sr span, and feedback button + let nbSpan = document.createElement('span'); + nbSpan.classList.add("nobreak"); + select.parentNode.insertBefore(nbSpan, select); + nbSpan.appendChild(select); + // put a span around select since select does not support ::after pseudo elements + let psSpan = document.createElement('span'); + psSpan.classList.add("select-wrapper", correctClass); + select.parentNode.insertBefore(psSpan, select); + psSpan.appendChild(select); + // put a visibly hidden (screen-reader only) span with feedback + let srSpan = document.createElement('span'); + srSpan.classList.add("visually-hidden"); + srSpan.appendChild(feedbackSpan); + nbSpan.appendChild(srSpan); + // give the feedback to sr users immediately + select.setAttribute('aria-describedby',`${ww_id}-${name}-feedback`); + // make the feedback button + srSpan.after(createFeedbackButton(`${ww_id}-${name}`, title)); + } + } + + let iframeContents = '' + + '' + + `` + + '' + + '' + + '' + + ''; + + // Determine javascript and css dependencies + const extra_css_files = []; + const extra_js_files = []; + //if (data.rh_result.flags.extra_js_files) { + // for (const jsFile of data.rh_result.flags.extra_js_files) { + // if (jsFile.file == "js/apps/GraphTool/graphtool.min.js" || jsFile.file == "js/apps/Scaffold/scaffold.js") { + // extra_css_files.push({ file: 'js/vendor/bootstrap/css/bootstrap.css', external: '0' }); + // extra_css_files.push({ file: 'themes/math4/math4.css', external: '0' }); + // extra_js_files.push({ file: 'js/vendor/bootstrap/js/bootstrap.js', external: '0' }); + // } + // } + //} + + if (data.extra_css_files) data.extra_css_files.unshift(...extra_css_files); + else data.extra_css_files = extra_css_files; + for (const cssFile of data.extra_css_files) { + if (/knowl/.test(cssFile.file)) continue; + iframeContents += ''; + } + + if (data.extra_js_files) data.extra_js_files.unshift(...extra_js_files); + else data.extra_js_files = extra_js_files; + for (const jsFile of data.extra_js_files) { + if (/knowl/.test(jsFile.file)) continue; + iframeContents += ''; + } + + iframeContents += + '' + + '' + + '' + + `` + + '
    ' + form.outerHTML + '
    ' + + ''; + + let iframe; + // If there is no action this is the initialization call. + if (!action) { + // Create the iframe. + iframe = document.createElement('iframe'); + iframe.style.width = '1px'; + iframe.style.minWidth = '100%'; + iframe.classList.add('problem-iframe'); + + // Hide the static problem + ww_container.querySelector('.problem-contents').classList.add('hidden-content'); + + if (activate_button != null) { + // Make sure the iframe follows the activate button in the DOM + activate_button.after(iframe); + } else { + ww_container.prepend(iframe); + } + + iFrameResize({ checkOrigin: false, scrolling: 'omit', heightCalculationMethod: 'min' }, iframe); + + iframe.addEventListener('load', () => { + // Set up form submission from inside the iframe. + const iframeForm = iframe.contentDocument.getElementById(ww_id + '-form'); + iframeForm.addEventListener('submit', (e) => { + handleWW(ww_id, "check"); + e.preventDefault(); + }); + + iframe.contentDocument.querySelectorAll('.collapse.in').forEach(collapse => collapse.classList.add('expanded')); + iframe.contentWindow.jQuery('.collapse').on('shown', function(e) { if (e.target != this) return; this.classList.add('expanded'); }); + iframe.contentWindow.jQuery('.collapse').on('hide', function(e) { if (e.target != this) return; this.classList.remove('expanded'); }); + + iframe.contentDocument.querySelectorAll("button.ww-feedback[data-bs-content]").forEach(button => { + const bsPopover = new iframe.contentWindow.bootstrap.Popover(button, { + html: true, + placement: 'bottom', + fallbackPlacements: [], + trigger: 'click', + customClass: 'result-popover' + (button.dataset.emptyContent ? ' nocontent' : '') + }); + bsPopover.show(); + const content = iframe.contentDocument.getElementById(button.id.replace("-feedback-button", "-content")); + const popover = content.parentNode.parentNode; + bsPopover.hide(); + + /*button.addEventListener('shown.bs.popover', () => { + // Note that the button itself has the aria-describedby attribute set by bootstrap automatically. + button.previousElementSibling?.setAttribute('aria-describedby', popover.id); + });*/ + + button.addEventListener('click', () => bsPopover.show(), { once: true }); + + popover.querySelector('.popover-arrow').remove(); + const title = popover.querySelector('.popover-header'); + if (button.dataset.emptyContent) content.parentNode.remove(); + if (title.textContent == localize_correct + '!') title.classList.add('correct'); + }); + iframe.contentWindow.MathJax.startup.promise.then(() => iframe.contentWindow.MathJax.typesetPromise(['.popover', '.popover-content'])); + }); + } else { + iframe = ww_container.querySelector('.problem-iframe'); + } + + iframe.srcdoc = iframeContents; + + iframe.addEventListener('load', () => { + // Remove the loader overlay + loader.remove(); + }, { once: true }) + + // Place focus on the problem. + ww_container.focus() + }); +} + +function WWshowCorrect(ww_id, answers) { + const ww_container = document.getElementById(ww_id); + const iframe = ww_container.querySelector('.problem-iframe'); + + const body = iframe.contentDocument.getElementById(ww_id + '-body') + $("body").trigger("runestone_show_correct", { + "ww_id": ww_id, + "answers": answers + }); + + let inputs = body.querySelectorAll("input:not([type=hidden])"); + for (const input of inputs) { + const name = input.name; + const span_id = `${ww_id}-${name}-correct`; + + if (input.type == 'text' && answers[name] && !(iframe.contentDocument.getElementById(span_id))) { + const input_id = input.id; + const mq_span = iframe.contentDocument.getElementById(`mq-answer-${input_id}`); + if (mq_span) { + mq_span.style.display = 'none'; + } + const feedbackButton = iframe.contentDocument.getElementById(`${ww_id}-${name}-feedback-button`); + if (feedbackButton) { + iframe.contentWindow.bootstrap.Popover.getInstance(feedbackButton)?.hide(); + feedbackButton.remove(); + } + label = iframe.contentDocument.getElementById(`${span_id}-label`); + if (label) { + label.parentElement.insertBefore(input, label); + label.remove(); + } + input.type = "hidden"; + // we need to convert things like < in answers to < + const correct_ans_text = iframe.contentDocument.createElement('div'); + correct_ans_text.innerHTML = answers[name].correct_ans; + input.value = correct_ans_text.textContent; + const show_span = iframe.contentDocument.createElement('span'); + show_span.id = span_id; + show_span.appendChild(answers[name].correct_ans_latex_string + ? iframe.contentDocument.createTextNode('\\(' + answers[name].correct_ans_latex_string + '\\)') + : iframe.contentDocument.createTextNode(answers[name].correct_ans)); + input.parentElement.insertBefore(show_span, input); + } + + if (input.type.toUpperCase() == 'RADIO' && answers[name]) { + const feedbackButton = iframe.contentDocument.getElementById(`${ww_id}-${name}-feedback-button`); + if (feedbackButton) { + iframe.contentWindow.bootstrap.Popover.getInstance(feedbackButton)?.hide(); + feedbackButton.remove(); + } + const correct_ans = answers[name].correct_choice || answers[name].correct_ans; + if (input.value == correct_ans) { + input.checked = true; + //input.setAttribute('checked', 'checked'); + } else { + input.checked = false; + } + } + + if (input.type.toUpperCase() == 'CHECKBOX' && answers[name]) { + const correct_choices = answers[name].correct_choices; + if (correct_choices.includes(input.value)) { + input.checked = true; + // input.setAttribute('checked', 'checked'); + } else { + input.checked = false; + // input.setAttribute('checked', false); + } + } + } + + const hiddenInputs = body.querySelectorAll("input[type=hidden]"); + for (const input of hiddenInputs) { + const name = input.name; + if (!input.nextElementSibling) continue; + const graphtoolContainer = input.nextElementSibling.nextElementSibling; + if (graphtoolContainer && answers[name] && graphtoolContainer.classList.contains('graphtool-container')) { + const feedbackButton = iframe.contentDocument.getElementById(`${ww_id}-${name}-feedback-button`); + if (feedbackButton) { + iframe.contentWindow.bootstrap.Popover.getInstance(feedbackButton)?.hide(); + feedbackButton.remove(); + } + const correct_ans_div = iframe.contentDocument.createElement('div'); + input.parentElement.insertBefore(correct_ans_div, graphtoolContainer); + graphtoolContainer.style.display = 'none'; + input.value = answers[name].correct_ans; + iframe.contentWindow.jQuery(correct_ans_div).html(answers[name].correct_ans_latex_string); + const script = iframe.contentDocument.createElement('script'); + script.textContent = correct_ans_div.querySelector('script').textContent + .replace('\nwindow.addEventListener("DOMContentLoaded",', '(') + .replace(/;\n$/, '();'); + iframe.contentDocument.body.appendChild(script); + } + } + + let selects = body.querySelectorAll("select:not([type=hidden])"); + for (const select of selects) { + const name = select.name; + const span_id = `${ww_id}-${name}-correct`; + if (answers[name] && !iframe.contentDocument.getElementById(span_id)) { + const feedbackButton = iframe.contentDocument.getElementById(`${ww_id}-${name}-feedback-button`); + if (feedbackButton) { + iframe.contentWindow.bootstrap.Popover.getInstance(feedbackButton)?.hide(); + feedbackButton.remove(); + } + select.style.display = "none"; + select.value = answers[name].correct_ans; + const show_span = iframe.contentDocument.createElement('span'); + show_span.id = span_id; + show_span.appendChild(answers[name].correct_ans_latex_string + ? iframe.contentDocument.createTextNode('\\(' + answers[name].correct_ans_latex_string + '\\)') + : iframe.contentDocument.createTextNode(answers[name].correct_ans)); + select.parentElement.insertBefore(show_span, select); + } + } + + // run MathJax on our new rendering + // FIXME: We only need to typeset the added elements, not the entire body. + const mathjaxTypesetScript = iframe.contentDocument.createElement('script'); + mathjaxTypesetScript.textContent = 'MathJax.startup.promise.then(() => MathJax.typesetPromise([document.body]));'; + iframe.contentDocument.body.appendChild(mathjaxTypesetScript); +} + +function resetWW(ww_id) { + const ww_container = document.getElementById(ww_id); + const activate_button = document.getElementById(ww_id + '-button'); + + ww_container.dataset.current_seed = ww_container.dataset.seed; + + iframe = ww_container.querySelector('.problem-iframe'); + iframe.remove(); + + ww_container.querySelector('.problem-contents').classList.remove('hidden-content'); + + ww_container.querySelector('.problem-buttons.webwork').remove(); + ww_container.querySelector('.problem-buttons').classList.remove('hidden-content'); + // if the newer activate button is there (but hidden) bring it back too + if (activate_button != null) {activate_button.classList.remove('hidden-content');}; +} + +function adjustSrcHrefs(container,ww_domain) { + container.querySelectorAll('[href]').forEach((node) => { + const href = node.attributes.href.value; + if (href !== '#' && !href.match(/^[a-z]+:\/\//i)) node.href = ww_domain + '/' + href; + }); + container.querySelectorAll('[src]').forEach((node) => { + node.src = new URL(node.attributes.src.value, ww_domain).href; + }); +} + +function translateHintSol(ww_id, body_div, ww_domain, b_ptx_has_hint, b_ptx_has_solution, hint_label_text, solution_label_text) { + // the problem text may come with "hint"s and "solution"s + // Each one is an "a" with the class ".knowl" and the data-type attribute of either "hint" or "solution". + // the WeBWorK knowl js would normally handle this, but we want PreTeXt knowl js to handle it + // so we replace the "a" with the content that should be there for PTX knowl js + // also if hint/sol were missing from the static version, we want these removed here + + const hintSols = body_div.querySelectorAll('.knowl[data-type="hint"],.knowl[data-type="solution"]'); + let solutionlikewrapper; + for (const hintSol of hintSols) { + const hintsolp = hintSol.parentNode; + if (!hintsolp) continue; + const hintsolpp = hintsolp.parentNode; //the div that contains each p with a knowl anchor + const hintSolType = hintSol.dataset.type; + + if (hintsolpp.querySelectorAll(".webwork.solutions").length == 0) + { + solutionlikewrapper = document.createElement('div'); + solutionlikewrapper.classList.add('webwork', 'solutions'); + hintsolpp.insertBefore(solutionlikewrapper, hintsolp); + } + + if ((hintSolType == 'solution' && !b_ptx_has_solution) || + (hintSolType == 'hint' && !b_ptx_has_hint)) + continue; + + const knowlDetails = document.createElement('details'); + knowlDetails.classList.add(hintSolType); + knowlDetails.classList.add('solution-like'); + knowlDetails.classList.add('born-hidden-knowl'); + + const knowlSummary = document.createElement('summary'); + const summaryLabel = document.createElement('span'); + summaryLabel.classList.add('type'); + summaryLabel.innerHTML = hintSolType == 'hint' ? hint_label_text : solution_label_text; + knowlSummary.appendChild(summaryLabel); + knowlDetails.appendChild(knowlSummary); + + const knowlContents = document.createElement('div'); + knowlContents.classList.add(hintSolType); + knowlContents.classList.add('solution-like'); + knowlContents.innerHTML = hintsolp.firstElementChild.dataset.knowlContents; + knowlDetails.appendChild(knowlContents); + + adjustSrcHrefs(knowlContents, ww_domain) + solutionlikewrapper.appendChild(knowlDetails); + } + for (const hintSol of hintSols) { + hintSol.parentNode?.remove(); + } +} + +function cloneAttributes(target, source) { + [...source.attributes].forEach( attr => { target.setAttribute(attr.nodeName ,attr.nodeValue) }); +} + +function createFeedbackButton(id, title, content) { + const feedbackButton = document.createElement('button'); + feedbackButton.dataset.bsTitle = title; + feedbackButton.dataset.bsContent = `
    ${content || ''}
    `; + if (!content) feedbackButton.dataset.emptyContent = '1'; + const contentSpan = document.createElement('span'); + contentSpan.innerHTML = ``; + feedbackButton.appendChild(contentSpan); + feedbackButton.type = 'button'; + feedbackButton.classList.add('ww-feedback'); + + feedbackButton.id = `${id}-feedback-button`; + feedbackButton.dataset.bsToggle = 'popover'; + + return feedbackButton; +} + +function webworkSeedHash(string) { + var hash = 0, i, chr; + if (string.length === 0) return hash; + for (i = 0; i < string.length; i++) { + chr = string.charCodeAt(i); + hash = ((hash << 5) - hash) + chr; + hash |= 0; //Convert to 32bit integer + } + return Math.abs(hash); +}; diff --git a/slides-annotated/_static/pretext/js/pretext-webwork/2.18/pretext-webwork.js b/slides-annotated/_static/pretext/js/pretext-webwork/2.18/pretext-webwork.js new file mode 100755 index 0000000..c9b6b7c --- /dev/null +++ b/slides-annotated/_static/pretext/js/pretext-webwork/2.18/pretext-webwork.js @@ -0,0 +1,1058 @@ +//Critical: +// TODO: Think about what should be done for an essay question. + +//Accessibility: + +//Enhancement: +// TODO: In a scaffold problem, adjust the green bar for when one part is correct. +// TODO: Have randomize check that new seed is actually producing new HTML. +// TODO: Don't offer randomize or button unless we know a new version can be produced after trying a few seeds. +// TODO: In a staged problem, Make Interactive button is adjacent to solution knowls; move it. +// TODO: MathQuill +// TODO: Image pop up. +// TODO: Deal with singleResult MultiAnswer problems. + +//Styling: +// TODO: Review all styling in all scenarios (staged/not, correct/partly-correct/incorrect/blank, single/multiple) +// TODO: Sean: I think I'd like to see a slightly larger font size on the buttons + +function handleWW(ww_id, action) { + const ww_container = document.getElementById(ww_id); + const ww_domain = ww_container.dataset.domain; + const ww_problemSource = ww_container.dataset.problemsource; + const ww_sourceFilePath = ww_container.dataset.sourcefilepath; + const ww_course_id = ww_container.dataset.courseid; + const ww_user_id = ww_container.dataset.userid; + const ww_course_password = ww_container.dataset.coursepassword; + const localize_correct = ww_container.dataset.localizeCorrect || "Correct"; + const localize_incorrect = ww_container.dataset.localizeIncorrect || "Incorrect"; + const localize_blank = ww_container.dataset.localizeBlank || "Blank"; + const localize_submit = ww_container.dataset.localizeSubmit || "Submit"; + const localize_check_responses = ww_container.dataset.localizeCheckResponses || "Check Responses"; + const localize_reveal = ww_container.dataset.localizeReveal || "Reveal"; + const localize_randomize = ww_container.dataset.localizeRandomize || "Randomize"; + const localize_reset = ww_container.dataset.localizeReset || "Reset"; + const runestone_logged_in = (typeof eBookConfig !== 'undefined' && eBookConfig.username !== ''); + // will be null on pages generated prior to late December 2022 + const activate_button = document.getElementById(ww_id + '-button') + + // Set the current seed + if (!action) { + ww_container.dataset.current_seed = ww_container.dataset.seed; + if (runestone_logged_in) { + ww_container.dataset.current_seed = webworkSeedHash(eBookConfig.username + ww_container.dataset.current_seed); + } + } + else if (action == 'randomize') ww_container.dataset.current_seed = Number(ww_container.dataset.current_seed) + 100; + + let loader = document.createElement('div'); + loader.style.position = 'absolute'; + loader.style.left = 0; + loader.style.top = 0; + loader.style.backgroundColor = 'rgba(0.2, 0.2, 0.2, 0.4)'; + loader.style.color = 'white'; + loader.style.width = '100%'; + loader.style.height = '100%'; + loader.style.display = 'flex'; + loader.style.alignItems = 'center'; + loader.style.justifyContent = 'center'; + loader.style.marginTop = '0'; + loader.tabIndex = -1; + const loaderText = document.createElement('span'); + loaderText.textContent = 'Loading'; + loaderText.style.fontSize = '2rem'; + loader.appendChild(loaderText); + ww_container.appendChild(loader); + loader.focus(); + + if (!action) { + // Determine if static version shows hints, solutions, or answers and save that information in the container dataset for later runs. + ww_container.dataset.hasHint = ww_container.getElementsByClassName('hint').length > 0; + ww_container.dataset.hasSolution = ww_container.getElementsByClassName('solution').length > 0; + ww_container.dataset.hasAnswer = ww_container.getElementsByClassName('answer').length > 0; + // Get (possibly localized) label text for hints and solutions. + ww_container.dataset.hintLabelText = ww_container.dataset.hasHint == 'true' + ? ww_container.querySelectorAll('.hint-knowl span.type, details.hint span.type')[0].textContent : 'Hint'; + ww_container.dataset.solutionLabelText = ww_container.dataset.hasSolution == 'true' + ? ww_container.querySelectorAll('.solution-knowl span.type, details.solution span.type')[0].textContent : 'Solution'; + + ww_container.tabIndex = -1; + } + + let url; + + if (action == 'check') { + const iframe = ww_container.querySelector('.problem-iframe'); + const formData = new FormData(iframe.contentDocument.getElementById(ww_id + "-form")); + const params = new URLSearchParams(formData); + url = new URL(ww_domain + '/webwork2/html2xml?' + params.toString()) + url.searchParams.append("answersSubmitted", '1'); + url.searchParams.append('WWsubmit', "1"); + } else { + url = new URL(ww_domain + '/webwork2/html2xml'); + url.searchParams.append("problemSeed", ww_container.dataset.current_seed); + if (ww_problemSource) url.searchParams.append("problemSource", ww_problemSource); + else if (ww_sourceFilePath) url.searchParams.append("sourceFilePath", ww_sourceFilePath); + url.searchParams.append("answersSubmitted", '0'); + url.searchParams.append("displayMode", "MathJax"); + url.searchParams.append("courseID", ww_course_id); + url.searchParams.append("userID", ww_user_id); + url.searchParams.append("course_password", ww_course_password); + url.searchParams.append("outputformat", "raw"); + // note ww_container.dataset.hasSolution is a string, possibly 'false' which is true + url.searchParams.append("showSolutions", ww_container.dataset.hasSolution == 'true' ? '1' : '0'); + url.searchParams.append("showHints", ww_container.dataset.hasHint == 'true' ? '1' : '0'); + url.searchParams.append("problemUUID",ww_id); + } + + // get the json and do stuff with what we get + $.getJSON(url.toString(), (data) => { + // Create the form that will contain the text and input fields of the interactive problem. + const form = document.createElement("form"); + form.id = ww_id + "-form"; + + // Create a div for the problem text. + const body_div = document.createElement("div"); + body_div.id = ww_id + "-body"; + body_div.classList.add("exercise", "exercise-like"); + body_div.lang = data.lang; + body_div.dir = data.dir; + + // Dump the problem text, answer blanks, etc. + body_div.innerHTML = data.rh_result.text; + + // Replace all hn headings with h6 headings. + for (const tag_name of ['h6', 'h5', 'h4', 'h3', 'h2', 'h1']) { + const headings = body_div.getElementsByTagName(tag_name); + for (heading of headings) { + const new_heading = document.createElement("h6"); + new_heading.innerHTML = heading.innerHTML; + cloneAttributes(new_heading, heading); + new_heading.classList.add('webwork-part'); + heading.replaceWith(new_heading); + } + } + + adjustSrcHrefs(body_div, ww_domain); + + translateHintSol(ww_id, body_div, ww_domain, + ww_container.dataset.hasHint == 'true', ww_container.dataset.hasSolution == 'true', + ww_container.dataset.hintLabelText, ww_container.dataset.solutionLabelText) + + // insert previous answers + if (runestone_logged_in) { + const answersObject = (wwList[ww_id.replace(/-ww-rs$/,'')].answers ? wwList[ww_id.replace(/-ww-rs$/,'')].answers : {'answers' : [], 'mqAnswers' : []}); + const mqAnswers = answersObject.mqAnswers; + for (const mqAnswer in mqAnswers) { + const mqInput = body_div.querySelector('input[id=' + mqAnswer + ']'); + if (mqInput && mqInput.value == '') { + mqInput.setAttribute('value', mqAnswers[mqAnswer]); + } + } + const answers = answersObject.answers; + for (const answer in answers) { + const input = body_div.querySelector('input[id=' + answer + ']'); + if (input && input.value == '') { + input.setAttribute('value', answers[answer]); + } + if (input && input.type.toUpperCase() == 'RADIO') { + const buttons = body_div.querySelectorAll('input[name=' + answer + ']'); + for (const button of buttons) { + if (button.value == answers[answer]) { + button.setAttribute('checked', 'checked'); + } + } + } + if (input && input.type.toUpperCase() == 'CHECKBOX') { + const checkboxes = body_div.querySelectorAll('input[name=' + answer + ']'); + for (const checkbox of checkboxes) { + // This is not a bulletproof approach if the problem used input values that are weird + // For example, with commas in them + // However, we are stuck with WW providing answers[answer] as a string like `[value0, value1]` + // and note that it is not `["value0", "value1"]`, so we cannot cleanly parse it into an array + let checkbox_regex = new RegExp('(\\[|, )' + checkbox.value + '(, |\\])'); + if (answers[answer].match(checkbox_regex)) { + checkbox.setAttribute('checked', 'checked'); + } + } + } + var select = body_div.querySelector('select[id=' + answer + ']'); + if (select && answers[answer]) { + // answers[answer] may be wrapped in \text{...} that we want to remove, since value does not have this. + let this_answer = answers[answer]; + if (/^\\text\{.*\}$/.test(this_answer)) {this_answer = this_answer.match(/^\\text\{(.*)\}$/)[1]}; + let quote_escaped_answer = this_answer.replace(/"/g, '\\"'); + const option = body_div.querySelector(`select[id="${answer}"] option[value="${quote_escaped_answer}"]`); + if (option) {option.setAttribute('selected', 'selected')}; + } + } + } + + // insert our cleaned up problem text + form.appendChild(body_div); + + // Set up hidden input fields that the form uses + const wwInputs = { + problemSeed: data.inputs_ref.problemSeed, + problemUUID: data.inputs_ref.problemUUID, + psvn: data.inputs_ref.psvn, + courseName: ww_course_id, + courseID: ww_course_id, + userID: ww_user_id, + course_password: ww_course_password, + displayMode: "MathJax", + session_key: data.rh_result.session_key, + outputformat: "raw", + language: data.formLanguage, + showSummary: data.showSummary, + // note ww_container.dataset.hasSolution is a string, possibly 'false' which is true + showSolutions: ww_container.dataset.hasSolution == 'true' ? '1' : '0', + showHints: ww_container.dataset.hasHint == 'true' ? '1' : '0', + forcePortNumber: data.forcePortNumber + }; + + if (ww_sourceFilePath) wwInputs.sourceFilePath = ww_sourceFilePath; + else if (ww_problemSource) wwInputs.problemSource = ww_problemSource; + + for (const wwInputName of Object.keys(wwInputs)) { + const input = document.createElement('input'); + input.type = 'hidden'; + input.name = wwInputName; + input.value = wwInputs[wwInputName]; + form.appendChild(input); + } + + // Prepare answers object + const answers = {}; + // id the answers even if we won't populate them + Object.keys(data.rh_result.answers).forEach(function(id) { + answers[id] = {}; + }, data.rh_result.answers); + if (ww_container.dataset.hasAnswer == 'true') { + // Update answer data + Object.keys(data.rh_result.answers).forEach(function(id) { + answers[id] = { + correct_ans: this[id].correct_ans, + correct_ans_latex_string: this[id].correct_ans_latex_string, + correct_choice: this[id].correct_choice, + correct_choices: this[id].correct_choices, + }; + }, data.rh_result.answers); + } + + let buttonContainer = ww_container.querySelector('.problem-buttons.webwork'); + // Create the submission buttons if they have not yet been created. + if (!buttonContainer) { + // Hide the original div that contains the old make active button. + ww_container.querySelector('.problem-buttons').classList.add('hidden-content'); + // And the newer activate button if it is there + if (activate_button != null) {activate_button.classList.add('hidden-content');}; + + // Create a new div for the webwork buttons. + buttonContainer = document.createElement('div'); + buttonContainer.classList.add('problem-buttons', 'webwork'); + if (activate_button != null) { + // Make sure the button container follows the activate button in the DOM + activate_button.after(buttonContainer); + } else { + ww_container.prepend(buttonContainer); + } + + // Check button + const check = document.createElement("button"); + check.type = "button"; + check.id = ww_id + '-check'; + check.style.marginRight = "0.25rem"; + check.classList.add('webwork-button'); + + // Adjust if more than one answer to check + const answerCount = body_div.querySelectorAll("input:not([type=hidden])").length + + body_div.querySelectorAll("select:not([type=hidden])").length; + + check.textContent = runestone_logged_in ? localize_submit : localize_check_responses; + check.addEventListener('click', () => handleWW(ww_id, "check")); + + buttonContainer.appendChild(check); + + // Show correct answers button if original PTX has answer knowl. + if (ww_container.dataset.hasAnswer == 'true') { + const correct = document.createElement("button"); + correct.classList.add("show-correct", 'webwork-button'); + correct.type = "button"; + correct.style.marginRight = "0.25rem"; + correct.textContent = localize_reveal; + correct.addEventListener('click', () => WWshowCorrect(ww_id, answers)); + buttonContainer.appendChild(correct); + } + + // randomize button + const randomize = document.createElement("button") + randomize.type = "button"; + randomize.classList.add('webwork-button'); + randomize.style.marginRight = "0.25rem"; + randomize.textContent = localize_randomize; + randomize.addEventListener('click', () => handleWW(ww_id, 'randomize')); + buttonContainer.appendChild(randomize) + + // reset button + const reset = document.createElement("button") + reset.type = "button" + reset.classList.add('webwork-button'); + reset.textContent = localize_reset; + reset.addEventListener('click', () => resetWW(ww_id)); + buttonContainer.appendChild(reset) + } else { + // Update the click handler for the show correct button. + if (ww_container.dataset.hasAnswer == 'true') { + const correct = buttonContainer.querySelector('.show-correct'); + const correctNew = correct.cloneNode(true); + correctNew.addEventListener('click', () => WWshowCorrect(ww_id, answers)); + correct.replaceWith(correctNew); + } + } + + if (action == 'check') { + // Runestone trigger + // TODO: condition on platform=Runestone + $("body").trigger('runestone_ww_check', data) + + const inputs = body_div.querySelectorAll("input:not([type=hidden])"); + for (const input of inputs) { + const name = input.name; + if (input.type == 'text' && answers[name]) { + const score = data.rh_result.answers[name].score; + let headline = ''; + let correctClass = ''; + if (score >= 1) { + headline = localize_correct + '!'; + correctClass = 'correct'; + } else if (score > 0 && score < 1) { + headline = `${Math.round(score * 100)}% ${localize_correct}.`; + correctClass = 'partly-correct'; + } else if (data.rh_result.answers[name].student_ans == '') { + headline = localize_blank + '.'; + correctClass = 'blank'; + } else if (score <= 0) { + headline = localize_incorrect + '.'; + correctClass = 'incorrect'; + } + let title = `${headline}`; + let message = (data.rh_result.answers[name].ans_message ? data.rh_result.answers[name].ans_message : ''); + input.classList.add(correctClass); + // make a span that contains feedback + feedbackSpan = document.createElement('span'); + feedbackSpan.id = `${ww_id}-${name}-feedback`; + feedbackHeadline = document.createElement('span'); + feedbackHeadline.id = `${ww_id}-${name}-headline`; + feedbackHeadline.textContent = headline; + feedbackSpan.appendChild(feedbackHeadline); + feedbackMessage = document.createElement('span'); + feedbackMessage.id = `${ww_id}-${name}-message`; + feedbackMessage.textContent = message; + feedbackSpan.appendChild(feedbackMessage); + // make a non breaking span to surround input, sr span, and feedback button + let nbSpan = document.createElement('span'); + nbSpan.classList.add("nobreak"); + input.parentNode.insertBefore(nbSpan, input); + nbSpan.appendChild(input); + // put a visibly hidden (screen-reader only) span with feedback + let srSpan = document.createElement('span'); + srSpan.classList.add("visually-hidden"); + srSpan.appendChild(feedbackSpan); + nbSpan.appendChild(srSpan); + // give the feedback to sr users immediately + input.setAttribute('aria-describedby',`${ww_id}-${name}-feedback`); + // make the feedback button + srSpan.after(createFeedbackButton(`${ww_id}-${name}`, title, message)); + + } + + if (input.type.toUpperCase() == 'RADIO' && answers[name]) { + const score = data.rh_result.answers[name].score; + const student_ans = data.rh_result.answers[name].student_value || data.rh_result.answers[name].student_ans; + const correct_ans = data.rh_result.answers[name].correct_choice || data.rh_result.answers[name].correct_ans; + if (input.value == student_ans) { + if (score == 1) { + input.parentNode.classList.add('correct'); + } else { + input.parentNode.classList.add('incorrect'); + } + const feedbackButton = createFeedbackButton(`${ww_id}-${name}`, + (student_ans == correct_ans) + ? `${localize_correct}` : `${localize_incorrect}.`) + feedbackButton.style.marginRight = '0.25rem'; + input.after(feedbackButton); + } + } + + if (input.type.toUpperCase() == 'CHECKBOX' && answers[name]) { + const score = data.rh_result.answers[name].score; + const student_ans = data.rh_result.answers[name].student_ans; + const correct_ans = data.rh_result.answers[name].correct_ans; + if (input.value == data.rh_result.answers[name].firstElement) { + const checkbox_div = input.parentNode.parentNode; + if (score == 1) { + checkbox_div.classList.add('correct'); + } else { + checkbox_div.classList.add('incorrect'); + } + const feedbackButton = createFeedbackButton(`${ww_id}-${name}`, + (student_ans == correct_ans) + ? `${localize_correct}` : `${localize_incorrect}.`) + checkbox_div.insertBefore(feedbackButton, checkbox_div.firstChild); + } + } + } + + const hiddenInputs = body_div.querySelectorAll("input[type=hidden]"); + for (const input of hiddenInputs) { + const name = input.name; + if (!input.nextElementSibling) continue; + const graphtoolContainer = input.nextElementSibling.nextElementSibling; + if (graphtoolContainer && answers[name] && graphtoolContainer.classList.contains('graphtool-container')) { + graphtoolContainer.style.position = 'relative'; + const score = data.rh_result.answers[name].score; + let title = ''; + if (score == 1) { + title = `${localize_correct}`; + } else if (score > 0 && score < 1) { + title = `${Math.round(score * 100)}% ${localize_correct}.`; + } else if (data.rh_result.answers[name].student_ans == '') { + // do nothing if the submitted answer is blank and the problem has not already been scored as correct + continue; + } else if (score == 0) { + title = `${localize_incorrect}.`; + } + + const feedbackButton = createFeedbackButton(`${ww_id}-${name}`, title, data.rh_result.answers[name].ans_message); + feedbackButton.style.position = 'absolute'; + feedbackButton.style.left = '100%'; + feedbackButton.style.top = 0; + feedbackButton.style.marginLeft = '0.5rem'; + feedbackButton.dataset.bsContainer = 'body'; + graphtoolContainer.appendChild(feedbackButton); + if (score == 1) { + graphtoolContainer.classList.add('correct'); + } else { + graphtoolContainer.classList.add('incorrect'); + } + + } + } + + const selects = body_div.querySelectorAll("select:not([type=hidden])"); + for (const select of selects) { + const name = select.name; + const score = data.rh_result.answers[name].score; + let title = ''; + if (score == 1) { + headline = localize_correct + '!'; + correctClass = 'correct'; + } else { + headline = localize_incorrect + '.'; + correctClass = 'incorrect'; + } + select.classList.add(correctClass); + title = `${headline}`; + // make a span that contains feedback + feedbackSpan = document.createElement('span'); + feedbackSpan.id = `${ww_id}-${name}-feedback`; + feedbackHeadline = document.createElement('span'); + feedbackHeadline.id = `${ww_id}-${name}-headline`; + feedbackHeadline.textContent = headline; + feedbackSpan.appendChild(feedbackHeadline); + // make a non breaking span to surround select, sr span, and feedback button + let nbSpan = document.createElement('span'); + nbSpan.classList.add("nobreak"); + select.parentNode.insertBefore(nbSpan, select); + nbSpan.appendChild(select); + // put a span around select since select does not support ::after pseudo elements + let psSpan = document.createElement('span'); + psSpan.classList.add("select-wrapper", correctClass); + select.parentNode.insertBefore(psSpan, select); + psSpan.appendChild(select); + // put a visibly hidden (screen-reader only) span with feedback + let srSpan = document.createElement('span'); + srSpan.classList.add("visually-hidden"); + srSpan.appendChild(feedbackSpan); + nbSpan.appendChild(srSpan); + // give the feedback to sr users immediately + select.setAttribute('aria-describedby',`${ww_id}-${name}-feedback`); + // make the feedback button + srSpan.after(createFeedbackButton(`${ww_id}-${name}`, title)); + } + } + + let iframeContents = '' + + '' + + `` + + '' + + '' + + '' + + ''; + + // Determine javascript and css dependencies + const extra_css_files = []; + const extra_js_files = []; + //if (data.rh_result.flags.extra_js_files) { + // for (const jsFile of data.rh_result.flags.extra_js_files) { + // if (jsFile.file == "js/apps/GraphTool/graphtool.min.js" || jsFile.file == "js/apps/Scaffold/scaffold.js") { + // extra_css_files.push({ file: 'js/vendor/bootstrap/css/bootstrap.css', external: '0' }); + // extra_css_files.push({ file: 'themes/math4/math4.css', external: '0' }); + // extra_js_files.push({ file: 'js/vendor/bootstrap/js/bootstrap.js', external: '0' }); + // } + // } + //} + + if (data.extra_css_files) data.extra_css_files.unshift(...extra_css_files); + else data.extra_css_files = extra_css_files; + for (const cssFile of data.extra_css_files) { + if (/knowl/.test(cssFile.file)) continue; + iframeContents += ''; + } + + if (data.extra_js_files) data.extra_js_files.unshift(...extra_js_files); + else data.extra_js_files = extra_js_files; + for (const jsFile of data.extra_js_files) { + if (/knowl/.test(jsFile.file)) continue; + iframeContents += ''; + } + + iframeContents += + '' + + '' + + '' + + `` + + '
    ' + form.outerHTML + '
    ' + + ''; + + let iframe; + // If there is no action this is the initialization call. + if (!action) { + // Create the iframe. + iframe = document.createElement('iframe'); + iframe.style.width = '1px'; + iframe.style.minWidth = '100%'; + iframe.classList.add('problem-iframe'); + + // Hide the static problem + ww_container.querySelector('.problem-contents').classList.add('hidden-content'); + + if (activate_button != null) { + // Make sure the iframe follows the activate button in the DOM + activate_button.after(iframe); + } else { + ww_container.prepend(iframe); + } + + iFrameResize({ checkOrigin: false, scrolling: 'omit', heightCalculationMethod: 'min' }, iframe); + + iframe.addEventListener('load', () => { + // Set up form submission from inside the iframe. + const iframeForm = iframe.contentDocument.getElementById(ww_id + '-form'); + iframeForm.addEventListener('submit', (e) => { + handleWW(ww_id, "check"); + e.preventDefault(); + }); + + iframe.contentDocument.querySelectorAll('.collapse.in').forEach(collapse => collapse.classList.add('expanded')); + iframe.contentWindow.jQuery('.collapse').on('shown', function(e) { if (e.target != this) return; this.classList.add('expanded'); }); + iframe.contentWindow.jQuery('.collapse').on('hide', function(e) { if (e.target != this) return; this.classList.remove('expanded'); }); + + iframe.contentDocument.querySelectorAll("button.ww-feedback[data-bs-content]").forEach(button => { + const bsPopover = new iframe.contentWindow.bootstrap.Popover(button, { + html: true, + placement: 'bottom', + fallbackPlacements: [], + trigger: 'click', + customClass: 'result-popover' + (button.dataset.emptyContent ? ' nocontent' : '') + }); + bsPopover.show(); + const content = iframe.contentDocument.getElementById(button.id.replace("-feedback-button", "-content")); + const popover = content.parentNode.parentNode; + bsPopover.hide(); + + /*button.addEventListener('shown.bs.popover', () => { + // Note that the button itself has the aria-describedby attribute set by bootstrap automatically. + button.previousElementSibling?.setAttribute('aria-describedby', popover.id); + });*/ + + button.addEventListener('click', () => bsPopover.show(), { once: true }); + + popover.querySelector('.popover-arrow').remove(); + const title = popover.querySelector('.popover-header'); + if (button.dataset.emptyContent) content.parentNode.remove(); + if (title.textContent == localize_correct + '!') title.classList.add('correct'); + }); + iframe.contentWindow.MathJax.startup.promise.then(() => iframe.contentWindow.MathJax.typesetPromise(['.popover', '.popover-content'])); + }); + } else { + iframe = ww_container.querySelector('.problem-iframe'); + } + + iframe.srcdoc = iframeContents; + + iframe.addEventListener('load', () => { + // Remove the loader overlay + loader.remove(); + }, { once: true }) + + // Place focus on the problem. + ww_container.focus() + }); +} + +function WWshowCorrect(ww_id, answers) { + const ww_container = document.getElementById(ww_id); + const iframe = ww_container.querySelector('.problem-iframe'); + + const body = iframe.contentDocument.getElementById(ww_id + '-body') + $("body").trigger("runestone_show_correct", { + "ww_id": ww_id, + "answers": answers + }); + + let inputs = body.querySelectorAll("input:not([type=hidden])"); + for (const input of inputs) { + const name = input.name; + const span_id = `${ww_id}-${name}-correct`; + + if (input.type == 'text' && answers[name] && !(iframe.contentDocument.getElementById(span_id))) { + const input_id = input.id; + const mq_span = iframe.contentDocument.getElementById(`mq-answer-${input_id}`); + if (mq_span) { + mq_span.style.display = 'none'; + } + const feedbackButton = iframe.contentDocument.getElementById(`${ww_id}-${name}-feedback-button`); + if (feedbackButton) { + iframe.contentWindow.bootstrap.Popover.getInstance(feedbackButton)?.hide(); + feedbackButton.remove(); + } + label = iframe.contentDocument.getElementById(`${span_id}-label`); + if (label) { + label.parentElement.insertBefore(input, label); + label.remove(); + } + input.type = "hidden"; + // we need to convert things like < in answers to < + const correct_ans_text = iframe.contentDocument.createElement('div'); + correct_ans_text.innerHTML = answers[name].correct_ans; + input.value = correct_ans_text.textContent; + const show_span = iframe.contentDocument.createElement('span'); + show_span.id = span_id; + show_span.appendChild(answers[name].correct_ans_latex_string + ? iframe.contentDocument.createTextNode('\\(' + answers[name].correct_ans_latex_string + '\\)') + : iframe.contentDocument.createTextNode(answers[name].correct_ans)); + input.parentElement.insertBefore(show_span, input); + } + + if (input.type.toUpperCase() == 'RADIO' && answers[name]) { + const feedbackButton = iframe.contentDocument.getElementById(`${ww_id}-${name}-feedback-button`); + if (feedbackButton) { + iframe.contentWindow.bootstrap.Popover.getInstance(feedbackButton)?.hide(); + feedbackButton.remove(); + } + const correct_ans = answers[name].correct_choice || answers[name].correct_ans; + if (input.value == correct_ans) { + input.checked = true; + //input.setAttribute('checked', 'checked'); + } else { + input.checked = false; + } + } + + if (input.type.toUpperCase() == 'CHECKBOX' && answers[name]) { + const correct_choices = answers[name].correct_choices; + if (correct_choices.includes(input.value)) { + input.checked = true; + // input.setAttribute('checked', 'checked'); + } else { + input.checked = false; + // input.setAttribute('checked', false); + } + } + } + + const hiddenInputs = body.querySelectorAll("input[type=hidden]"); + for (const input of hiddenInputs) { + const name = input.name; + if (!input.nextElementSibling) continue; + const graphtoolContainer = input.nextElementSibling.nextElementSibling; + if (graphtoolContainer && answers[name] && graphtoolContainer.classList.contains('graphtool-container')) { + const feedbackButton = iframe.contentDocument.getElementById(`${ww_id}-${name}-feedback-button`); + if (feedbackButton) { + iframe.contentWindow.bootstrap.Popover.getInstance(feedbackButton)?.hide(); + feedbackButton.remove(); + } + const correct_ans_div = iframe.contentDocument.createElement('div'); + input.parentElement.insertBefore(correct_ans_div, graphtoolContainer); + graphtoolContainer.style.display = 'none'; + input.value = answers[name].correct_ans; + iframe.contentWindow.jQuery(correct_ans_div).html(answers[name].correct_ans_latex_string); + const script = iframe.contentDocument.createElement('script'); + script.textContent = correct_ans_div.querySelector('script').textContent + .replace('\nwindow.addEventListener("DOMContentLoaded",', '(') + .replace(/;\n$/, '();'); + iframe.contentDocument.body.appendChild(script); + } + } + + let selects = body.querySelectorAll("select:not([type=hidden])"); + for (const select of selects) { + const name = select.name; + const span_id = `${ww_id}-${name}-correct`; + if (answers[name] && !iframe.contentDocument.getElementById(span_id)) { + const feedbackButton = iframe.contentDocument.getElementById(`${ww_id}-${name}-feedback-button`); + if (feedbackButton) { + iframe.contentWindow.bootstrap.Popover.getInstance(feedbackButton)?.hide(); + feedbackButton.remove(); + } + select.style.display = "none"; + select.value = answers[name].correct_ans; + const show_span = iframe.contentDocument.createElement('span'); + show_span.id = span_id; + show_span.appendChild(answers[name].correct_ans_latex_string + ? iframe.contentDocument.createTextNode('\\(' + answers[name].correct_ans_latex_string + '\\)') + : iframe.contentDocument.createTextNode(answers[name].correct_ans)); + select.parentElement.insertBefore(show_span, select); + } + } + + // run MathJax on our new rendering + // FIXME: We only need to typeset the added elements, not the entire body. + const mathjaxTypesetScript = iframe.contentDocument.createElement('script'); + mathjaxTypesetScript.textContent = 'MathJax.startup.promise.then(() => MathJax.typesetPromise([document.body]));'; + iframe.contentDocument.body.appendChild(mathjaxTypesetScript); +} + +function resetWW(ww_id) { + const ww_container = document.getElementById(ww_id); + const activate_button = document.getElementById(ww_id + '-button'); + + ww_container.dataset.current_seed = ww_container.dataset.seed; + + iframe = ww_container.querySelector('.problem-iframe'); + iframe.remove(); + + ww_container.querySelector('.problem-contents').classList.remove('hidden-content'); + + ww_container.querySelector('.problem-buttons.webwork').remove(); + ww_container.querySelector('.problem-buttons').classList.remove('hidden-content'); + // if the newer activate button is there (but hidden) bring it back too + if (activate_button != null) {activate_button.classList.remove('hidden-content');}; +} + +function adjustSrcHrefs(container,ww_domain) { + container.querySelectorAll('[href]').forEach((node) => { + const href = node.attributes.href.value; + if (href !== '#' && !href.match(/^[a-z]+:\/\//i)) node.href = ww_domain + '/' + href; + }); + container.querySelectorAll('[src]').forEach((node) => { + node.src = new URL(node.attributes.src.value, ww_domain).href; + }); +} + +function translateHintSol(ww_id, body_div, ww_domain, b_ptx_has_hint, b_ptx_has_solution, hint_label_text, solution_label_text) { + // the problem text may come with "hint"s and "solution"s + // Each one is an "a" with the class ".knowl" and the data-type attribute of either "hint" or "solution". + // the WeBWorK knowl js would normally handle this, but we want PreTeXt knowl js to handle it + // so we replace the "a" with the content that should be there for PTX knowl js + // also if hint/sol were missing from the static version, we want these removed here + + const hintSols = body_div.querySelectorAll('.knowl[data-type="hint"],.knowl[data-type="solution"]'); + let solutionlikewrapper; + for (const hintSol of hintSols) { + const hintsolp = hintSol.parentNode; + if (!hintsolp) continue; + const hintsolpp = hintsolp.parentNode; //the div that contains each p with a knowl anchor + const hintSolType = hintSol.dataset.type; + + if (hintsolpp.querySelectorAll(".webwork.solutions").length == 0) + { + solutionlikewrapper = document.createElement('div'); + solutionlikewrapper.classList.add('webwork', 'solutions'); + hintsolpp.insertBefore(solutionlikewrapper, hintsolp); + } + + if ((hintSolType == 'solution' && !b_ptx_has_solution) || + (hintSolType == 'hint' && !b_ptx_has_hint)) + continue; + + const knowlDetails = document.createElement('details'); + knowlDetails.classList.add(hintSolType); + knowlDetails.classList.add('solution-like'); + knowlDetails.classList.add('born-hidden-knowl'); + + const knowlSummary = document.createElement('summary'); + const summaryLabel = document.createElement('span'); + summaryLabel.classList.add('type'); + summaryLabel.innerHTML = hintSolType == 'hint' ? hint_label_text : solution_label_text; + knowlSummary.appendChild(summaryLabel); + knowlDetails.appendChild(knowlSummary); + + const knowlContents = document.createElement('div'); + knowlContents.classList.add(hintSolType); + knowlContents.classList.add('solution-like'); + knowlContents.innerHTML = hintsolp.firstElementChild.dataset.knowlContents; + knowlDetails.appendChild(knowlContents); + + adjustSrcHrefs(knowlContents, ww_domain) + solutionlikewrapper.appendChild(knowlDetails); + } + for (const hintSol of hintSols) { + hintSol.parentNode?.remove(); + } +} + +function cloneAttributes(target, source) { + [...source.attributes].forEach( attr => { target.setAttribute(attr.nodeName ,attr.nodeValue) }); +} + +function createFeedbackButton(id, title, content) { + const feedbackButton = document.createElement('button'); + feedbackButton.dataset.bsTitle = title; + feedbackButton.dataset.bsContent = `
    ${content || ''}
    `; + if (!content) feedbackButton.dataset.emptyContent = '1'; + const contentSpan = document.createElement('span'); + contentSpan.innerHTML = ``; + feedbackButton.appendChild(contentSpan); + feedbackButton.type = 'button'; + feedbackButton.classList.add('ww-feedback'); + + feedbackButton.id = `${id}-feedback-button`; + feedbackButton.dataset.bsToggle = 'popover'; + + return feedbackButton; +} + +function webworkSeedHash(string) { + var hash = 0, i, chr; + if (string.length === 0) return hash; + for (i = 0; i < string.length; i++) { + chr = string.charCodeAt(i); + hash = ((hash << 5) - hash) + chr; + hash |= 0; //Convert to 32bit integer + } + return Math.abs(hash); +}; diff --git a/slides-annotated/_static/pretext/js/pretext-webwork/2.19/pretext-webwork.js b/slides-annotated/_static/pretext/js/pretext-webwork/2.19/pretext-webwork.js new file mode 100755 index 0000000..99fb2f0 --- /dev/null +++ b/slides-annotated/_static/pretext/js/pretext-webwork/2.19/pretext-webwork.js @@ -0,0 +1,677 @@ +//Critical: +// TODO: Think about what should be done for an essay question. + +//Accessibility: + +//Enhancement: +// TODO: Have randomize check that new seed is actually producing new HTML. +// TODO: Don't offer randomize button unless we know a new version can be produced after trying a few seeds. + +//Styling: +// TODO: Review all styling in all scenarios (staged/not, correct/partly-correct/incorrect/blank, single/multiple) + +async function handleWW(ww_id, action) { + const ww_container = document.getElementById(ww_id); + const ww_domain = ww_container.dataset.domain; + const ww_processing = 'webwork2'; + const ww_origin = ww_container.dataset.origin; + const ww_problemSource = ww_container.dataset.problemsource; + const ww_sourceFilePath = ww_container.dataset.sourcefilepath; + const ww_course_id = ww_container.dataset.courseid; + const ww_user_id = ww_container.dataset.userid; + const ww_passwd = ww_container.dataset.coursepassword; + const localize_submit = ww_container.dataset.localizeSubmit || "Submit"; + const localize_check_responses = ww_container.dataset.localizeCheckResponses || "Check Responses"; + const localize_reveal = ww_container.dataset.localizeReveal || "Reveal"; + const localize_randomize = ww_container.dataset.localizeRandomize || "Randomize"; + const localize_reset = ww_container.dataset.localizeReset || "Reset"; + const runestone_logged_in = (typeof eBookConfig !== 'undefined' && eBookConfig.username !== ''); + // will be null on pages generated prior to late December 2022 + const activate_button = document.getElementById(ww_id + '-button') + + // Set the current seed + if (!action) { + ww_container.dataset.current_seed = ww_container.dataset.seed; + if (runestone_logged_in) { + ww_container.dataset.current_seed = webworkSeedHash(eBookConfig.username + ww_container.dataset.current_seed); + } + } + else if (action == 'randomize') ww_container.dataset.current_seed = Number(ww_container.dataset.current_seed) + 100; + + let loader = document.createElement('div'); + loader.style.position = 'absolute'; + loader.style.left = 0; + loader.style.top = 0; + loader.style.backgroundColor = 'rgba(0.2, 0.2, 0.2, 0.4)'; + loader.style.color = 'white'; + loader.style.width = '100%'; + loader.style.height = '100%'; + loader.style.display = 'flex'; + loader.style.alignItems = 'center'; + loader.style.justifyContent = 'center'; + loader.style.marginTop = '0'; + loader.tabIndex = -1; + const loaderText = document.createElement('span'); + loaderText.textContent = 'Loading'; + loaderText.style.fontSize = '2rem'; + loader.appendChild(loaderText); + ww_container.appendChild(loader); + loader.focus(); + + if (!action) { + // Determine if static version shows hints, solutions, or answers and save that information in the container dataset for later runs. + ww_container.dataset.hasHint = ww_container.getElementsByClassName('hint').length > 0; + ww_container.dataset.hasSolution = ww_container.getElementsByClassName('solution').length > 0; + ww_container.dataset.hasAnswer = ww_container.getElementsByClassName('answer').length > 0; + // Get (possibly localized) label text for hints and solutions. + ww_container.dataset.hintLabelText = ww_container.dataset.hasHint == 'true' + ? ww_container.querySelectorAll('.hint-knowl span.type, details.hint span.type')[0].textContent : 'Hint'; + ww_container.dataset.solutionLabelText = ww_container.dataset.hasSolution == 'true' + ? ww_container.querySelectorAll('.solution-knowl span.type, details.solution span.type')[0].textContent : 'Solution'; + + ww_container.tabIndex = -1; + } + + let url; + if (ww_processing == 'webwork2') { + url = new URL(ww_domain + '/webwork2/render_rpc'); + } + let formData = new FormData(); + let generatedPG = 'generated/webwork/pg/'; + if (runestone_logged_in) { + generatedPG = `/ns/books/published/${eBookConfig.basecourse}/${generatedPG}`; + } + + if (action == 'check' || action =='reveal') { + const iframe = ww_container.querySelector('.problem-iframe'); + formData = new FormData(iframe.contentDocument.getElementById(ww_id + "-form")); + formData.set("answersSubmitted", '1'); + formData.set('WWsubmit', "1"); + if (action == 'reveal' && ww_container.dataset.hasAnswer == 'true') { + formData.set('WWcorrectAnsOnly', "1"); + } + if (ww_origin == 'generated') { + const rawProblemSource = await fetch(generatedPG + ww_problemSource).then((r) => r.text()); + formData.set("rawProblemSource", rawProblemSource); + } + else if (ww_origin == 'webwork2') formData.set("sourceFilePath", ww_sourceFilePath); + } else { + formData.set("problemSeed", ww_container.dataset.current_seed); + if (ww_origin == 'generated') { + const rawProblemSource = await fetch(generatedPG + ww_problemSource).then((r) => r.text()); + formData.set("rawProblemSource", rawProblemSource); + } + else if (ww_origin == 'webwork2') formData.set("sourceFilePath", ww_sourceFilePath); + formData.set("answersSubmitted", '0'); + formData.set("displayMode", "MathJax"); + formData.set("courseID", ww_course_id); + formData.set("user", ww_user_id); + formData.set("userID", ww_user_id); + formData.set("passwd", ww_passwd); + formData.set("disableCookies", '1'); + formData.set("outputformat", "raw"); + // note ww_container.dataset.hasSolution is a string, possibly 'false' which is true + formData.set("showSolutions", ww_container.dataset.hasSolution == 'true' ? '1' : '0'); + formData.set("showHints", ww_container.dataset.hasHint == 'true' ? '1' : '0'); + formData.set("problemUUID",ww_id); + } + + // If in Runestone, check if there are previous answer submissions and get them now to use in the form. + let checkboxesString = ''; + if (runestone_logged_in && !action) { + const answersObject = (wwList[ww_id.replace(/-ww-rs$/,'')].answers ? wwList[ww_id.replace(/-ww-rs$/,'')].answers : {'answers' : [], 'mqAnswers' : []}); + const previousAnswers = answersObject.answers; + if (previousAnswers !== null) { + formData.set('WWsubmit', 1); + } + for (const answer in previousAnswers) { + if (previousAnswers[answer].constructor === Array) { + for (const k in previousAnswers[answer]) { + checkboxesString += '&'; + checkboxesString += answer; + checkboxesString += '='; + checkboxesString += previousAnswers[answer][k]; + } + } else { + formData.set(answer, previousAnswers[answer]); + } + } + } + // Need to get form data as a string, including possible repeated checkbox names + // Do not pass post data as an object, or checkbox names will overwrite one another + const formString = new URLSearchParams(formData).toString(); + + $.post(url, formString + checkboxesString, (data) => { + // Create the form that will contain the text and input fields of the interactive problem. + const form = document.createElement("form"); + form.id = ww_id + "-form"; + form.dataset.iframeHeight = 1; + + // Create a div for the problem text. + const body_div = document.createElement("div"); + body_div.id = ww_id + "-body"; + body_div.classList.add("exercise", "exercise-like"); + body_div.lang = data.lang; + body_div.dir = data.dir; + + // Dump the problem text, answer blanks, etc. + body_div.innerHTML = data.rh_result.text; + + // If showPartialCorrectAnswers = 0, alter the feedback buttons according to whether or not the score is 100%. + if ('showPartialCorrectAnswers' in data.rh_result.flags && data.rh_result.flags.showPartialCorrectAnswers == 0) { + if ('score' in data.rh_result.problem_result && data.rh_result.problem_result.score >= 1) { + body_div.querySelectorAll('button.ww-feedback-btn').forEach( + function(button) { + button.classList.remove('btn-info'); + button.classList.add('btn-success'); + button.setAttribute('aria-label', 'Correct'); + button.dataset.bsCustomClass = button.dataset.bsCustomClass + ' correct'; + button.dataset.bsTitle = button.dataset.bsTitle.replace('Answer Preview', 'Correct'); + button.firstChild.classList.add('correct') + } + ); + } else { + body_div.querySelectorAll('button.ww-feedback-btn').forEach( + function(button) { + button.setAttribute('aria-label', 'One or more answers incorrect'); + button.dataset.bsTitle = button.dataset.bsTitle.replace('Answer Preview', 'One or more answers incorrect'); + } + ); + } + } + + // Replace all hn headings with h6 headings. + for (const tag_name of ['h6', 'h5', 'h4', 'h3', 'h2', 'h1']) { + const headings = body_div.getElementsByTagName(tag_name); + for (heading of headings) { + const new_heading = document.createElement("h6"); + new_heading.innerHTML = heading.innerHTML; + cloneAttributes(new_heading, heading); + new_heading.classList.add('webwork-part'); + heading.replaceWith(new_heading); + } + } + + // Hide textarea input and hide associated buttons + var textareas = body_div.getElementsByTagName("textarea"); + for(var i = 0, max = textareas.length; i < max; i++) + { + textareas[i].style.display = "none"; + textareas[i].className = ''; + } + var textareabuttons = body_div.querySelectorAll(".latexentry-preview"); + for(var i = 0, max = textareabuttons.length; i < max; i++) + { + textareabuttons[i].remove(); + } + + adjustSrcHrefs(body_div, ww_domain); + + translateHintSol(ww_id, body_div, ww_domain, + ww_container.dataset.hasHint == 'true', ww_container.dataset.hasSolution == 'true', + ww_container.dataset.hintLabelText, ww_container.dataset.solutionLabelText) + + // insert previous answers + if (runestone_logged_in) { + const answersObject = (wwList[ww_id.replace(/-ww-rs$/,'')].answers ? wwList[ww_id.replace(/-ww-rs$/,'')].answers : {'answers' : [], 'mqAnswers' : []}); + const mqAnswers = answersObject.mqAnswers; + for (const mqAnswer in mqAnswers) { + const mqInput = body_div.querySelector('input[id=' + mqAnswer + ']'); + if (mqInput && mqInput.value == '') { + mqInput.setAttribute('value', mqAnswers[mqAnswer]); + } + } + const answers = answersObject.answers; + for (const answer in answers) { + const input = body_div.querySelector('input[id=' + answer + ']'); + if (input && input.value == '') { + input.setAttribute('value', answers[answer]); + } + } + } + + // insert our cleaned up problem text + form.appendChild(body_div); + + // Set up hidden input fields that the form uses + const wwInputs = { + problemSeed: data.inputs_ref.problemSeed, + problemUUID: data.inputs_ref.problemUUID, + psvn: data.inputs_ref.psvn, + courseName: ww_course_id, + courseID: ww_course_id, + user: ww_user_id, + passwd: ww_passwd, + displayMode: "MathJax", + session_key: data.rh_result.session_key, + outputformat: "raw", + language: data.formLanguage, + showSummary: data.showSummary, + disableCookies: '1', + // note ww_container.dataset.hasSolution is a string, possibly 'false' which is true + showSolutions: ww_container.dataset.hasSolution == 'true' ? '1' : '0', + showHints: ww_container.dataset.hasHint == 'true' ? '1' : '0', + forcePortNumber: data.forcePortNumber + }; + + if (ww_sourceFilePath) wwInputs.sourceFilePath = ww_sourceFilePath; + else if (ww_problemSource && ww_origin == 'webwork2') wwInputs.problemSource = ww_problemSource; + + for (const wwInputName of Object.keys(wwInputs)) { + const input = document.createElement('input'); + input.type = 'hidden'; + input.name = wwInputName; + input.value = wwInputs[wwInputName]; + form.appendChild(input); + } + + let buttonContainer = ww_container.querySelector('.problem-buttons.webwork'); + // Create the submission buttons if they have not yet been created. + if (!buttonContainer) { + // Hide the original div that contains the Activate. + ww_container.querySelector('.problem-buttons').classList.add('hidden-content', 'hidden'); + + // Create a new div for the webwork buttons. + buttonContainer = document.createElement('div'); + buttonContainer.classList.add('problem-buttons', 'webwork'); + if (activate_button != null) { + // Make sure the button container follows the activate button in the DOM + activate_button.after(buttonContainer); + } else { + ww_container.prepend(buttonContainer); + } + + // Check button + const check = document.createElement("button"); + check.type = "button"; + check.id = ww_id + '-check'; + check.style.marginRight = "0.25rem"; + check.classList.add('webwork-button'); + + check.textContent = runestone_logged_in ? localize_submit : localize_check_responses; + + check.addEventListener('click', () => handleWW(ww_id, "check")); + + buttonContainer.appendChild(check); + + // Show correct answers button if original PTX has answer knowl. + if (ww_container.dataset.hasAnswer == 'true') { + const correct = document.createElement("button"); + correct.classList.add("show-correct", 'webwork-button'); + correct.type = "button"; + correct.style.marginRight = "0.25rem"; + correct.textContent = localize_reveal; + correct.addEventListener('click', () => handleWW(ww_id, 'reveal')); + buttonContainer.appendChild(correct); + } + + // randomize button + const randomize = document.createElement("button") + randomize.type = "button"; + randomize.classList.add('webwork-button'); + randomize.style.marginRight = "0.25rem"; + randomize.textContent = localize_randomize; + randomize.addEventListener('click', () => handleWW(ww_id, 'randomize')); + buttonContainer.appendChild(randomize) + + // reset button + const reset = document.createElement("button") + reset.type = "button" + reset.classList.add('webwork-button'); + reset.textContent = localize_reset; + reset.addEventListener('click', () => resetWW(ww_id)); + buttonContainer.appendChild(reset) + } + + if (runestone_logged_in && action == 'check') { + // Runestone trigger + $("body").trigger('runestone_ww_check', data) + } + let courseUrlBase = ''; + if (runestone_logged_in){ + courseUrlBase = '/ns/books/published/' + eBookConfig.basecourse + '/'; + } + let iframeContents = '' + + '' + + `` + + '' + + `` + + '' + + ''; + + // Determine javascript and css dependencies + const extra_css_files = []; + const extra_js_files = []; + + if (data.extra_css_files) data.extra_css_files.unshift(...extra_css_files); + else data.extra_css_files = extra_css_files; + for (const cssFile of data.extra_css_files) { + iframeContents += ''; + } + + if (data.extra_js_files) data.extra_js_files.unshift(...extra_js_files); + else data.extra_js_files = extra_js_files; + for (const jsFile of data.extra_js_files) { + iframeContents += ''; + } + + iframeContents += + `` + + '' + + `` + + '' + + '
    ' + form.outerHTML + '
    ' + + ''; + + let iframe; + // If there is no action this is the initialization call. + if (!action) { + // Create the iframe. + iframe = document.createElement('iframe'); + iframe.style.width = '1px'; + iframe.style.minWidth = '100%'; + iframe.classList.add('problem-iframe'); + + // Hide the static problem + ww_container.querySelector('.problem-contents').classList.add('hidden-content', 'hidden'); + + if (activate_button != null) { + // Make sure the iframe follows the activate button in the DOM + activate_button.after(iframe); + } else { + ww_container.prepend(iframe); + } + + iFrameResize({ checkOrigin: false, scrolling: 'omit', heightCalculationMethod: 'taggedElement' }, iframe); + + iframe.addEventListener('load', () => { + // Set up form submission from inside the iframe. + const iframeForm = iframe.contentDocument.getElementById(ww_id + '-form'); + iframeForm.addEventListener('submit', (e) => { + handleWW(ww_id, "check"); + e.preventDefault(); + }); + + iframe.contentDocument.querySelectorAll('.collapse.in').forEach(collapse => collapse.classList.add('expanded')); + iframe.contentWindow.jQuery('.collapse').on('shown', function(e) { if (e.target != this) return; this.classList.add('expanded'); }); + iframe.contentWindow.jQuery('.collapse').on('hide', function(e) { if (e.target != this) return; this.classList.remove('expanded'); }); + + iframe.contentWindow.MathJax.startup.promise.then(() => iframe.contentWindow.MathJax.typesetPromise(['.popover', '.popover-content'])); + }); + } else { + iframe = ww_container.querySelector('.problem-iframe'); + } + + iframe.srcdoc = iframeContents; + + iframe.addEventListener('load', () => { + // Remove the loader overlay + loader.remove(); + }, { once: true }) + + // Place focus on the problem. + ww_container.focus() + }, "json"); +} + +function WWshowCorrect(ww_id, answers) { + const ww_container = document.getElementById(ww_id); + const iframe = ww_container.querySelector('.problem-iframe'); + + const body = iframe.contentDocument.getElementById(ww_id + '-body') + $("body").trigger("runestone_show_correct", { + "ww_id": ww_id, + "answers": answers + }); + + let inputs = body.querySelectorAll("input:not([type=hidden])"); + for (const input of inputs) { + const name = input.name; + const span_id = `${ww_id}-${name}-correct`; + + if (input.type == 'text' && answers[name] && !(iframe.contentDocument.getElementById(span_id))) { + const input_id = input.id; + const mq_span = iframe.contentDocument.getElementById(`mq-answer-${input_id}`); + if (mq_span) { + mq_span.style.display = 'none'; + } + label = iframe.contentDocument.getElementById(`${span_id}-label`); + if (label) { + label.parentElement.insertBefore(input, label); + label.remove(); + } + input.type = "hidden"; + // we need to convert things like < in answers to < + const correct_ans_text = iframe.contentDocument.createElement('div'); + correct_ans_text.innerHTML = answers[name].correct_ans; + input.value = correct_ans_text.textContent; + const show_span = iframe.contentDocument.createElement('span'); + show_span.id = span_id; + show_span.appendChild(answers[name].correct_ans_latex_string + ? iframe.contentDocument.createTextNode('\\(' + answers[name].correct_ans_latex_string + '\\)') + : iframe.contentDocument.createTextNode(answers[name].correct_ans)); + input.parentElement.insertBefore(show_span, input); + } + + if (input.type.toUpperCase() == 'RADIO' && answers[name]) { + const correct_ans = answers[name].correct_choice || answers[name].correct_ans; + if (input.value == correct_ans) { + input.checked = true; + } else { + input.checked = false; + } + } + + if (input.type.toUpperCase() == 'CHECKBOX' && answers[name]) { + const correct_choices = answers[name].correct_choices; + if (correct_choices.includes(input.value)) { + input.checked = true; + } else { + input.checked = false; + } + } + } + + const hiddenInputs = body.querySelectorAll("input[type=hidden]"); + for (const input of hiddenInputs) { + const name = input.name; + if (!input.nextElementSibling) continue; + const graphtoolContainer = input.nextElementSibling.nextElementSibling; + if (graphtoolContainer && answers[name] && graphtoolContainer.classList.contains('graphtool-container')) { + const correct_ans_div = iframe.contentDocument.createElement('div'); + input.parentElement.insertBefore(correct_ans_div, graphtoolContainer); + graphtoolContainer.style.display = 'none'; + input.value = answers[name].correct_ans; + iframe.contentWindow.jQuery(correct_ans_div).html(answers[name].correct_ans_latex_string); + const script = iframe.contentDocument.createElement('script'); + script.textContent = correct_ans_div.querySelector('script').textContent + .replace('\nwindow.addEventListener("DOMContentLoaded",', '(') + .replace(/;\n$/, '();'); + iframe.contentDocument.body.appendChild(script); + } + } + + let selects = body.querySelectorAll("select:not([type=hidden])"); + for (const select of selects) { + const name = select.name; + const span_id = `${ww_id}-${name}-correct`; + if (answers[name] && !iframe.contentDocument.getElementById(span_id)) { + select.style.display = "none"; + select.value = answers[name].correct_ans; + const show_span = iframe.contentDocument.createElement('span'); + show_span.id = span_id; + show_span.appendChild(answers[name].correct_ans_latex_string + ? iframe.contentDocument.createTextNode('\\(' + answers[name].correct_ans_latex_string + '\\)') + : iframe.contentDocument.createTextNode(answers[name].correct_ans)); + select.parentElement.insertBefore(show_span, select); + } + } + + // run MathJax on our new rendering + // FIXME: We only need to typeset the added elements, not the entire body. + const mathjaxTypesetScript = iframe.contentDocument.createElement('script'); + mathjaxTypesetScript.textContent = 'MathJax.startup.promise.then(() => MathJax.typesetPromise([document.body]));'; + iframe.contentDocument.body.appendChild(mathjaxTypesetScript); +} + +function resetWW(ww_id) { + const ww_container = document.getElementById(ww_id); + const activate_button = document.getElementById(ww_id + '-button'); + + ww_container.dataset.current_seed = ww_container.dataset.seed; + + iframe = ww_container.querySelector('.problem-iframe'); + iframe.remove(); + + ww_container.querySelector('.problem-contents').classList.remove('hidden-content', 'hidden'); + + ww_container.querySelector('.problem-buttons.webwork').remove(); + ww_container.querySelector('.problem-buttons').classList.remove('hidden-content', 'hidden'); + // if the newer activate button is there (but hidden) bring it back too + if (activate_button != null) {activate_button.classList.remove('hidden-content', 'hidden');}; +} + +function adjustSrcHrefs(container,ww_domain) { + container.querySelectorAll('[href]').forEach((node) => { + const href = node.attributes.href.value; + if (href !== '#' && !href.match(/^[a-z]+:\/\//i)) node.href = ww_domain + '/' + href; + }); + container.querySelectorAll('[data-knowl-url]').forEach((node) => { + const dku = node.dataset.knowlUrl; + if (dku !== '#' && !dku.match(/^[a-z]+:\/\//i)) node.dataset.knowlUrl = ww_domain + dku; + }); + container.querySelectorAll('[src]').forEach((node) => { + node.src = new URL(node.attributes.src.value, ww_domain).href; + }); +} + +function translateHintSol(ww_id, body_div, ww_domain, b_ptx_has_hint, b_ptx_has_solution, hint_label_text, solution_label_text) { + // The problem text may come with "hint"s and "solution"s + // Each one is a div.accordion > details.accordion-item > summary.accordion-button + // Styling is not the PTX way, so we change it to one div.solutions + // with (potentially multiple) details.born-hidden-knowl > summary.knowl__link + // Also if hint/sol were missing from the static version, we want these removed here + + const hintSols = body_div.querySelectorAll('.accordion.hint,.accordion.solution'); + if (hintSols.length == 0) {return}; + + for (const hintSol of hintSols) { + const parent = hintSol.parentNode; + solutionlikewrapper = document.createElement('div'); + solutionlikewrapper.classList.add('solutions'); + parent.insertBefore(solutionlikewrapper, hintSol); + + const hintSolType = hintSol.classList.contains('hint') ? 'hint' : 'solution'; + + if ((hintSolType == 'solution' && !b_ptx_has_solution) || + (hintSolType == 'hint' && !b_ptx_has_hint)) + continue; + + const knowlDetails = hintSol.getElementsByTagName('details')[0]; + knowlDetails.className = ''; + knowlDetails.classList.add(hintSolType, 'solution-like', 'born-hidden-knowl'); + solutionlikewrapper.appendChild(knowlDetails); + + const knowlSummary = knowlDetails.getElementsByTagName('summary')[0]; + knowlSummary.className = ''; + knowlSummary.classList.add('knowl__link'); + + const summaryLabel = knowlSummary.children[0]; + summaryLabel.remove(); + + const newLabelSpan = document.createElement('span'); + newLabelSpan.innerHTML = hintSolType == 'hint' ? hint_label_text : solution_label_text; + knowlSummary.appendChild(newLabelSpan); + + const newLabelPeriod = document.createElement('span'); + newLabelPeriod.innerHTML = '.'; + knowlSummary.appendChild(newLabelPeriod); + + adjustSrcHrefs(knowlDetails, ww_domain); + hintSol.remove(); + + const originalDetailsContent = knowlDetails.getElementsByTagName('div')[0]; + const newDetailsContent = originalDetailsContent.getElementsByTagName('div')[0]; + newDetailsContent.className = ''; + newDetailsContent.classList.add(hintSolType, 'solution-like', 'knowl__content'); + knowlDetails.appendChild(newDetailsContent); + originalDetailsContent.remove(); + } +} + +function cloneAttributes(target, source) { + [...source.attributes].forEach( attr => { target.setAttribute(attr.nodeName ,attr.nodeValue) }); +} + +function webworkSeedHash(string) { + var hash = 0, i, chr; + if (string.length === 0) return hash; + for (i = 0; i < string.length; i++) { + chr = string.charCodeAt(i); + hash = ((hash << 5) - hash) + chr; + hash |= 0; //Convert to 32bit integer + } + return Math.abs(hash); +}; diff --git a/slides-annotated/_static/pretext/js/pretext-webwork/2.20/pretext-webwork.js b/slides-annotated/_static/pretext/js/pretext-webwork/2.20/pretext-webwork.js new file mode 100755 index 0000000..1cd3ca6 --- /dev/null +++ b/slides-annotated/_static/pretext/js/pretext-webwork/2.20/pretext-webwork.js @@ -0,0 +1,686 @@ +//Critical: +// TODO: Think about what should be done for an essay question. + +//Accessibility: + +//Enhancement: +// TODO: Have randomize check that new seed is actually producing new HTML. +// TODO: Don't offer randomize button unless we know a new version can be produced after trying a few seeds. + +//Styling: +// TODO: Review all styling in all scenarios (staged/not, correct/partly-correct/incorrect/blank, single/multiple) + +async function handleWW(ww_id, action) { + const ww_container = document.getElementById(ww_id); + const ww_domain = ww_container.dataset.domain; + const ww_processing = 'webwork2'; + const ww_origin = ww_container.dataset.origin; + const ww_problemSource = ww_container.dataset.problemsource; + let ww_baseCourse = ww_container.dataset.documentid; + const ww_sourceFilePath = ww_container.dataset.sourcefilepath; + const ww_course_id = ww_container.dataset.courseid; + const ww_user_id = ww_container.dataset.userid; + const ww_passwd = ww_container.dataset.coursepassword; + const localize_submit = ww_container.dataset.localizeSubmit || "Submit"; + const localize_check_responses = ww_container.dataset.localizeCheckResponses || "Check Responses"; + const localize_reveal = ww_container.dataset.localizeReveal || "Reveal"; + const localize_randomize = ww_container.dataset.localizeRandomize || "Randomize"; + const localize_reset = ww_container.dataset.localizeReset || "Reset"; + const runestone_logged_in = (typeof eBookConfig !== 'undefined' && eBookConfig.username !== ''); + // will be null on pages generated prior to late December 2022 + const activate_button = document.getElementById(ww_id + '-button') + + // Set the current seed + if (!action) { + ww_container.dataset.current_seed = ww_container.dataset.seed; + if (runestone_logged_in) { + ww_container.dataset.current_seed = webworkSeedHash(eBookConfig.username + ww_container.dataset.current_seed); + } + } + else if (action == 'randomize') ww_container.dataset.current_seed = Number(ww_container.dataset.current_seed) + 100; + + let loader = document.createElement('div'); + loader.style.position = 'absolute'; + loader.style.left = 0; + loader.style.top = 0; + loader.style.backgroundColor = 'rgba(0.2, 0.2, 0.2, 0.4)'; + loader.style.color = 'white'; + loader.style.width = '100%'; + loader.style.height = '100%'; + loader.style.display = 'flex'; + loader.style.alignItems = 'center'; + loader.style.justifyContent = 'center'; + loader.style.marginTop = '0'; + loader.tabIndex = -1; + const loaderText = document.createElement('span'); + loaderText.textContent = 'Loading'; + loaderText.style.fontSize = '2rem'; + loader.appendChild(loaderText); + ww_container.appendChild(loader); + loader.focus(); + + if (!action) { + // Determine if static version shows hints, solutions, or answers and save that information in the container dataset for later runs. + ww_container.dataset.hasHint = ww_container.getElementsByClassName('hint').length > 0; + ww_container.dataset.hasSolution = ww_container.getElementsByClassName('solution').length > 0; + ww_container.dataset.hasAnswer = ww_container.getElementsByClassName('answer').length > 0; + // Get (possibly localized) label text for hints and solutions. + ww_container.dataset.hintLabelText = ww_container.dataset.hasHint == 'true' + ? ww_container.querySelectorAll('.hint-knowl span.type, details.hint span.type')[0].textContent : 'Hint'; + ww_container.dataset.solutionLabelText = ww_container.dataset.hasSolution == 'true' + ? ww_container.querySelectorAll('.solution-knowl span.type, details.solution span.type')[0].textContent : 'Solution'; + + ww_container.tabIndex = -1; + } + + let url; + if (ww_processing == 'webwork2') { + url = new URL(ww_domain + '/webwork2/render_rpc'); + } + let formData = new FormData(); + let generatedPG = 'generated/webwork/pg/'; + // For Runestone, we specify where to find the generated problems. + if (runestone_logged_in) { + if (ww_problemSource && !ww_baseCourse) { + // WeBWorK authored in source, generated after 2025/10/15-ish, should have a baseCourse specified. We fall back to parsing the baseCourse from ww_id, which *should* always start with the base course prior to the first underscore (although this is not guaranteed). If no underscore is found, we fall back to eBookConfig.basecourse. + // This is only needed for webwork problems authored in source, not OPL problems, so we only check when ww_problemSource is set. + const parts = ww_id.split('_'); + ww_baseCourse = parts.length > 1 ? parts[0] : eBookConfig.basecourse; + console.log("using the base course " + ww_baseCourse); + } + generatedPG = `/ns/books/published/${ww_baseCourse}/${generatedPG}`; + } + + if (action == 'check' || action =='reveal') { + const iframe = ww_container.querySelector('.problem-iframe'); + formData = new FormData(iframe.contentDocument.getElementById(ww_id + "-form")); + formData.set("answersSubmitted", '1'); + formData.set('WWsubmit', "1"); + if (action == 'reveal' && ww_container.dataset.hasAnswer == 'true') { + formData.set('WWcorrectAnsOnly', "1"); + } + if (ww_origin == 'generated') { + const rawProblemSource = await fetch(generatedPG + ww_problemSource).then((r) => r.text()); + formData.set("rawProblemSource", rawProblemSource); + } + else if (ww_origin == 'webwork2') formData.set("sourceFilePath", ww_sourceFilePath); + } else { + formData.set("problemSeed", ww_container.dataset.current_seed); + if (ww_origin == 'generated') { + const rawProblemSource = await fetch(generatedPG + ww_problemSource).then((r) => r.text()); + formData.set("rawProblemSource", rawProblemSource); + } + else if (ww_origin == 'webwork2') formData.set("sourceFilePath", ww_sourceFilePath); + formData.set("answersSubmitted", '0'); + formData.set("displayMode", "MathJax"); + formData.set("courseID", ww_course_id); + formData.set("user", ww_user_id); + formData.set("userID", ww_user_id); + formData.set("passwd", ww_passwd); + formData.set("disableCookies", '1'); + formData.set("outputformat", "raw"); + // note ww_container.dataset.hasSolution is a string, possibly 'false' which is true + formData.set("showSolutions", ww_container.dataset.hasSolution == 'true' ? '1' : '0'); + formData.set("showHints", ww_container.dataset.hasHint == 'true' ? '1' : '0'); + formData.set("problemUUID",ww_id); + } + + // If in Runestone, check if there are previous answer submissions and get them now to use in the form. + let checkboxesString = ''; + if (runestone_logged_in && !action) { + const answersObject = (wwList[ww_id.replace(/-ww-rs$/,'')].answers ? wwList[ww_id.replace(/-ww-rs$/,'')].answers : {'answers' : [], 'mqAnswers' : []}); + const previousAnswers = answersObject.answers; + if (previousAnswers !== null) { + formData.set('WWsubmit', 1); + } + for (const answer in previousAnswers) { + if (previousAnswers[answer].constructor === Array) { + for (const k in previousAnswers[answer]) { + checkboxesString += '&'; + checkboxesString += answer; + checkboxesString += '='; + checkboxesString += previousAnswers[answer][k]; + } + } else { + formData.set(answer, previousAnswers[answer]); + } + } + } + // Need to get form data as a string, including possible repeated checkbox names + // Do not pass post data as an object, or checkbox names will overwrite one another + const formString = new URLSearchParams(formData).toString(); + + $.post(url, formString + checkboxesString, (data) => { + // Create the form that will contain the text and input fields of the interactive problem. + const form = document.createElement("form"); + form.id = ww_id + "-form"; + form.dataset.iframeHeight = 1; + + // Create a div for the problem text. + const body_div = document.createElement("div"); + body_div.id = ww_id + "-body"; + body_div.classList.add("exercise", "exercise-like"); + body_div.lang = data.lang; + body_div.dir = data.dir; + + // Dump the problem text, answer blanks, etc. + body_div.innerHTML = data.rh_result.text; + + // If showPartialCorrectAnswers = 0, alter the feedback buttons according to whether or not the score is 100%. + if ('showPartialCorrectAnswers' in data.rh_result.flags && data.rh_result.flags.showPartialCorrectAnswers == 0) { + if ('score' in data.rh_result.problem_result && data.rh_result.problem_result.score >= 1) { + body_div.querySelectorAll('button.ww-feedback-btn').forEach( + function(button) { + button.classList.remove('btn-info'); + button.classList.add('btn-success'); + button.setAttribute('aria-label', 'Correct'); + button.dataset.bsCustomClass = button.dataset.bsCustomClass + ' correct'; + button.dataset.bsTitle = button.dataset.bsTitle.replace('Answer Preview', 'Correct'); + button.firstChild.classList.add('correct') + } + ); + } else { + body_div.querySelectorAll('button.ww-feedback-btn').forEach( + function(button) { + button.setAttribute('aria-label', 'One or more answers incorrect'); + button.dataset.bsTitle = button.dataset.bsTitle.replace('Answer Preview', 'One or more answers incorrect'); + } + ); + } + } + + // Replace all hn headings with h6 headings. + for (const tag_name of ['h6', 'h5', 'h4', 'h3', 'h2', 'h1']) { + const headings = body_div.getElementsByTagName(tag_name); + for (heading of headings) { + const new_heading = document.createElement("h6"); + new_heading.innerHTML = heading.innerHTML; + cloneAttributes(new_heading, heading); + new_heading.classList.add('webwork-part'); + heading.replaceWith(new_heading); + } + } + + // Hide textarea input and hide associated buttons + var textareas = body_div.getElementsByTagName("textarea"); + for(var i = 0, max = textareas.length; i < max; i++) + { + textareas[i].style.display = "none"; + textareas[i].className = ''; + } + var textareabuttons = body_div.querySelectorAll(".latexentry-preview"); + for(var i = 0, max = textareabuttons.length; i < max; i++) + { + textareabuttons[i].remove(); + } + + adjustSrcHrefs(body_div, ww_domain); + + translateHintSol(ww_id, body_div, ww_domain, + ww_container.dataset.hasHint == 'true', ww_container.dataset.hasSolution == 'true', + ww_container.dataset.hintLabelText, ww_container.dataset.solutionLabelText) + + // insert previous answers + if (runestone_logged_in) { + const answersObject = (wwList[ww_id.replace(/-ww-rs$/,'')].answers ? wwList[ww_id.replace(/-ww-rs$/,'')].answers : {'answers' : [], 'mqAnswers' : []}); + const mqAnswers = answersObject.mqAnswers; + for (const mqAnswer in mqAnswers) { + const mqInput = body_div.querySelector('input[id=' + mqAnswer + ']'); + if (mqInput && mqInput.value == '') { + mqInput.setAttribute('value', mqAnswers[mqAnswer]); + } + } + const answers = answersObject.answers; + for (const answer in answers) { + const input = body_div.querySelector('input[id=' + answer + ']'); + if (input && input.value == '') { + input.setAttribute('value', answers[answer]); + } + } + } + + // insert our cleaned up problem text + form.appendChild(body_div); + + // Set up hidden input fields that the form uses + const wwInputs = { + problemSeed: data.inputs_ref.problemSeed, + problemUUID: data.inputs_ref.problemUUID, + psvn: data.inputs_ref.psvn, + courseName: ww_course_id, + courseID: ww_course_id, + user: ww_user_id, + passwd: ww_passwd, + displayMode: "MathJax", + session_key: data.rh_result.session_key, + outputformat: "raw", + language: data.formLanguage, + showSummary: data.showSummary, + disableCookies: '1', + // note ww_container.dataset.hasSolution is a string, possibly 'false' which is true + showSolutions: ww_container.dataset.hasSolution == 'true' ? '1' : '0', + showHints: ww_container.dataset.hasHint == 'true' ? '1' : '0', + forcePortNumber: data.forcePortNumber + }; + + if (ww_sourceFilePath) wwInputs.sourceFilePath = ww_sourceFilePath; + else if (ww_problemSource && ww_origin == 'webwork2') wwInputs.problemSource = ww_problemSource; + + for (const wwInputName of Object.keys(wwInputs)) { + const input = document.createElement('input'); + input.type = 'hidden'; + input.name = wwInputName; + input.value = wwInputs[wwInputName]; + form.appendChild(input); + } + + let buttonContainer = ww_container.querySelector('.problem-buttons.webwork'); + // Create the submission buttons if they have not yet been created. + if (!buttonContainer) { + // Hide the original div that contains the Activate. + ww_container.querySelector('.problem-buttons').classList.add('hidden-content', 'hidden'); + + // Create a new div for the webwork buttons. + buttonContainer = document.createElement('div'); + buttonContainer.classList.add('problem-buttons', 'webwork'); + if (activate_button != null) { + // Make sure the button container follows the activate button in the DOM + activate_button.after(buttonContainer); + } else { + ww_container.prepend(buttonContainer); + } + + // Check button + const check = document.createElement("button"); + check.type = "button"; + check.id = ww_id + '-check'; + check.style.marginRight = "0.25rem"; + check.classList.add('webwork-button'); + + check.textContent = runestone_logged_in ? localize_submit : localize_check_responses; + + check.addEventListener('click', () => handleWW(ww_id, "check")); + + buttonContainer.appendChild(check); + + // Show correct answers button if original PTX has answer knowl. + if (ww_container.dataset.hasAnswer == 'true') { + const correct = document.createElement("button"); + correct.classList.add("show-correct", 'webwork-button'); + correct.type = "button"; + correct.style.marginRight = "0.25rem"; + correct.textContent = localize_reveal; + correct.addEventListener('click', () => handleWW(ww_id, 'reveal')); + buttonContainer.appendChild(correct); + } + + // randomize button + const randomize = document.createElement("button") + randomize.type = "button"; + randomize.classList.add('webwork-button'); + randomize.style.marginRight = "0.25rem"; + randomize.textContent = localize_randomize; + randomize.addEventListener('click', () => handleWW(ww_id, 'randomize')); + buttonContainer.appendChild(randomize) + + // reset button + const reset = document.createElement("button") + reset.type = "button" + reset.classList.add('webwork-button'); + reset.textContent = localize_reset; + reset.addEventListener('click', () => resetWW(ww_id)); + buttonContainer.appendChild(reset) + } + + if (runestone_logged_in && action == 'check') { + // Runestone trigger + $("body").trigger('runestone_ww_check', data) + } + let courseUrlBase = ''; + if (runestone_logged_in){ + courseUrlBase = '/ns/books/published/' + eBookConfig.basecourse + '/'; + } + let iframeContents = '' + + '' + + `` + + '' + + `` + + '' + + ''; + + // Determine javascript and css dependencies + const extra_css_files = []; + const extra_js_files = []; + + if (data.extra_css_files) data.extra_css_files.unshift(...extra_css_files); + else data.extra_css_files = extra_css_files; + for (const cssFile of data.extra_css_files) { + iframeContents += ''; + } + + if (data.extra_js_files) data.extra_js_files.unshift(...extra_js_files); + else data.extra_js_files = extra_js_files; + for (const jsFile of data.extra_js_files) { + iframeContents += ''; + } + + iframeContents += + `` + + '' + + `` + + '' + + '
    ' + form.outerHTML + '
    ' + + ''; + + let iframe; + // If there is no action this is the initialization call. + if (!action) { + // Create the iframe. + iframe = document.createElement('iframe'); + iframe.style.width = '1px'; + iframe.style.minWidth = '100%'; + iframe.classList.add('problem-iframe'); + + // Hide the static problem + ww_container.querySelector('.problem-contents').classList.add('hidden-content', 'hidden'); + + if (activate_button != null) { + // Make sure the iframe follows the activate button in the DOM + activate_button.after(iframe); + } else { + ww_container.prepend(iframe); + } + + iFrameResize({ checkOrigin: false, scrolling: 'omit', heightCalculationMethod: 'taggedElement' }, iframe); + + iframe.addEventListener('load', () => { + // Set up form submission from inside the iframe. + const iframeForm = iframe.contentDocument.getElementById(ww_id + '-form'); + iframeForm.addEventListener('submit', (e) => { + handleWW(ww_id, "check"); + e.preventDefault(); + }); + + iframe.contentDocument.querySelectorAll('.collapse.in').forEach(collapse => collapse.classList.add('expanded')); + iframe.contentWindow.jQuery('.collapse').on('shown', function(e) { if (e.target != this) return; this.classList.add('expanded'); }); + iframe.contentWindow.jQuery('.collapse').on('hide', function(e) { if (e.target != this) return; this.classList.remove('expanded'); }); + + iframe.contentWindow.MathJax.startup.promise.then(() => iframe.contentWindow.MathJax.typesetPromise(['.popover', '.popover-content'])); + }); + } else { + iframe = ww_container.querySelector('.problem-iframe'); + } + + iframe.srcdoc = iframeContents; + + iframe.addEventListener('load', () => { + // Remove the loader overlay + loader.remove(); + }, { once: true }) + + // Place focus on the problem. + ww_container.focus() + }, "json"); +} + +function WWshowCorrect(ww_id, answers) { + const ww_container = document.getElementById(ww_id); + const iframe = ww_container.querySelector('.problem-iframe'); + + const body = iframe.contentDocument.getElementById(ww_id + '-body') + $("body").trigger("runestone_show_correct", { + "ww_id": ww_id, + "answers": answers + }); + + let inputs = body.querySelectorAll("input:not([type=hidden])"); + for (const input of inputs) { + const name = input.name; + const span_id = `${ww_id}-${name}-correct`; + + if (input.type == 'text' && answers[name] && !(iframe.contentDocument.getElementById(span_id))) { + const input_id = input.id; + const mq_span = iframe.contentDocument.getElementById(`mq-answer-${input_id}`); + if (mq_span) { + mq_span.style.display = 'none'; + } + label = iframe.contentDocument.getElementById(`${span_id}-label`); + if (label) { + label.parentElement.insertBefore(input, label); + label.remove(); + } + input.type = "hidden"; + // we need to convert things like < in answers to < + const correct_ans_text = iframe.contentDocument.createElement('div'); + correct_ans_text.innerHTML = answers[name].correct_ans; + input.value = correct_ans_text.textContent; + const show_span = iframe.contentDocument.createElement('span'); + show_span.id = span_id; + show_span.appendChild(answers[name].correct_ans_latex_string + ? iframe.contentDocument.createTextNode('\\(' + answers[name].correct_ans_latex_string + '\\)') + : iframe.contentDocument.createTextNode(answers[name].correct_ans)); + input.parentElement.insertBefore(show_span, input); + } + + if (input.type.toUpperCase() == 'RADIO' && answers[name]) { + const correct_ans = answers[name].correct_choice || answers[name].correct_ans; + if (input.value == correct_ans) { + input.checked = true; + } else { + input.checked = false; + } + } + + if (input.type.toUpperCase() == 'CHECKBOX' && answers[name]) { + const correct_choices = answers[name].correct_choices; + if (correct_choices.includes(input.value)) { + input.checked = true; + } else { + input.checked = false; + } + } + } + + const hiddenInputs = body.querySelectorAll("input[type=hidden]"); + for (const input of hiddenInputs) { + const name = input.name; + if (!input.nextElementSibling) continue; + const graphtoolContainer = input.nextElementSibling.nextElementSibling; + if (graphtoolContainer && answers[name] && graphtoolContainer.classList.contains('graphtool-container')) { + const correct_ans_div = iframe.contentDocument.createElement('div'); + input.parentElement.insertBefore(correct_ans_div, graphtoolContainer); + graphtoolContainer.style.display = 'none'; + input.value = answers[name].correct_ans; + iframe.contentWindow.jQuery(correct_ans_div).html(answers[name].correct_ans_latex_string); + const script = iframe.contentDocument.createElement('script'); + script.textContent = correct_ans_div.querySelector('script').textContent + .replace('\nwindow.addEventListener("DOMContentLoaded",', '(') + .replace(/;\n$/, '();'); + iframe.contentDocument.body.appendChild(script); + } + } + + let selects = body.querySelectorAll("select:not([type=hidden])"); + for (const select of selects) { + const name = select.name; + const span_id = `${ww_id}-${name}-correct`; + if (answers[name] && !iframe.contentDocument.getElementById(span_id)) { + select.style.display = "none"; + select.value = answers[name].correct_ans; + const show_span = iframe.contentDocument.createElement('span'); + show_span.id = span_id; + show_span.appendChild(answers[name].correct_ans_latex_string + ? iframe.contentDocument.createTextNode('\\(' + answers[name].correct_ans_latex_string + '\\)') + : iframe.contentDocument.createTextNode(answers[name].correct_ans)); + select.parentElement.insertBefore(show_span, select); + } + } + + // run MathJax on our new rendering + // FIXME: We only need to typeset the added elements, not the entire body. + const mathjaxTypesetScript = iframe.contentDocument.createElement('script'); + mathjaxTypesetScript.textContent = 'MathJax.startup.promise.then(() => MathJax.typesetPromise([document.body]));'; + iframe.contentDocument.body.appendChild(mathjaxTypesetScript); +} + +function resetWW(ww_id) { + const ww_container = document.getElementById(ww_id); + const activate_button = document.getElementById(ww_id + '-button'); + + ww_container.dataset.current_seed = ww_container.dataset.seed; + + iframe = ww_container.querySelector('.problem-iframe'); + iframe.remove(); + + ww_container.querySelector('.problem-contents').classList.remove('hidden-content', 'hidden'); + + ww_container.querySelector('.problem-buttons.webwork').remove(); + ww_container.querySelector('.problem-buttons').classList.remove('hidden-content', 'hidden'); + // if the newer activate button is there (but hidden) bring it back too + if (activate_button != null) {activate_button.classList.remove('hidden-content', 'hidden');}; +} + +function adjustSrcHrefs(container,ww_domain) { + container.querySelectorAll('[href]').forEach((node) => { + const href = node.attributes.href.value; + if (href !== '#' && !href.match(/^[a-z]+:\/\//i)) node.href = ww_domain + '/' + href; + }); + container.querySelectorAll('[data-knowl-url]').forEach((node) => { + const dku = node.dataset.knowlUrl; + if (dku !== '#' && !dku.match(/^[a-z]+:\/\//i)) node.dataset.knowlUrl = ww_domain + dku; + }); + container.querySelectorAll('[src]').forEach((node) => { + node.src = new URL(node.attributes.src.value, ww_domain).href; + }); +} + +function translateHintSol(ww_id, body_div, ww_domain, b_ptx_has_hint, b_ptx_has_solution, hint_label_text, solution_label_text) { + // The problem text may come with "hint"s and "solution"s + // Each one is a div.accordion > details.accordion-item > summary.accordion-button + // Styling is not the PTX way, so we change it to one div.solutions + // with (potentially multiple) details.born-hidden-knowl > summary.knowl__link + // Also if hint/sol were missing from the static version, we want these removed here + + const hintSols = body_div.querySelectorAll('.accordion.hint,.accordion.solution'); + if (hintSols.length == 0) {return}; + + for (const hintSol of hintSols) { + const parent = hintSol.parentNode; + solutionlikewrapper = document.createElement('div'); + solutionlikewrapper.classList.add('solutions'); + parent.insertBefore(solutionlikewrapper, hintSol); + + const hintSolType = hintSol.classList.contains('hint') ? 'hint' : 'solution'; + + if ((hintSolType == 'solution' && !b_ptx_has_solution) || + (hintSolType == 'hint' && !b_ptx_has_hint)) + continue; + + const knowlDetails = hintSol.getElementsByTagName('details')[0]; + knowlDetails.className = ''; + knowlDetails.classList.add(hintSolType, 'solution-like', 'born-hidden-knowl'); + solutionlikewrapper.appendChild(knowlDetails); + + const knowlSummary = knowlDetails.getElementsByTagName('summary')[0]; + knowlSummary.className = ''; + knowlSummary.classList.add('knowl__link'); + + const summaryLabel = knowlSummary.children[0]; + summaryLabel.remove(); + + const newLabelSpan = document.createElement('span'); + newLabelSpan.innerHTML = hintSolType == 'hint' ? hint_label_text : solution_label_text; + knowlSummary.appendChild(newLabelSpan); + + const newLabelPeriod = document.createElement('span'); + newLabelPeriod.innerHTML = '.'; + knowlSummary.appendChild(newLabelPeriod); + + adjustSrcHrefs(knowlDetails, ww_domain); + hintSol.remove(); + + const originalDetailsContent = knowlDetails.getElementsByTagName('div')[0]; + const newDetailsContent = originalDetailsContent.getElementsByTagName('div')[0]; + newDetailsContent.className = ''; + newDetailsContent.classList.add(hintSolType, 'solution-like', 'knowl__content'); + knowlDetails.appendChild(newDetailsContent); + originalDetailsContent.remove(); + } +} + +function cloneAttributes(target, source) { + [...source.attributes].forEach( attr => { target.setAttribute(attr.nodeName ,attr.nodeValue) }); +} + +function webworkSeedHash(string) { + var hash = 0, i, chr; + if (string.length === 0) return hash; + for (i = 0; i < string.length; i++) { + chr = string.charCodeAt(i); + hash = ((hash << 5) - hash) + chr; + hash |= 0; //Convert to 32bit integer + } + return Math.abs(hash); +}; diff --git a/slides-annotated/_static/pretext/js/pretext.js b/slides-annotated/_static/pretext/js/pretext.js new file mode 100755 index 0000000..bcddf7e --- /dev/null +++ b/slides-annotated/_static/pretext/js/pretext.js @@ -0,0 +1,257 @@ +/******************************************************************************* + * pretext.js + ******************************************************************************* + * The main front-end controller for PreTeXt documents. + * + * Homepage: pretextbook.org + * Repository: https://github.com/PreTeXtBook/JS_core + * + * Authors: David Farmer, Rob Beezer + * + ******************************************************************************* + */ + +// from https://stackoverflow.com/questions/34422189/get-item-offset-from-the-top-of-page +function getOffsetTop(e) { + // recursively walk up the DOM via offsetParent, accumulating offsetTop as we go + if (!e) return 0; + return getOffsetTop(e.offsetParent) + e.offsetTop; +}; + +function scrollTocToActive() { + //Try to figure out current TocItem from URL + let fileNameWHash = window.location.href.split("/").pop(); + let fileName = fileNameWHash.split("#")[0]; + + //Find just the filename in ToC + let tocEntry = document.querySelector('#ptx-toc a[href="' + fileName + '"]'); + if (!tocEntry) { + return; //complete failure, get out + } + + let tocEntryTop = 0; + //See if we can also match fileName#hash (assuming there is a fragment) + if (fileNameWHash.includes('#')) { + let tocEntryWHash = document.querySelector( + '#ptx-toc a[href="' + fileNameWHash + '"]' + ); + if (tocEntryWHash) { + //Matched something below a subsection - activate the list item that contains it + tocEntry.closest("li").querySelectorAll("li").forEach(li => { + li.classList.remove("active"); + }); + tocEntryWHash.closest("li").classList.add("active"); + tocEntryTop = getOffsetTop(tocEntryWHash); + } + } + if (!tocEntryTop) { + tocEntryTop = getOffsetTop(tocEntry); + } + + //Now activate ToC item for fileName and scroll to it + // Don't use scrollIntoView because it changes users tab position in Chrome + // and messes up keyboard navigation + tocEntry.closest("li").classList.add("active"); + // Scroll only if the tocEntry is below the bottom half of the window, + // scrolling to that position. + let toc = document.querySelector("#ptx-toc"); + let tocTop = getOffsetTop(toc); + toc.scrollTop = tocEntryTop - tocTop - 0.4 * self.innerHeight; +} + +function toggletoc() { + thesidebar = document.getElementById("ptx-sidebar"); + if (thesidebar.classList.contains("hidden") || thesidebar.classList.contains("visible")) { + thesidebar.classList.toggle("hidden"); + thesidebar.classList.toggle("visible"); + } else if (thesidebar.offsetParent === null) { /* not currently visible */ + thesidebar.classList.toggle("visible"); + } else { + thesidebar.classList.toggle("hidden"); + } + scrollTocToActive(); +} + +function samePageLink(a) { + if (!(a instanceof HTMLAnchorElement)) return false; + + try { + const linkUrl = new URL(a.href, document.baseURI); + const currentUrl = new URL(window.location.href); + + const sameDocument = + linkUrl.origin === currentUrl.origin && + linkUrl.pathname === currentUrl.pathname && + linkUrl.search === currentUrl.search; + + return sameDocument && !!linkUrl.hash; + } catch (e) { + // Invalid URL + return false; + } +} + + +window.addEventListener("DOMContentLoaded",function(event) { + thetocbutton = document.getElementsByClassName("toc-toggle")[0]; + thetocbutton.addEventListener("click", (e) => { + toggletoc(); + e.stopPropagation(); // keep global click handler from immediately toggling it back + }); + + // For themes that want it, install click handlers to auto close the toc + // when the reader clicks anywhere outside it or selects a subsection. + // (Selecting other sections or chapters navigates away from the page so + // effectively closes the TOC.) + if (getComputedStyle(document.documentElement).getPropertyValue('--auto-collapse-toc') == "yes") { + + const sidebar = document.getElementById("ptx-sidebar"); + + // Handle all clicks outside the sidebar + window.addEventListener("click", function(event) { + if (sidebar.classList.contains("visible")) { + if (!event.composedPath().includes(sidebar)) { + toggletoc(); + } + } + }); + + // Handle clicks inside the sidebar but on link within a subsection. + sidebar.addEventListener("click", function (event) { + if (samePageLink(event.target.closest('a'))) { + toggletoc(); + } + }); + + // Handle persistent sidebar if the page is restored from cache on back/forward buttons. + window.addEventListener('pageshow', (e) => { + if (e.persisted) { + sidebar.classList.remove('visible'); + sidebar.classList.add('hidden'); + } + }); + } +}); + +/* jump to next page if reader tries to scroll past the bottom */ +// var hitbottom = false; +// window.onscroll = function(ev) { +// if ((window.innerHeight + window.scrollY) >= document.body.scrollHeight) { +// // you're at the bottom of the page +// console.log("Bottom of page"); +// if (hitbottom) { +// console.log("hit bottom again"); +// thenextbutton = document.getElementsByClassName("next-button")[0]; +// thenextbutton.click(); +// } else { +// hitbottom = true; +// /* only jump to next page if hard scroll in quick succession */ +// window.scrollBy(0, -20); +// setTimeout(function (){ hitbottom = false }, 1000); +// } +// } +// }; + + +//----------------------------------------------------------------------------- +// Dynamic TOC logic +//----------------------------------------------------------------------------- + +//item is assumed to be expander in toc-item +function toggleTOCItem(expander) { + let listItem = expander.closest(".toc-item"); + listItem.classList.toggle("expanded"); + let expanded = listItem.classList.contains("expanded"); + + let itemType = getTOCItemType(listItem); + if(expanded) { + expander.title = "Close" + (itemType !== "" ? " " + itemType : ""); + } else { + expander.title = "Expand" + (itemType !== "" ? " " + itemType : ""); + } + + //should be one of each... for/of for safety and built in null avoidance + for (const childUL of listItem.querySelectorAll(":scope > ul.toc-item-list")) { + for (const childItem of childUL.querySelectorAll(":scope > li.toc-item")) { + if(expanded) { + childItem.classList.add("visible"); + childItem.classList.remove("hidden"); + } else { + childItem.classList.remove("visible"); + childItem.classList.add("hidden"); + } + } + } +} + +//finds item type from classes or empty string on failure +function getTOCItemType(item) { + //Type should be class that looks like toc-X where X is not item. Find it and return X + for(let className of item.classList) { + if(className !== "toc-item" && className.length > 3 && className.slice(0,4) === "toc-") + return className.slice(4); + } + return ""; +} + +//finds depth of toc-item as defined by number .toc-item-lists it is in +function getTOCItemDepth(item) { + let depth = 0; + let curParent = item.closest(".toc-item-list"); + while(curParent !== null) { + depth++; + curParent = curParent.parentElement.closest(".toc-item-list"); + } + return depth; +} + +window.addEventListener("DOMContentLoaded", function(event) { + if(document.querySelector(".ptx-toc.focused") === null) + return; //only in focused mode + + let maxDepth = 1000; //how deep TOC goes + //check toc for depth class and get value from there + for(let className of document.querySelector(".ptx-toc").classList) + if(className.length > 5 && className.slice(0,5) === "depth") + maxDepth = Number(className.slice(5)); + + let preexpandedLevels = 1; //how many levels to preexpand + let tocDataSet = document.querySelector(".ptx-toc").dataset; + if(typeof tocDataSet.preexpandedLevels !== 'undefined') + preexpandedLevels = Number(tocDataSet.preexpandedLevels); + + let tocItems = document.querySelectorAll(".ptx-toc ul.structural > .toc-item"); + for (const tocItem of tocItems) { + let hasChildren = tocItem.querySelector('ul.structural') !== null; + let depth = getTOCItemDepth(tocItem); + + if(hasChildren && depth < maxDepth) { + let expander = document.createElement("button"); + expander.classList.add('toc-expander'); + expander.classList.add('toc-chevron-surround'); + expander.title = 'toc-expander'; + // content of span is set by CSS :before rule. + expander.innerHTML = ''; + tocItem.querySelector(".toc-title-box").append(expander); + expander.addEventListener('click', () => { + toggleTOCItem(expander); + }); + + let isActive = tocItem.classList.contains("contains-active") || tocItem.classList.contains("active"); + let preExpanded = isActive || depth < preexpandedLevels; + let itemType = getTOCItemType(tocItem); + if(preExpanded) { + toggleTOCItem(expander); + } else { + expander.title = "Expand" + (itemType !== "" ? " " + itemType : ""); + } + } + } +}); + +// This needs to be after the TOC's geometry is settled +window.addEventListener("DOMContentLoaded",function(event) { + scrollTocToActive(); +}); + +window.onhashchange = scrollTocToActive; diff --git a/slides-annotated/_static/pretext/js/pretext_add_on.js b/slides-annotated/_static/pretext/js/pretext_add_on.js new file mode 100755 index 0000000..ae2225f --- /dev/null +++ b/slides-annotated/_static/pretext/js/pretext_add_on.js @@ -0,0 +1,1264 @@ +/******************************************************************************* + * pretext_add_on.js + ******************************************************************************* + * Javascript for supplementary material in PreTeXt documents. + * + * Homepage: pretextbook.org + * Repository: https://github.com/PreTeXtBook/JS_core + * + * Authors: David Farmer, Rob Beezer, Alex Jordan + * + ******************************************************************************* + */ + +/* +console.log("thisbrowser.userAgent", window.navigator.userAgent); +*/ + +var minivers = "0"; +if (typeof miniversion !== 'undefined') { + console.log("typeof miniversion", typeof miniversion, "dddd", typeof miniversion == 'undefined'); + minivers = miniversion.toString(); +} +console.log(" minivers", minivers); + +/* scrollbar width from https://stackoverflow.com/questions/13382516/getting-scroll-bar-width-using-javascript */ +function getScrollbarWidth() { + var outer = document.createElement("div"); + outer.style.visibility = "hidden"; + outer.style.width = "100px"; + outer.style.msOverflowStyle = "scrollbar"; // needed for WinJS apps + + document.body.appendChild(outer); + + var widthNoScroll = outer.offsetWidth; + // force scrollbars + outer.style.overflow = "scroll"; + + // add innerdiv + var inner = document.createElement("div"); + inner.style.width = "100%"; + outer.appendChild(inner); + + var widthWithScroll = inner.offsetWidth; + + // remove divs + outer.parentNode.removeChild(outer); + + return widthNoScroll - widthWithScroll; +} + +/* + copy permalink address to clipboard + requires browser support, otherwise does nothing +*/ +async function copyPermalink(linkNode) { + // structure borrowed from https://flaviocopes.com/clipboard-api/ + if (!navigator.clipboard) { + // Clipboard API not available + console.log("Error: Clipboard API not available"); + return + } + console.log("copying permalink for", linkNode); + var elem = linkNode.parentElement + if (!linkNode) { + console.log("Error: Something went wrong finding permalink URL") + return + } + const this_permalink_url = linkNode.href; + const this_permalink_description = elem.getAttribute('data-description'); + var link = "" + this_permalink_description + ""; + var msg_link = "" + this_permalink_description + ""; + var text_fallback = this_permalink_description + " \r\n" + this_permalink_url; + var copy_success = true; + try { + // NOTE: this method will only work in Firefox if the user has + // dom.events.asyncClipboard.clipboardItem + // set to true in their about:config. + // Annoyingly, this setting is turned off by default. + // If that setting is off, this try block will fail and we'll use the + // fallback method lower down instead. + await navigator.clipboard.write([ + new ClipboardItem({ + 'text/html': new Blob([link], { type: 'text/html' }), + 'text/plain': new Blob([text_fallback], { type: 'text/plain' }), + }) + ]); + } catch (err) { + console.log('Permalink-to-clipboard using ClipboardItem failed, falling back to clipboard.writeText', err); + copy_success = false; + } + if (! copy_success) { + try { + await navigator.clipboard.writeText(text_fallback); + } catch (err) { + console.log('Permalink-to-clipboard using clipboard.writeText failed', err); + console.error('Failed to copy link to clipboard!'); + return + } + } + + console.log(`copied '${this_permalink_url}' to clipboard`); + // temporary element to alert user that link was copied + let copied_msg = document.createElement('p'); + copied_msg.setAttribute('role', 'alert'); + copied_msg.className = "permalink-alert"; + copied_msg.innerHTML = "Link to " + msg_link + " copied to clipboard"; + elem.parentElement.insertBefore(copied_msg, elem); + // show confirmation for a couple seconds + await new Promise((resolve, reject) => setTimeout(resolve, 1500)); + copied_msg.remove(); + +} + +// Add event listener to add onClick handler for permalinks +window.addEventListener("DOMContentLoaded", function() { + const permalinks = document.querySelectorAll('.autopermalink > a'); + permalinks.forEach(link => { + link.addEventListener('click', function(event) { + event.preventDefault(); // Prevent default anchor behavior + copyPermalink(link); + }); + }); +}); + + +window.addEventListener("load",function(event) { + + + /* click an image to magnify */ + $('body').on('click','.image-box > img:not(.draw_on_me):not(.mag_popup), .sbspanel > img:not(.draw_on_me):not(.mag_popup), figure > img:not(.draw_on_me):not(.mag_popup), figure > div > img:not(.draw_on_me):not(.mag_popup)', function(){ + var img_big = document.createElement('div'); + img_big.setAttribute('style', 'background:#fff;'); + img_big.setAttribute('class', 'mag_popup_container'); + img_big.innerHTML = ''; + // place_to_put_big_img = $(this).parents(".sbsrow, figure, li").last(); + place_to_put_big_img = $(this).parents(".image-box, .sbsrow, figure, li, .cols2 article:nth-of-type(2n)").last(); + // for .cols2, the even ones have to go inside the previous odd one + if (place_to_put_big_img.prop("tagName") == "ARTICLE") { + place_to_put_big_img = place_to_put_big_img.prev().children().first(); + } + $(img_big).insertBefore(place_to_put_big_img); + }); + + /* click the big image to make it go away */ + $('body').on('click','img.mag_popup', function(){ + this.parentNode.remove(); + }); + + /* add ids to p that have none */ + p_no_id = document.querySelectorAll('.main p:not([id])'); + for (var n=p_no_id.length - 1; n >= 0; --n) { + e = p_no_id[n]; + if (e.hasAttribute('id')) { +/* + console.log(e, "was id'd in a previous round"); +*/ + continue + } +/* +console.log("this is e", e); +*/ + if (e.classList.contains('watermark')) { + console.log(e, "skipping the watermark"); + continue + } +/* + console.log("\n XXXXXXXXX p with no id", e); +*/ + prev_p = $(e).prevAll("p"); + console.log("prev_p", prev_p, "xx"); + if(prev_p.length == 0) { + console.log(" PPP problem: prev_p has no length:", prev_p); + continue + } + console.log("which has id", prev_p[0].id); + var parts_found = 1; + var parts_to_id = [e]; + for (var i=0; i < prev_p.length; ++i) { + this_previous = prev_p[i]; + console.log("i", i, "this_previous", this_previous, "id", this_previous.id, "???", this_previous.hasAttribute('id')) + if (!this_previous.hasAttribute('id')) { + parts_to_id.unshift(this_previous) + } + else { + base_id = this_previous.id; + console.log("base_id", base_id); + console.log("ready to add id to", parts_to_id); + for (var j=0; j < parts_to_id.length; ++j) { + ++parts_found; + var next_id = base_id + "-part" + parts_found.toString(); + console.log("parts_found", parts_found, "next_id", next_id); + parts_to_id[j].setAttribute("id", next_id); + } + break // because we found the id that is the base for the missing ids + } + } + } + + console.log("adding video popouts"); + all_iframes = document.querySelectorAll('body iframeXXXX'); + // for now, we just want the iframes that hace youtube in the src + for (var i = 0; i < all_iframes.length; i++) { + this_item = all_iframes[i]; + this_item_src = this_item.src; + // console.log("this_item_src", this_item_src); + if(this_item_src.includes("youtube")) { + this_item_id = this_item.id; + this_item_width = this_item.width; + this_item_height = this_item.height; + if(this_item_height < 150) { continue } + console.log("found a youtube video on", this_item_id); + var empty_div = document.createElement('div'); + var this_videomag_container = document.createElement('div'); + parent_tag = this_item.parentElement.tagName; + if(parent_tag == "FIGURE") { + this_videomag_container.setAttribute("class", "videobig"); + } else { + this_videomag_container.setAttribute("class", "videobig nofigure"); + } +/* + this_videomag_container.setAttribute('class', 'videobig'); +*/ + this_videomag_container.setAttribute('video-id', this_item_id); + this_videomag_container.setAttribute('data-width', this_item_width); + this_videomag_container.setAttribute('data-height', this_item_height); + this_videomag_container.innerHTML = 'fit width'; + +/* replace this with a surrounding div, for placement, containing a inline-block so the background looks right */ + this_item.insertAdjacentElement("beforebegin", empty_div); // because of hard-coded permalinks being inline-block */ + this_item.insertAdjacentElement("beforebegin", this_videomag_container); + this_item.insertAdjacentElement("beforebegin", empty_div); // because of hard-coded permalinks being inline-block */ + } + } + +/* replace this with a single class fo rthe button, with supplementary classes that say to shrink or grow */ + $(".videobig").click(function(){ + parent_video_id = this.getAttribute("video-id"); + console.log("clicked videobig for", parent_video_id); + this_video = document.getElementById(parent_video_id); + console.log("make big: ", this_video); + original_width = this.getAttribute("data-width"); + original_height = this.getAttribute("data-height"); + + browser_width = $(window).width(); + width_ratio = browser_width/original_width; + console.log("the browser is wider by a factor of",width_ratio); + this_video.setAttribute("width", width_ratio*original_width); + this_video.setAttribute("height", width_ratio*original_height); + this_video.setAttribute("style", "position:relative; left:-260px; z-index:1000"); + + this.setAttribute("class", "videosmall"); + this.innerHTML = "make small"; + $(".videosmall").click(function(){ + console.log("clicked videosmall"); + parent_video_id = this.getAttribute("video-id"); + this_video = document.getElementById(parent_video_id); + original_width = this.getAttribute("data-width"); + original_height = this.getAttribute("data-height"); + + this_video.removeAttribute("style"); + this_video.setAttribute("width", original_width); + this_video.setAttribute("height", original_height); + this.setAttribute("class", "videobig"); + this.innerHTML = "fit width"; + }); + }); + +}, +false); + +/* for the random WW problems */ + +function updateURLParameter(url, param, paramVal){ + var newAdditionalURL = ""; + var tempArray = url.split("?"); + var baseURL = tempArray[0]; + var additionalURL = tempArray[1]; + var temp = ""; + if (additionalURL) { + tempArray = additionalURL.split("&"); + for (var i=0; i div.onclick > svg > path').attr('fill', '#0000aa'); + $('path').attr('fill', '#0000aa') +// }, 5000) +}); +*/ + +window.addEventListener("load",function(event) { + document.onkeyup = function(event) + { + var e = (!event) ? window.event : event; + switch(e.keyCode) + { + case 13: //CR + just_hit_escape = false; + if ($(document.activeElement).hasClass("workspace")) { + process_workspace() + } + case 27: //esc + // var parent_sage_cell = $(this).closest(".sagecell_editor"); + var parent_sage_cell = document.activeElement.closest(".sagecell_editor"); + if (parent_sage_cell && !just_hit_escape) { + console.log("staying in the sage cell", parent_sage_cell, document.activeElement) + just_hit_escape = true; + setTimeout(function(){ just_hit_escape = false }, 1000); + // console.log("parent_sage_cell", parent_sage_cell); + // if ($(parent_sage_cell).hasClass('sagecell_editor')) { + // console.log("I am trapped in a sage cell", $(document.activeElement).closest(".sagecell_editor")); + // console.log($(document.activeElement)); + // var this_sage_cell = $(document.activeElement).closest(".sagecell_editor"); + // this_sage_cell.next().focus; + // } + // else + } else + if(knowl_focus_stack.length > 0 ) { + most_recently_opened = knowl_focus_stack.pop(); + knowl_focus_stack_uid.pop(); + most_recently_opened.focus(); + console.log("moved back one knowl"); + } else { + console.log("no open knowls being tracked"); + break; + } + break; + } +}; +}, +false); + +// a hack for hosted tracking + +window.addEventListener("load",function(event) { + if($('body').attr('id') == "judson-AATA") { + console.log(" found AATA"); + console.log(" looking for id"); + if (typeof eBookConfig !== 'undefined') { + if(eBookConfig['username']) { + aa_id = "run" + eBookConfig['username']; + ut_id = eBookConfig['username']; + console.log(" done looking for id", ut_id); +var newscript = document.createElement('script'); + newscript.type = 'text/javascript'; + newscript.async = true; + newscript.src = 'https://pretextbook.org/js/' + '0.13' + '/' + 'trails' + '.js'; + var allscripts = document.getElementsByTagName('script'); + var s = allscripts[allscripts.length - 1]; + console.log('s',s); + console.log("adding a script", newscript); + s.parentNode.insertBefore(newscript, s.nextSibling); + trail = true; + console.log(" done adding script"); + } else { + console.log(" did not find username"); + } + } else { + console.log(" did not find eBookConfig") + } + } +}); + +function loadResource(type, file) { + /* type should be js or css */ + if (typeof js_version === 'undefined') { js_version = '0.2' } + if (typeof css_version === 'undefined') { css_version = '0.6' } + var newresource, allresources, s; + var linktype = "script"; + if (type == "css") { linktype = "link" } + newresource = document.createElement(linktype); + + if (type == "css") { + newresource.type = 'text/css'; + newresource.rel = 'stylesheet'; + newresource.href = 'https://pretextbook.org/css/' + css_version + '/' + file + '.css'; + newresource.href += '?minivers=' + minivers; + } else if (type == "js") { + newresource.type = 'text/javascript'; +// newscript.async = true; + newresource.src = 'https://pretextbook.org/js/' + js_version + '/' + file + '.js'; + newresource.src += '?minivers=' + minivers; + } else { + console.log("unknown resource type", type, "for", file); + return + } + + allresources = document.getElementsByTagName(linktype); + s = allresources[allresources.length - 1]; + console.log('s',s); + console.log("adding a resource", newresource); + s.parentNode.insertBefore(newresource, s.nextSibling); +} + + +window.addEventListener("load",function(event) { + if(false && $('body').attr('id') == "pretext-SA") { + console.log(" found DMOI"); + if (typeof uname === "undefined") { uname = "" } + console.log("aaaa", uname, " uname"); + if(uname == "editor") { + loadResource('js', 'edit'); + } else { + console.log("not enabling editing") + } + /* } else if ($('body').attr('id') == "pugetsound-SW") { */ + } else if (false && window.location.href.includes("soundwriting.pugetsound")) { +/* a bunch of temporary exploration for a Sound Writing survey */ + console.log("please take our survey"); + console.log(window.location.href); + console.log(window.location.href.includes("soundwriting.pugetsound")); + + loadResource("js", "login"); + loadResource("css", "features"); + setTimeout( loadResource("js", "survey"), 1000); /* I know: sloppy */ + + // } else if ((typeof online_editable !== 'undefined') && online_editable) { + } else if (false && $('body').attr('id') == "pretext-SA") { + loadResource('css', 'features'); + loadResource('js', 'login') + loadResource('js', 'edit'); + } else { + var this_source_txt; + var source_url = window.location.href; + source_url = source_url.replace(/(#|\?).*/, ""); + source_url = source_url.replace(/html$/, "ptx"); + if (typeof sourceeditable !== 'undefined') { + fetch(source_url).then( + function(u){ return u.text();} + ).then( + function(text){ + this_source_txt = text; + if (this_source_txt.includes("404 Not")) { + console.log("Editing not enabled: source unavailable") + } else { + loadResource('css', 'features'); + loadResource('css', 'edit'); + loadResource('js', 'login') + loadResource('js', 'edit'); + } + } + ); + } else { + console.log("Source file unavailable: editing not possible") + } + } + +}); + +// this is to open every knowl on a page +// (this code is not actually used anywhere) +window.addEventListener("load",function(event) { + if($('body').hasClass("braillesample")) { + var knowl_id_counterX = 0; + console.log(" found braillesample"); + var all_knowls = $('[data-knowl]'); + console.log("found", all_knowls.length, "knowls"); + console.log("which are", all_knowls); + for (var j=1; j < all_knowls.length; ++j) { + console.log(j, "un-knowling", all_knowls[j]); + console.log("attr", $(all_knowls[j]).attr("data-knowl")); + $knowl = $(all_knowls[j]); + if(!$knowl.attr("data-knowl-uid")) { + $knowl.attr("data-knowl-uid", knowl_id_counterX); + knowl_id_counterX++; + } + knowl_click_handler($knowl); + // knowl_click_handler($(all_knowls[j])) + } +}}); + +// when the anchor is a knowl, open it +window.addEventListener("load",function(event) { + if (window.location.hash.length) { + let id = window.location.hash.substring(1); + var the_anchor = document.getElementById(id); + console.log("id", id, "the_anchor", the_anchor); + if (the_anchor.tagName == "ARTICLE") { + var contained_knowl = the_anchor.querySelector("a[data-knowl]"); + if (contained_knowl && contained_knowl.parentElement == the_anchor) { + console.log("found a knowl", contained_knowl); + // knowl_click_handler($(contained_knowl)) + contained_knowl.click() + } + } else if (the_anchor.hasAttribute("data-knowl")) { + the_anchor.click() + } else { + // if it is a hidden knowl, find the knowl and open it + var this_hidden_content = the_anchor.closest(".hidden-content"); + if (this_hidden_content) { + console.log("linked to a hidden knowl with this_hidden_content", this_hidden_content); + var the_refid = this_hidden_content.id; + var this_knowl = document.querySelector('[data-refid="' + the_refid + '"]'); + this_knowl.click() + } + } + } +}); + + +// What purpose does this serve? +function urlattribute() { + var this_urlstub = window.location.hostname; + document.body.setAttribute("data-urlstub", this_urlstub); +} + + +// The new method for creating pages and adjusting workspace // + +// This is used multiple places to set height of workspace divs to their author-provided heights +function setInitialWorkspaceHeights() { + const workspaces = document.querySelectorAll('.workspace'); + workspaces.forEach(ws => { + ws.style.height = ws.getAttribute('data-space') || '0px'; + ws.setAttribute("contenteditable", "true"); + }); +} + +// If a printout (worksheet or handout) includes authored pages, we only need to put content before the first page and after the last page into the first and last pages, respectively. +function adjustPrintoutPages() { + const printout = document.querySelector('section.worksheet, section.handout'); + if (!printout) { + console.warn("No printout found, exiting adjustPrintoutPages."); + return; + } + const pages = printout.querySelectorAll('.onepage'); + if (pages.length === 0) { + console.warn("No pages found in printout, exiting adjustPrintoutPages."); + return; + } + // Find all children before the first .onepage element: + const firstPage = pages[0]; + const lastPage = pages[pages.length - 1]; + // Move all children before the first page into the first page + const pageFirstChild = firstPage.firstChild; + let currentChild = printout.firstChild; + while (currentChild && currentChild !== firstPage) { + const nextChild = currentChild.nextSibling; // Save the next sibling before removing + firstPage.insertBefore(currentChild, pageFirstChild); // Move to the first page + currentChild = nextChild; // Move to the next child + } + // Now find all children after the last .onepage element: + let nextChild = lastPage.nextSibling; + while (nextChild) { + const tempChild = nextChild; + nextChild = nextChild.nextSibling; + lastPage.appendChild(tempChild); + } + console.log("Moved all content before the first page and after the last page into the respective pages."); +} + +// This is the main function we will call then a printout does not come from the XSL with pages already defined (for now, the XSL will keep the behavior as an option). +function createPrintoutPages(margins) { + + // Assumptions: needs to work for both letter (8.5in x 11in) and a4 (210mm x 297mm) paper sizes. We will work in pixels (96/in): those are 816px x 1056px and 794px x 1122.5px respectively (1 inch = 96 px, 1 cm = 37.8 px). We assume that the printing interface of the browser will do the right thing with these. + + // For purposes of finding page breaks, we will use 794 as our width and 1056 as our height (so A4 width and letter height). Then we will rescale workspace on each page to fit the actual paper size selected. + + const conservativeContentHeight = 1056 - (margins.top + margins.bottom); // in pixels + const conservativeContentWidth = 794 - (margins.left + margins.right); // in pixels + + const printout = document.querySelector('section.worksheet, section.handout'); + if (!printout) { + console.warn("No printout found, exiting createPrintoutPages."); + return; + } + printout.style.width = toString(conservativeContentWidth + margins.left + margins.right) + 'px'; + // Set the height of each workspace based on its data-space attribute + setInitialWorkspaceHeights(printout); + + // We want to consider each "block" of the printout. Some of these will be direct children of the printout, some will be nested inside these children. So first create a list of the elements that we consider blocks. + let rows = []; + for (const child of printout.children) { + if (child.classList.contains('sidebyside')) { + // sidebyside could have tasks, but we don't want to dive further into them. + rows.push(child); + } else if (child.querySelector('.task')) { + // Keep the child as a block, but put each task after the first one as its own row: + rows.push(child); + const tasks = child.querySelectorAll('.task'); + for (let i = tasks.length-1; i > 0; i--) { + // Move the task out of the original child and place it directly after it in the printout. We do this in reverse order so when every task is moved, they return to the original order. They will then be added to the rows list as their own blocks. + printout.insertBefore(tasks[i], child.nextSibling); + } + // Skipping separate treatment of exercisegroups for now. + //} else if (child.classList.contains('exercisegroup')) { + // for (const subChild of child.children) { + // if (subChild.classList.contains('exercisegroup-exercises')){ + // for (const row of subChild.children){ + // rows.push(row); + // } + // } else { + // rows.push(child); + // } + // } + } else { + rows.push(child); + } + } + // Loop through the blocks and create a list of objects including the block, its height, and its workspace height. Only include blocks that have height (this will remove autopermalinks, as desired). + let blockList = []; + for (const row of rows) { + let blockHeight = getElementTotalHeight(row); + if (blockHeight === 0) { + console.log("Skipping row with zero height:", row); + continue; + } + let totalWorkspaceHeight = 0; + if (row.querySelector('.workspace')) { + // Workspace height is not just sum of workspace heights; we need to be careful with sidebyside and columns + totalWorkspaceHeight = getElemWorkspaceHeight(row); + } + blockList.push({elem: row, height: blockHeight, workspaceHeight: totalWorkspaceHeight}); + } + + // Now find pageBreaks so that extra workspace is as uniform as possible. + const pageBreaks = findPageBreaks(blockList, conservativeContentHeight); + + // Create page divs and insert rows into them + for (let i = 0; i < pageBreaks.length; i++) { + const pageDiv = document.createElement('section'); + pageDiv.classList.add('onepage'); + if (i === 0) { + pageDiv.classList.add('firstpage'); + } + // A single page will be both first and last + if (i === pageBreaks.length - 1) { + pageDiv.classList.add('lastpage'); + } + // The pageBreaks array gives the indices of blocks that should start a page. + // So we will want to look for go through the blocks selecting those starting with the previous index (or 0) up to but not including the current index. + const start = pageBreaks[i-1] || 0; + const end = pageBreaks[i]; + for (let j = start; j < end; j++) { + const row = blockList[j].elem; + pageDiv.appendChild(row); + } + printout.appendChild(pageDiv); + } + + // remove any old content that is not in a page + for (const child of printout.children) { + if (!child.classList.contains('onepage')) { + console.log("Removing old child not in a page:", child); + printout.removeChild(child); + } + } +} + + + // We look at each page and adjust the heights of the workspaces to fit it nicely into the page. + // The width and height of the page will now depend on the letter or a4 setting. +function adjustWorkspaceToFitPage({paperSize, margins}) { + let paperWidth, paperHeight; + if (paperSize === 'a4' || document.body.classList.contains('a4')) { + console.log("Setting page size to A4"); + paperWidth = 794; // 210mm in px + paperHeight = 1122.5; // 297mm in px 794px x 1122.5px + } else { + console.log("Setting page size to Letter"); + paperWidth = 816; // 8.5in in px + paperHeight = 1056; // 11in in px + } + const paperContentHeight = paperHeight - (margins.top + margins.bottom); + + // Reset the heights of workspace divs to their author-provided heights + setInitialWorkspaceHeights(); + + const pages = document.querySelectorAll('.onepage'); + pages.forEach(page => { + console.log("Adjusting workspace height for page:", page); + // Set width to get accurate calculations + page.style.width = paperWidth + 'px'; + const rows = page.children; + let totalContentHeight = 0; + let totalWorkspaceHeight = 0; + for (const row of rows) { + totalContentHeight += getElementTotalHeight(row); + totalWorkspaceHeight += getElemWorkspaceHeight(row); + } + if (totalWorkspaceHeight === 0) { + console.log("No workspaces on this page, skipping workspace adjustment."); + // Reset the style for the page + page.style.width = ""; + return; + } + const extraHeight = paperContentHeight - totalContentHeight; + console.log("Extra height to distribute across workspaces:", extraHeight, "px."); + // Determine the factor by which to multiply each workspace to make the total height fit the paperContentHeight + const workspaceAdjustmentFactor = (totalWorkspaceHeight + extraHeight) / totalWorkspaceHeight; + console.log("Workspace adjustment factor for page:", workspaceAdjustmentFactor); + // Now adjust each workspace in the page by this factor + const pageWorkspaces = page.querySelectorAll('.workspace'); + pageWorkspaces.forEach(ws => { + const originalHeight = ws.offsetHeight; + const newHeight = originalHeight * workspaceAdjustmentFactor; + ws.style.height = newHeight + "px"; + }); + // Reset the style for the page + page.style.width = ""; + }); + console.log("Set page sizes to content area of paper size."); +} + +// Helper functions for calculating heights and workspace sizes +function getElementTotalHeight(elem) { + // Calculate the total height of the element, including padding, border, and top margin. + const style = getComputedStyle(elem); + const marginTop = parseFloat(style.marginTop); + const marginBottom = parseFloat(style.marginBottom); + const height = elem.offsetHeight; + return height + marginTop + marginBottom; +} + +function getElemWorkspaceHeight(elem) { + // Calculate the total height of all workspaces in the element. + // This is easy for elements stacked vertically, but we must be careful for side-by-side workspaces. Since we will multiply each workspace by a factor to fit the page, taking the largest workspace height should give us an upper bound for the amount of vertical space that is workspace. + // Note that this won't work well if we need to reduce the workspace, since there we would want to take the minimum heights. + if (elem.classList.contains('sidebyside')) { + const sbspanels = elem.querySelectorAll('.sbspanel'); + let max = 0; + sbspanels.forEach(panel => { + const workspaces = panel.querySelectorAll('.workspace'); + let totalHeight = 0; + workspaces.forEach(workspace => { + const workspaceHeight = workspace.offsetHeight; + if (workspaceHeight) { + totalHeight += workspaceHeight; + } + }); + if (totalHeight > max) { + max = totalHeight; // Take the maximum height of workspaces in sidebyside + } + }); + return max; // Return the maximum height of workspaces in sidebyside + } + // We can take care of exercisegroups and single colomn regular layout together. + let columns = 1; + if (elem.classList.contains('exercisegroup')) { + // Check for column classes and set columns accordingly + for (let i = 2; i <= 6; i++) { + if (elem.querySelector(`.cols${i}`)) { + columns = i; + console.log("Found exercisegroup with columns:", columns); + break; + } + } + } + const workspaces = elem.querySelectorAll('.workspace'); + let totalHeight = 0; + workspaces.forEach(ws => { + const workspaceHeight = ws.offsetHeight; + if (workspaceHeight) { + totalHeight += workspaceHeight; + } + }); + return totalHeight / columns; // Divide by columns if sidebyside to get average height per column +} + +// Functions for finding the optimal page breaks +function findPageBreaks(rows, pageHeight) { + // An array for the page breaks. The nth element will be the index of the last row on page n. + let pageBreaks = []; + // An array for the minimum cost possible for rows i to the end. + let minCost = Array(rows.length).fill(Infinity); + minCost[rows.length] = 0; // No cost for no rows + // An array to keep track of the next row to start a new page after i in minCost. + let nextPageBreak = Array(rows.length).fill(-1); + + // Now loop through the rows in reverse order to find the optimal page breaks. + for (let i = rows.length - 1; i >= 0; i--) { + let cumulativeHeight = 0; + let cumulativeWorkspaceHeight = 0; + // Loop through the rows starting from i to find the best page break + for (let j = i; j < rows.length; j++) { + cumulativeHeight += rows[j].height; + cumulativeWorkspaceHeight += rows[j].workspaceHeight; + if (cumulativeHeight > pageHeight) { + if (j === i) { + // The page height is too big for a single row. We make this row its own page and move on. + console.log("Row", i, "exceeds page height by itself, setting as its own page."); + minCost[i] = 0; // No cost for a single row + nextPageBreak[i] = i + 1; // The next page break is after this row + break; // Move to the next row + } else { + // We have already set minCost and NextPageBreak at an earlier point in the loop. This means we have done the best we can for this row so we stop and move to the next earlier row. + break; // Stop if we exceed the page height + } + } + + const cost = (pageHeight - cumulativeHeight)**2 + minCost[j+1]; // Cost is how much space is left on the page, plus the cost of the following pages. + if (cost < minCost[i]) { + minCost[i] = cost; + nextPageBreak[i] = j+1; // Set the next page break to be after row j + } + } + } + // Backtrack to find the actual page breaks based on nextPageBreak + // Note: the nextPage = 1 is not an indexing mistake; we always assume that row 0 is a title and will go on the same page as row 1. + let nextPage = 1; + while (nextPage < rows.length) { + pageBreaks.push(nextPageBreak[nextPage]); + nextPage = nextPageBreak[nextPage]; + } + return pageBreaks; +} + +function setPageGeometryCSS({paperSize, margins}) { + // Remove any existing geometry CSS to avoid duplicates + const existingStyle = document.getElementById("page-geometry-css"); + if (existingStyle) { + existingStyle.remove(); + } + let wsWidth = paperSize === "letter" ? "816px" : "794px"; // 8.5in for Letter, 210mm for A4 + let wsHeight = paperSize === "letter" ? "1056px" : "1123px"; // 11in for Letter, 297mm for A4 + // Create a new style element for geometry CSS + const style = document.createElement("style"); + // Add an identifier to the style element to avoid conflicts + style.id = "page-geometry-css"; + // NB we need to add the fallback values for the margins in @page because some browsers do not support CSS variables in @page rules. + style.textContent = ` + :root { + --ws-width: ${wsWidth}; + --ws-height: ${wsHeight}; + --ws-top-margin: ${margins.top}px; + --ws-right-margin: ${margins.right}px; + --ws-bottom-margin: ${margins.bottom}px; + --ws-left-margin: ${margins.left}px; + } + @page { + margin: var(--ws-top-margin, ${margins.top}px) var(--ws-right-margin, ${margins.right}px) var(--ws-bottom-margin, ${margins.bottom}px) var(--ws-left-margin, ${margins.left}px); + } + `; + document.head.appendChild(style); +} + +function toggleWorkspaceHighlight(isChecked) { + if (isChecked) { + // Toggle the highlight class on the body based on the checkbox state + document.body.classList.add("highlight-workspace"); + // If we haven't already inserted divs to show the original workspace heights, do that now + if (!document.querySelector('.workspace-container')) { + console.log("adding original workspace divs"); + // Insert divs to show the original workspace + document.querySelectorAll('.workspace').forEach(workspace => { + // Create a container div to hold the workspace div and the original div + const container = document.createElement('div'); + container.classList.add('workspace-container'); + // Set the container height to the current workspace height + container.style.height = window.getComputedStyle(workspace).height; + const original = document.createElement('div'); + original.classList.add('original-workspace'); + const originalHeight = workspace.getAttribute('data-space') || '0px'; + original.setAttribute('title', 'Author-specified workspace height (' + originalHeight + ')'); + console.log("setting original workspace height for", workspace); + // Use the data-space attribute for height of original workspace + original.style.height = originalHeight; + // insert original div before the workspace content + container.appendChild(original); + // Add a warning class if the original height is greater than the current height + if (original.offsetHeight > workspace.offsetHeight) { + original.classList.add('warning'); + } + // Move the workspace into the container + workspace.parentNode.insertBefore(container, workspace); + container.appendChild(workspace); + }); + } + } else { + document.body.classList.remove("highlight-workspace"); + } +} + +// Printout print preview and page setup +window.addEventListener("load",function(event) { + // We condition on the existence of the papersize radio buttons, which only appear in the printout print preview. + if (document.querySelector('input[name="papersize"]')) { + // First, get the margins for pages to be passed around as needed. + const marginList = document.querySelector('section.worksheet, section.handout').getAttribute('data-margins').split(' '); + // Convert margin values to pixels if they are not already numbers + function toPixels(value) { + if (typeof value === "number") return value; + if (typeof value !== "string") return 0; + value = value.trim(); + if (value.endsWith("px")) { + return parseFloat(value); + } else if (value.endsWith("in")) { + return Math.floor(parseFloat(value) * 96); + } else if (value.endsWith("cm")) { + return Math.floor(parseFloat(value) * 37.8); + } else if (value.endsWith("mm")) { + return Math.floor(parseFloat(value) * 3.78); + } else if (value.endsWith("pt")) { + return Math.floor(parseFloat(value) * (96 / 72)); + } else { + // fallback: try to parse as px + return parseFloat(value) || 0; + } + } + const margins = { + top: toPixels(marginList[0] || "0.75in"), // Default to 0.75in if not specified + right: toPixels(marginList[1] || "0.75in"), + bottom: toPixels(marginList[2] || "0.75in"), + left: toPixels(marginList[3] || "0.75in") + } + // Get the papersize from localStorage or set it based on user's geographic region + let paperSize = localStorage.getItem("papersize"); + if (paperSize) { + const radio = document.querySelector(`input[name="papersize"][value="${paperSize}"]`); + if (radio) { + radio.checked = true; + } + // Set the papersize class on body + document.body.classList.remove("a4", "letter"); + document.body.classList.add(paperSize); + setPageGeometryCSS({paperSize: paperSize, margins: margins}); + } else { + // Try to set papersize based on user's geographic region + // Default to 'letter' for North and South America, 'a4' elsewhere + try { + fetch('https://ipapi.co/json/') + .then(response => response.json()) + .then(data => { + let continent = data && data.continent_code ? data.continent_code : ""; + paperSize = (continent === "NA" || continent === "SA") ? "letter" : "a4"; + const radio = document.querySelector(`input[name="papersize"][value="${paperSize}"]`); + if (radio) { + radio.checked = true; + localStorage.setItem("papersize", paperSize); + } + document.body.classList.remove("a4", "letter"); + document.body.classList.add(paperSize); + console.log("Setting papersize to", paperSize); + }) + .catch((err) => { + // rethrow to be caught by the outer catch + throw err; + }); + } catch (e) { + // fallback: default to letter + const radio = document.querySelector(`input[name="papersize"][value="letter"]`); + if (radio) radio.checked = true; + } + //NB: the default papersize is set to 'letter' in the body class list. + } + const papersizeRadios = document.querySelectorAll('input[name="papersize"]'); + papersizeRadios.forEach(radio => { + radio.addEventListener('change', function() { + if (this.checked) { + document.body.classList.remove("a4", "letter"); + document.body.classList.add(this.value); + localStorage.setItem("papersize", this.value); + console.log("Setting papersize to", this.value); + + // If the "highlight workspace" checkbox was already checked, then we should restart the process by reloading the page. Specifically, we run into issues when there are .workspace-container divs already present. + if (document.querySelector(".workspace-container")) { + console.log("Reloading page to apply new papersize with workspace highlight enabled."); + window.location.reload(); + return; + } else { + // Otherwise, we can just adjust the workspace heights to fit the new paper size. + console.log("Adjusting workspace heights to fit new papersize."); + adjustWorkspaceToFitPage({paperSize: this.value, margins: margins}); + setPageGeometryCSS({paperSize: this.value, margins: margins}); + } + } + }); + }); + + // Open all details elements (knowls) on the page + var born_hidden_knowls = document.querySelectorAll('details'); + console.log("born_hidden_knowls", born_hidden_knowls); + born_hidden_knowls.forEach(function(detail) { + detail.open = true; + }); + // If the printout has authored pages, there will be at least one .onepage element. + if (document.querySelector('.onepage')) { + adjustPrintoutPages(); + /* not the right way: need to figure out what this needs to wait for */ + //window.setTimeout(adjustPrintoutPages, 1000); + } else { + createPrintoutPages(margins); + } + // After pages are set up, we adjust the workspace heights to fit the page (based on the paper size). + adjustWorkspaceToFitPage({paperSize: paperSize, margins: margins}); + + console.log("finished adjusting workspace"); + + + // Get the 'highlight workspace' checkbox state from localStorage or set it to false by default + const highlightWorkspaceCheckbox = document.getElementById("highlight-workspace-checkbox"); + if (highlightWorkspaceCheckbox) { + highlightWorkspaceCheckbox.checked = localStorage.getItem("highlightWorkspace") === "true"; + highlightWorkspaceCheckbox.addEventListener("change", function() { + localStorage.setItem("highlightWorkspace", this.checked); + toggleWorkspaceHighlight(this.checked); + }); + // Initial toggle to apply the highlight class if checked + toggleWorkspaceHighlight(highlightWorkspaceCheckbox.checked); + } + + + + // Not sure why this is here: + window.setTimeout(urlattribute, 1500); + } +}); + + + +//----------------------------------------------------------------- +// Dark/Light mode swiching + +function isDarkMode() { + if (document.documentElement.dataset.darkmode === 'disabled') + return false; + + const currentTheme = localStorage.getItem("theme"); + if (currentTheme === "dark") + return true; + else if (currentTheme === "light") + return false; + + return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; +} + +function setDarkMode(isDark) { + if(document.documentElement.dataset.darkmode === 'disabled') + return; + + const parentHtml = document.documentElement; + const iframes = document.querySelectorAll("iframe[data-dark-mode-enabled]"); + + // Update the parent document + if (isDark) { + parentHtml.classList.add("dark-mode"); + } else { + parentHtml.classList.remove("dark-mode"); + } + + // Sync each iframe's class with the parent + for (const iframe of iframes) { + try { + const iframeHtml = iframe.contentWindow.document.documentElement; + if (isDark) { + iframeHtml.classList.add("dark-mode") + } else { + iframeHtml.classList.remove("dark-mode") + } + } catch (err) { + console.warn("Dark mode sync to iframe failed:", err); + } + } + + const modeButton = document.getElementById("light-dark-button"); + if (modeButton) { + modeButton.querySelector('.icon').innerText = isDark ? "light_mode" : "dark_mode"; + modeButton.querySelector('.name').innerText = isDark ? "Light Mode" : "Dark Mode"; + } +} + +// Run this as soon as possible to avoid flicker +setDarkMode(isDarkMode()); + +// Rest of dark mode setup logic waits until after load +window.addEventListener("DOMContentLoaded", function(event) { + // Rerun setDarkMode now that it can update buttons + const isDark = isDarkMode(); + setDarkMode(isDark); + + const modeButton = document.getElementById("light-dark-button"); + modeButton.addEventListener("click", function() { + const wasDark = isDarkMode(); + setDarkMode(!wasDark); + localStorage.setItem("theme", wasDark ? "light" : "dark"); + }); +}); + +// Share button and embed in LMS code +window.addEventListener("DOMContentLoaded", function(event) { + const shareButton = document.getElementById("embed-button"); + if (shareButton) { + const sharePopup = document.getElementById("embed-popup"); + const embedCode = ""; + const embedTextbox = document.getElementById("embed-code-textbox"); + if (embedTextbox) { + embedTextbox.value = embedCode; + } + shareButton.addEventListener("click", function() { + sharePopup.classList.toggle("hidden"); + }); + const copyButton = document.getElementById("copy-embed-button"); + if (copyButton) { + copyButton.addEventListener("click", function() { + const embedTextbox = document.getElementById("embed-code-textbox"); + if (embedTextbox) { + navigator.clipboard.writeText(embedCode).then(() => { + console.log("Embed code copied to clipboard!"); + }).catch(err => { + console.error("Failed to copy embed code: ", err); + }); + //copyButton.innerHTML = "✓✓"; + // show confirmation for 2 seconds: + copyButton.querySelector('.icon').innerText = "library_add_check"; + setTimeout(function() { + copyButton.querySelector('.icon').innerText = "content_copy"; + sharePopup.classList.add("hidden"); + }, 450); + } + }); + } + } +}); + +// Hide everything except the content when the URL has "embed" in it +window.addEventListener("DOMContentLoaded", function(event) { + const urlParams = new URLSearchParams(window.location.search); + if (urlParams.has("embed")) { + // Set dark mode based on value of param + if (urlParams.get("embed") === "dark") { + setDarkMode(true); + } else { + setDarkMode(false); + } + const elemsToHide = [ + "ptx-navbar", + "ptx-masthead", + "ptx-page-footer", + "ptx-sidebar", + "ptx-content-footer" + ]; + for (let id of elemsToHide) { + const elem = document.getElementById(id); + if (elem) { + elem.classList.add("hidden"); + } + } + } +}); + +// START Support for code-copy button functionality +document.addEventListener("click", (ev) => { + const codeBox = ev.target.closest(".clipboardable"); + if (!navigator.clipboard || !codeBox) return; + const button = ev.target.closest(".code-copy"); + const preContent = codeBox.querySelector("pre").textContent; + navigator.clipboard.writeText(preContent); + button.classList.toggle("copied") + setTimeout(() => button.classList.toggle("copied"), 1000); +}); + +document.addEventListener("DOMContentLoaded", () => { + const elements = document.querySelectorAll(".clipboardable"); + for (el of elements) { + const div = document.createElement("div"); + div.classList.add("clipboardable"); + el.classList.remove("clipboardable"); + el.replaceWith(div); + div.insertAdjacentElement("afterbegin", el); + div.insertAdjacentHTML("beforeend", ` + + `.trim()); + } +}); +// END Support for code-copy button functionality diff --git a/slides-annotated/_static/pretext/js/pretext_search.js b/slides-annotated/_static/pretext/js/pretext_search.js new file mode 100755 index 0000000..e78d765 --- /dev/null +++ b/slides-annotated/_static/pretext/js/pretext_search.js @@ -0,0 +1,315 @@ + +// next comment is out of date: there are more search options +// from lunr-pretext-search-index.js we will have either +// var ptx_lunr_search_style = "default"; +// or +// var ptx_lunr_search_style = "reference"; + +// since there is only one search box now, this can be simplified +function doSearch(searchlocation="A") { + // Get the search terms from the input text box + var terms; + if(searchlocation == "A") { + terms = document.getElementById("ptxsearch").value; + } else { + terms = document.getElementById("ptxsearchB").value; + } + + localStorage.setItem('last-search-terms', JSON.stringify({terms: terms, time: Date.now()})); + + // Where do we want to put the results? + let resultArea = document.getElementById("searchresults") + resultArea.innerHTML = ""; // clear out any previous results + // assume AND for multiple words + var searchterms = terms; + if(searchlocation == "B") { + document.getElementById("ptxsearch").value = searchterms + console.log("ptxsearch value", document.getElementById("ptxsearch").value); + } else { + searchterms = terms; + } + + searchterms = searchterms.toLowerCase().trim(); + let pageResult = []; + if(searchterms != "") { + pageResult = ptx_lunr_idx.query((q) => { + for(let term of searchterms.split(' ')) { + q.term(term, { fields: ["title"], boost: 20 }); //exact title match with 20x weight + q.term(term, { wildcard: lunr.Query.wildcard.TRAILING, fields: ["title"], boost: 10 }); //inexact title 10x weight + q.term(term, { fields: ["body"], boost: 5 }); //exact body 5x weight + q.term(term, { wildcard: lunr.Query.wildcard.TRAILING, fields: ["body"] }); //inexact body + } + }); + } + // Number the documents from first to last so we can order the results by their + // position in the book. + snum = 0; + for (let doc of ptx_lunr_docs) { + doc.snum = snum; + snum += 1; + } + + //Limit to a sane number of results - otherwise search like 'e' matches every page + const MAX_RESULTS = 100; + let numUnshown = (pageResult.length > MAX_RESULTS) ? pageResult.length - MAX_RESULTS : 0; + pageResult.slice(0, MAX_RESULTS); + + // Transfer meta data from the document to the results to make it easy to add + // our lists later. + augmentResults(pageResult, ptx_lunr_docs); + pageResult.sort(comparePosition); + addResultToPage(terms, pageResult, ptx_lunr_docs, numUnshown, resultArea); + MathJax.typeset(); +} + +// Find the entry for a search result in the original document index +function findEntry(resultId, db) { + for (const page of db) { + if (page.id === resultId) { + return page; + } + } + return resultId; +} + +function augmentResults(result, docs) { + for (let res of result) { + let info = findEntry(res.ref, docs); + res.number = info.number; + res.type = info.type; + res.title = info.title; + res.url = info.url; + res.level = info.level; + res.snum = info.snum; + res.score = parseFloat(res.score); + + //extra score multiplier based on level - prioritize sections over subsections/exercises/etc... + const LEVEL_WEIGHTS = [3, 2, 1.5] + if( res.level < 2 ) + res.score *= LEVEL_WEIGHTS[res.level]; + + res.body = ''; + //Add body snippets and highlights + const REVEAL_WINDOW = 30; + let titleMarked = false; + for (const hit in res.matchData.metadata) { + if(res.matchData.metadata[hit].title) { + //only show one match in title as locations change after first markup + if(!titleMarked) { + if(!res.matchData.metadata[hit].title.position) + continue; + let positionData = res.matchData.metadata[hit].title.position[0]; + const startClipInd = positionData[0]; + const endClipInd = positionData[0] + positionData[1]; + res.title = res.title.substring(0, endClipInd) + '' + res.title.substring(endClipInd); + res.title = res.title.substring(0, startClipInd) + '' + res.title.substring(startClipInd); + titleMarked = true; + } + } else if (res.matchData.metadata[hit].body) { + if(!res.matchData.metadata[hit].body.position) + continue; + const bodyContent = info.body; + let positionData = res.matchData.metadata[hit].body.position[0]; + const startInd = positionData[0] - REVEAL_WINDOW; + const endInd = positionData[0] + positionData[1] + REVEAL_WINDOW; + const startClipInd = positionData[0]; + const endClipInd = positionData[0] + positionData[1]; + let resultSnippet = (startInd > 0 ? '...' : '' ) + bodyContent.substring(startInd, startClipInd); + resultSnippet += '' + bodyContent.substring(startClipInd, endClipInd) + ''; + resultSnippet += bodyContent.substring(endClipInd, endInd) + (endInd < bodyContent.length ? '...' : '' ) + '
    '; + res.body += resultSnippet; + } + } + } +} + +function rearrangedArray(arry) { + // return a new array which is arry (with depth) sorted according to meas, + // again as an array with depth. + // "with depth'' means that large children drag along their parents. + let newarry = []; + let startind = 0; + let numtograb = 0; +let ct = 1; + while (arry.length > 0 && ct < 500) { +++ct; // just in case something goes wrong + const locofmax = maxLocation(arry) + let segmentstart = locofmax; + let segmentlength = 1; + while (arry[segmentstart].level == "2") { + --segmentstart + } + while (segmentstart + segmentlength < arry.length && arry[segmentstart + segmentlength].level == "2") { + ++segmentlength + } +// console.log("locofmax", locofmax, "starting", segmentstart, "going", segmentlength, "from", arry.length); + newarry.push(...arry.splice(segmentstart,segmentlength)); + } +// console.log("newarry", newarry); + return newarry +} +function maxLocation(arry) { + let maxloc = 0; + let maxvalsofar = -1; + for (let index = 0; index < arry.length; ++index) { + if (arry[index].score > maxvalsofar) { + maxloc = index; + maxvalsofar = arry[index].score + } + } + return maxloc +} + +function comparePosition(a, b) { + if (a.snum < b.snum) { + return -1; + } + if (a.snum > b.snum) { + return 1; + } + return 0; +} +function compareScoreDesc(a, b) { + if (a.score < b.score) { + return 1; + } + if (a.score > b.score) { + return -1; + } + return 0; +} + +function addResultToPage(searchterms, result, docs, numUnshown, resultArea) { + /* backward compatibility for old html */ + if (document.getElementById("searchempty")) { + document.getElementById("searchempty").style.display = "none"; + } + let len = result.length; + console.log("first result", result[0]); + if (len == 0) { + if (document.getElementById("searchempty")) { + document.getElementById("searchempty").style.display = "block"; + } else { + let noresults = document.createElement("div"); + noresults.classList.add("noresults"); + search_no_results_string = "No results were found" + noresults.innerHTML = search_no_results_string + "."; + // console.log("the new variable", search_results_heading_string); + resultArea.appendChild(noresults); + } + document.getElementById("searchresultsplaceholder").style.display = null; + return + } +// console.log("result",result); + let allScores = result.map(function (r) { return r.score }); +// console.log(typeof allScores[0], "allScores",allScores); + allScores.sort((a,b) => (a - b)); + allScores.reverse(); +// console.log("allScores, sorted",allScores); + +// let high = result[Math.floor(len*0.25)].score; +// let med = result[Math.floor(len*0.5)].score; +// let low = result[Math.floor(len*0.75)].score; + // sort the results by their position in the book, not their score + let high = allScores[Math.floor(len*0.20)]; + let med = allScores[Math.floor(len*0.40)]; + let low = allScores[Math.floor(len*0.75)]; + if (ptx_lunr_search_style == "reference") { + result = rearrangedArray(result); + } + let indent = "1"; + let currIndent = indent; + let origResult = resultArea; + // Create list entries indenting as needed. + for (const res of result) { + let link = document.createElement("a") + // add a class so we can colorize the results based on their rank in terms + // of search score. + if (res.score >= high) { + link.classList.add("high_result") + } else if (res.score >= med) { + link.classList.add("medium_result") + } else if (res.score >= low) { + link.classList.add("low_result") + } else { + link.classList.add("no_result") + } + currIndent = res.level; + if (currIndent > indent) { + indent = currIndent; + let ilist = document.createElement("ul") + ilist.classList.add("detailed_result"); + resultArea.appendChild(ilist); + resultArea = ilist; + } else if (currIndent < indent) { + resultArea = origResult; + indent = currIndent; + } + link.href = `${res.url}`; + link.innerHTML = `${res.type} ${res.number} ${res.title}`; + let clip = document.createElement("div"); + clip.classList.add("search-result-clip"); + clip.innerHTML = `${res.body}`; + let bullet = document.createElement("li"); + bullet.classList.add('search-result-bullet'); + bullet.appendChild(link); + bullet.appendChild(clip); + let p = document.createElement("text"); + p.classList.add('search-result-score'); + p.innerHTML = ` (${res.score.toFixed(2)})`; + bullet.appendChild(p); + resultArea.appendChild(bullet); + } + + // Auto-close search results when a result is clicked in case result is on + // the same page search started from + const resultsDiv = document.getElementById('searchresultsplaceholder'); + const backDiv = document.querySelector('.searchresultsbackground'); + resultArea.querySelectorAll("a").forEach((link) => { + link.addEventListener('click', (e) => { + backDiv.style.display = 'none'; + resultsDiv.style.display = 'none'; + }); + }); + //Could print message about how many results are not shown. No way to localize it though... + // if(numUnshown > 0) { + // let bullet = document.createElement("li"); + // bullet.classList.add('search-results-bullet'); + // bullet.classList.add('search-results-unshown-count'); + // let p = document.createElement("text"); + // p.innerHTML = `${parseInt(numUnshown)} unshown results...`; + // bullet.appendChild(p); + // resultArea.appendChild(bullet); + // } + document.getElementById("searchresultsplaceholder").style.display = null; + MathJax.typesetPromise(); +} + +window.addEventListener("load", function (event) { + const resultsDiv = document.getElementById('searchresultsplaceholder'); + + //insert a div to be backgroud behind searchresultsplaceholder + const backDiv = document.createElement("div"); + backDiv.classList.add("searchresultsbackground"); + backDiv.style.display = 'none'; + resultsDiv.parentNode.appendChild(backDiv); + + document.getElementById("searchbutton").addEventListener('click', (e) => { + resultsDiv.style.display = null; + backDiv.style.display = null; + let searchInput = document.getElementById("ptxsearch"); + searchInput.value = JSON.parse(localStorage.getItem("last-search-terms")).terms; + searchInput.select(); + doSearch(); + }); + + document.getElementById("ptxsearch").addEventListener('input', (e) => { + doSearch(); + }); + + document.getElementById("closesearchresults").addEventListener('click', (e) => { + resultsDiv.style.display = 'none'; + backDiv.style.display = 'none'; + document.getElementById('searchbutton').focus(); + }); +}); diff --git a/slides-annotated/_static/pretext/js/ptx_search.js b/slides-annotated/_static/pretext/js/ptx_search.js new file mode 100755 index 0000000..f35bea2 --- /dev/null +++ b/slides-annotated/_static/pretext/js/ptx_search.js @@ -0,0 +1,112 @@ +function doSearch() { + // Get the search terms from the input text box + let terms = document.getElementById("ptxsearch").value; + // Where do we want to put the results? + let resultArea = document.getElementById("searchresults") + resultArea.innerHTML = ""; // clear out any previous results + // do the search using the provided index + let pageResult = ptx_lunr_idx.search(terms); + // Number the documents from first to last so we can order the results by their + // position in the book. + snum = 0; + for (let doc of ptx_lunr_docs) { + doc.snum = snum; + snum += 1; + } + // Transfer meta data from the document to the results to make it easy to add + // our lists later. + augmentResults(pageResult, ptx_lunr_docs); + addResultToPage(pageResult, ptx_lunr_docs, resultArea); + MathJax.typeset(); +} + +// Find the entry for a search result in the original document index +function findEntry(resultId, db) { + for (const page of db) { + if (page.id === resultId) { + return page; + } + } + return resultId; +} + +function augmentResults(result, docs) { + for (let res of result) { + let info = findEntry(res.ref, docs); + res.number = info.number; + res.type = info.type; + res.title = info.title; + res.url = info.url; + res.level = info.level; + res.snum = info.snum; + } +} + + +function comparePosition(a, b) { + if (a.snum < b.snum) { + return -1; + } + if (a.snum > b.snum) { + return 1; + } + return 0; +} + +function addResultToPage(result, docs, resultArea) { + let len = result.length + let high = result[Math.floor(len*0.25)].score; + let med = result[Math.floor(len*0.5)].score; + let low = result[Math.floor(len*0.75)].score; + // sort the results by their position in the book, not their score + result = result.sort(comparePosition) + let indent = "1"; + let currIndent = indent; + let origResult = resultArea; + // Create list entries indenting as needed. + for (const res of result) { + let link = document.createElement("a") + // add a class so we can colorize the results based on their rank in terms + // of search score. + if (res.score >= high) { + link.classList.add("high_result") + } else if (res.score >= med) { + link.classList.add("medium_result") + } else if (res.score >= low) { + link.classList.add("low_result") + } + currIndent = res.level; + if (currIndent > indent) { + indent = currIndent; + let ilist = document.createElement("ul") + ilist.classList.add("detailed_result"); + resultArea.appendChild(ilist); + resultArea = ilist; + } else if (currIndent < indent) { + resultArea = origResult; + indent = currIndent; + } + let bullet = document.createElement("li") + bullet.style.marginTop = "5px"; + link.href = `${res.url}`; + link.innerHTML = `${res.type} ${res.number} ${res.title}`; + bullet.appendChild(link) + let p = document.createElement("text"); + p.innerHTML = ` (${res.score.toFixed(2)})`; + bullet.appendChild(p); + resultArea.appendChild(bullet); + } + +} + + +function showHelp() { + let state = document.getElementById("helpme").style.display; + if (state == "none") { + document.getElementById("helpme").style.display = "block"; + document.getElementById("helpbutt").innerHTML = "Hide Help" + } else { + document.getElementById("helpme").style.display = "none"; + document.getElementById("helpbutt").innerHTML = "Show Help" + } +} diff --git a/slides-annotated/_static/pretext/js/user_preferences.js b/slides-annotated/_static/pretext/js/user_preferences.js new file mode 100755 index 0000000..b5a63c6 --- /dev/null +++ b/slides-annotated/_static/pretext/js/user_preferences.js @@ -0,0 +1,510 @@ + +editorLog = console.log; +// editorLog = function(){}; +debugLog = function(){}; +// debugLog = console.log; +//parseLog = function(){}; +parseLog = console.log; +errorLog = console.log; + +/* the structure of each object, and its realization as PreTeXt source or in HTML, + is recorded in the objectStructure dictionary. + +{"xml:id": new_id, "sourcetag": new_tag, "parent": parent_description, "title": ""} + +In pretext, pieces are of the form ["piecename", "tag"], while +in source, pieces are of the form ["piecename", "required_component"], while + +*/ + +editing_mode = 0; + +current_page = location.host+location.pathname; +debugLog("current_page", current_page); +chosen_edit_option_key = "edit_option".concat(current_page); +/* +chosen_edit_option = readCookie(chosen_edit_option_key) || ""; +editing_mode = chosen_edit_option; +*/ +chosen_edit_option = "PLACEHOLDER"; +debugLog("chosen_edit_option", chosen_edit_option, "chosen_edit_option", chosen_edit_option > 0); + +var font_families = { + 'RS': "'Roboto Serif', serif;", + 'OS': "'Open Sans', sans-serif;" +} + +var font_vals = { + // 'face': 'serif', + 'size': [12, 8, 20], + 'height': [135, 80, 200], + 'wspace': [0, -10,20], + 'lspace': [0, -20,20], + 'wdth': [100, 50, 150], + 'wght': [400, 100, 1000] +} + +function fontcss(fvals) { +console.log("in fontcss",fvals); + var csskeys = Object.keys(fvals); + var this_style = ""; + for (var j=0; j < csskeys.length; ++j) { + this_key = csskeys[j]; + document.getElementById("the" + this_key).innerHTML = fvals[this_key][0]; + +console.log("in fontcss", this_key, "with value", fvals[this_key][0], "/10", fvals[this_key][0]/10.0); + if (this_key == 'size') { + this_style += "font-size: " + fvals[this_key][0].toString() + "pt; " + } else if (this_key == 'height') { + this_style += "line-height: " + (fvals[this_key][0]/100.0).toString() + "; " + } else if (this_key == 'lspace') { + this_style += "letter-spacing: " + (fvals[this_key][0]/200.0).toString() + "rem; " + } else if (this_key == 'wspace') { + this_style += "word-spacing: " + (fvals[this_key][0]/50.0).toString() + "rem; " + } + } + + this_style += "font-variation-settings: "; + for (var j=0; j < csskeys.length; ++j) { + this_key = csskeys[j]; + + if (this_key == 'wdth') { + this_style += "'wdth' " + fvals[this_key][0].toString() + "," + } else if (this_key == 'wght') { + this_style += "'wght' " + fvals[this_key][0].toString() + "," + } + } + this_style = this_style.slice(0, -1); + this_style += ";"; + +console.log("returning this_style", this_style); + return this_style +} + +function choice_options(this_choice) { + console.log("choice_options of", this_choice); + val_dict = prefs_menu_vals[this_choice]; + console.log("val_dict", val_dict); + var these_keys = Object.keys(val_dict); + these_keys.sort(); + var these_choices = ""; + for (var j=0; j < these_keys.length; ++j) { + this_key = these_keys[j]; + these_choices += '
  • '; + these_choices += val_dict[this_key]; + these_choices += '
  • '; + } + return these_choices +} + + +// we have to keep track of multiple consecutive carriage returns +this_char = ""; +prev_char = ""; +prev_prev_char = ""; + +// sometimes we have to prevent Tab from changing focus +this_focused_element = ""; +prev_focused_element = ""; +prev_prev_focused_element = ""; + +var menu_neutral_background = "#ddb"; +var menu_active_background = "#fdd"; + +var recent_editing_actions = []; // we unshift to this, so most recent edit is first. + // currently just a human-readable list +var ongoing_editing_actions = []; +var old_content = {}; // to hold old versions of changed materials + +// what will happen with internationalization? +var keyletters = ["KeyA", "KeyB", "KeyC", "KeyD", "KeyE", "KeyF", "KeyG", "KeyH", "KeyI", "KeyJ", "KeyK", "KeyL", "KeyM", "KeyN", "KeyO", "KeyP", "KeyQ", "KeyR", "KeyS", "KeyT", "KeyU", "KeyV", "KeyW", "KeyX", "KeyY", "KeyZ"]; + +var movement_location_options = []; +var movement_location = 0; +var first_move = true; // used when starting to move, because object no longer occupies its original location + +Storage.prototype.setObject = function(key, value) { +// this.setItem(key, JSON.stringify(value)); + this.setItem(key, JSON.stringify(value, function(key, val) { +// console.log("key", key, "value", value, "val", val); + return val.toFixed ? Number(val.toFixed(3)) : val; +})); +} + +Storage.prototype.getObject = function(key) { + var value = this.getItem(key); + return value && JSON.parse(value); +} + +function randomstring(len) { + if (!len) { len = 10 } + return "tMP" + (Math.random() + 1).toString(36).substring(2,len) +} + +function removeItemFromList(lis, value) { + var index = lis.indexOf(value); + if (index > -1) { + lis.splice(index, 1); + } + return lis; +} + + +function textNodesUnder(node){ + var all = []; + for (node=node.firstChild;node;node=node.nextSibling){ + if (node.nodeType==3) { all.push([3, node]) } + else if (node.nodeType==1) { + all.push([1, node]) + var thistag = node.cloneNode().outerHTML; + console.log("thistag", thistag); + [thisopen, thisclose] = thistag.split("><"); + thisopen += ">"; thisclose += "<"; + thisinsides = textNodesUnder(node.cloneNode().innerHTML); +// all.push([0, thisopen]); + for (var j=0; j < thisinsides.length; ++j) { +// all.push(thisinsides[j]) + } +// all.push([0, thisclose]); + } +// probably we only want direct children +// else all = all.concat(textNodesUnder(node)); + } + return all; +} + +function wordsAllWrapped(node) { + var these_text_nodes = textNodesUnder(node); + console.log("node", node); + console.log("these_text_nodes", these_text_nodes); + for (var j=0; j < these_text_nodes.length; ++j) { + var this_text_node = these_text_nodes[j]; + var thistype = this_text_node[0]; + var thisnode = this_text_node[1]; + if (thistype == 3) { + // var these_node_words_and_spaces = these_text_nodes[j].nodeValue.split(/(\s+)/); + var these_node_words_and_spaces = thisnode.nodeValue.split(/(\s+)/); + console.log("these_node_words_and_spaces", these_node_words_and_spaces); + var spanned_words = ""; + for (var k=0; k < these_node_words_and_spaces.length; ++k) { + spanned_words += '' + these_node_words_and_spaces[k] + "" + } + var wass_text = document.createElement('div'); + wass_text.setAttribute('class', 'wastext'); + wass_text.innerHTML = spanned_words; + // these_text_nodes[j].nodeValue = spanned_words + thisnode.replaceWith(wass_text); + wass_text.outerHTML = wass_text.innerHTML + } else if (thistype == 1) { + // // leave it there, but make sure we know about it + // thisnode.classList.add("oneelement") + if (thisnode.classList.contains("process-math") || + thisnode.classList.contains("autopermalink") || + thisnode.classList.contains("latex-logo") || + thisnode.classList.contains("heading") || + // should we instead check for tags that *can* contain line breaks? + ["A", "CODE", "PRE"].includes(thisnode.tagName)) { + //wrap it in a span.oneword + var word_wrapper = document.createElement('span'); + word_wrapper.setAttribute('class', 'oneword'); + thisnode.parentNode.insertBefore(word_wrapper, thisnode); + word_wrapper.appendChild(thisnode); + } else { + wordsAllWrapped(thisnode) + } + } + } +} + +function linesAllWrapped() { + + // var testID = "p-446"; + // var testNode = document.getElementById(testID); + // var all_para = document.querySelectorAll(".para"); + // var all_para = document.querySelectorAll("SECTION P"); + var all_para = document.querySelectorAll("SECTION .para:not(.logical)"); + for (var pj = 0; pj < all_para.length; ++pj) { + testNode = all_para[pj]; + wordsAllWrapped(testNode); + console.log(" wordsAllWrapped", pj, "of", testNode); + + var these_words = document.querySelectorAll(".oneword, .oneelement"); + var this_line = []; + var all_lines = ""; + var current_height = these_words[0].getBoundingClientRect().bottom; + var word_depth = 0; + for (var j=0; j < these_words.length; ++j) { + // next line is an error if the paragraphs starts with an element + var this_word = these_words[j]; + var this_parent = this_word.parentElement; + if (this_word.classList.contains("oneword")) { + this_height = this_word.getBoundingClientRect().bottom; + if ( (Math.abs(this_height - current_height) < 10) && j > 0) { + if (this_parent == testNode && word_depth == 0) { + this_line.push(this_word.innerHTML) + } else { +console.log(word_depth, ":",this_parent == testNode, "this_parent", this_parent.tagName, "ccc", this_parent.ClassList, "xxx", this_word.innerHTML); + if (word_depth == 0) { +console.log("opening the tag", this_parent.tagName,"parent", this_parent,"of",this_word.innerHTML); + var parent_classlist = this_parent.classList; +console.log("parent_classlist", parent_classlist, "first", parent_classlist[0], "length", parent_classlist.length); + // did not work. why? var parent_classes = parent_classlist.join(" "); + this_line.push('<' + this_parent.tagName + ' class="' + parent_classlist[0] + '">'); + this_line.push(this_word.innerHTML); + word_depth += 1 + + } else { + this_line.push(this_word.innerHTML); + } + if (this_word.nextElementSibling == null) { +console.log("closing the tag", this_parent.tagName); + // need to close the wrapping element + this_line.push(""); + word_depth -= 1; + if (word_depth > 0 && this_word.parentElement.nextElementSibling == null) { + this_line.push(""); + word_depth -= 1; + } + } + } + } else { + html_line_contents = ""; + for (var k=0; k < this_line.length; ++k) { + html_line_contents += this_line[k] + } + // hack which only handles up to depth 2 + if (word_depth > 0) { + html_line_contents += "" + } + if (word_depth > 1) { + html_line_contents += "" + } + console.log("made", html_line_contents); + all_lines += '
    ' + html_line_contents + '
    '; + current_height = this_height; + this_line = [this_word.innerHTML]; + if (word_depth > 0) { + this_line.unshift('<' + this_parent.tagName + ' class="' + this_parent.classList[0] + '">') + } + if (word_depth > 1) { + this_line.unshift('<' + this_parent.tagName + ' class="' + this_parent.parentElement.classList[0] + '">') + } + } + } else { // we have an html node + this_line.push(this_word.outerHTML) + } + } + // partial last line may be dangling + // bug: need to fix closing tags at the end? maybe rely on automatic closure + if (this_line.length > 0) { + html_line_contents = ""; + for (var k=0; k < this_line.length; ++k) { + html_line_contents += this_line[k] + } + all_lines += '
    ' + html_line_contents + '
    ' + } + testNode.innerHTML = all_lines + } +} + +editorLog("adding tab listener"); + +document.addEventListener('keydown', logKeyDown); + +function nextsibligntabbable(startingplace, where) { + var candidate = startingplace.nextElementSibling; + if (where == "previous" ) { candidate = startingplace.previousElementSibling } + + while (candidate) { +console.log("candidate A", candidate); + if (candidate.hasAttribute("tabindex")) { return candidate } +console.log("candidate B", candidate); + if (where == "next" ) { candidate = candidate.nextElementSibling } + else { candidate = candidate.previousElementSibling } + } +} + +function logKeyDown(e) { + if (e.code == "ShiftLeft" || e.code == "ShiftRight" || e.code == "Shift") { return } + prev_prev_char = prev_char; + prev_char = this_char; + this_char = e; + debugLog("logKey",e,"XXX",e.code); + + var input_region = document.activeElement; + debugLog("input_region", input_region); + + if (input_region.id == "user-preferences-button") { + if (e.code == "Enter") { + document.getElementById("preferences_menu_holder").classList.toggle("hidden") + } else if (e.code == "Tab") { +console.log("tabbing to main pref menu", document.getElementById("preferences_menu_holder").firstElementChild); +console.log("tabbing to main pref menu", document.getElementById("preferences_menu_holder").firstElementChild.firstElementChild); + e.preventDefault(); + document.getElementById("preferences_menu_holder").firstElementChild.firstElementChild.focus() + } + } else if (input_region.hasAttribute("data-env")) { + var this_submenu = input_region.getElementsByTagName("ol")[0]; +console.log("this_submenu C", this_submenu); + if ((e.code == "Enter") || (e.code == "ArrowRight")) { +console.log("selecting",input_region, "which has", input_region.getElementsByTagName("ol"), "first",input_region.getElementsByTagName("ol")[0]); + this_submenu.classList.toggle("hidden") + input_region.classList.toggle("selected") + input_region.parentElement.classList.toggle("active") + } else if (e.code == "Tab" && e.shiftKey) { + e.preventDefault(); + if (input_region.classList.contains("selected")) { + this_submenu.classList.loggle("hidden"); + input_region.classList.loggle("selected"); + } else { // cycle up through the elements + // previous_sibling = input_region.previousElementSibling; + previous_sibling = nextsibligntabbable(input_region, "previous"); + if (previous_sibling) { previous_sibling.focus() } + else { + document.getElementById("preferences_menu_holder").classList.toggle("hidden"); + document.getElementById("user-preferences-button").focus(); + } + } + } else if (e.code == "Tab") { + e.preventDefault(); + if (input_region.classList.contains("selected")) { + var firstchild = this_submenu.firstElementChild; + if (firstchild.hasAttribute("tabindex")) { + firstchild.focus() + } else { + nextsibligntabbable(firstchild, "next").focus() + } + } else { // cycle through the elements +// next_sibling = input_region.nextElementSibling; + next_sibling = nextsibligntabbable(input_region, "next"); + if (next_sibling) { next_sibling.focus() } + else { + document.getElementById("preferences_menu_holder").classList.toggle("hidden"); + document.getElementById("user-preferences-button").focus(); + } + } + } + } else if (input_region.hasAttribute("data-val")) { +console.log("input_region", input_region); +console.log("input_regionparentElement", input_region.parentElement); + if ((e.code == "Enter") || (e.code == "ArrowRight")) { +console.log("font_vals is", font_vals); + var dataval = input_region.getAttribute("data-val"); + var datachange = input_region.getAttribute("data-change"); +console.log("dataval", dataval, "datachange",datachange); + if (input_region.parentElement.classList.contains("fonts")) { + font_vals[dataval][0] += parseFloat(datachange); + if (font_vals[dataval][0] < font_vals[dataval][1]) { font_vals[dataval][0] = font_vals[dataval][1] } + else if (font_vals[dataval][0] > font_vals[dataval][2]) { font_vals[dataval][0] = font_vals[dataval][2] } +console.log("font_vals are", font_vals); +console.log("css font_vals", fontcss(font_vals)); + var new_style = fontcss(font_vals); + var paras = document.getElementsByClassName('para'); + for (i = 0; i < paras.length; i++) { + paras[i].setAttribute('style', new_style); + } + } else if (input_region.parentElement.classList.contains("fontfamily")) { + document.body.setAttribute("data-font", datachange); + var checks = document.getElementsByClassName('ffcheck'); + for (i = 0; i < checks.length; i++) { + checks[i].innerHTML = ''; + } + document.getElementById("the" + datachange).innerHTML = "✔️"; + } else if (input_region.parentElement.classList.contains("avatar")) { + var checks = document.getElementsByClassName('avatarcheck'); + for (i = 0; i < checks.length; i++) { + checks[i].innerHTML = ''; + } + document.getElementById("theavatarbutton").innerHTML = dataval; + document.getElementById("the" + dataval).innerHTML = "✔️"; + + } else if (input_region.parentElement.classList.contains("atmosphere")) { + document.body.setAttribute("data-atmosphere", dataval); + var checks = document.getElementsByClassName('atmospherecheck'); + for (i = 0; i < checks.length; i++) { + checks[i].innerHTML = ''; + } + document.getElementById("the" + dataval).innerHTML = "✔️"; + + } else if (input_region.parentElement.classList.contains("ruler")) { + // could be motion or actual ruler + if (["mouse", "arrow", "eye"].includes(dataval)) { + document.body.setAttribute("data-motion", dataval); + var checks = document.getElementsByClassName('motioncheck'); + for (i = 0; i < checks.length; i++) { + checks[i].innerHTML = ''; + } + document.getElementById("the" + dataval).innerHTML = "✔️"; + } else { + if (!document.body.hasAttribute("data-ruler")) { + linesAllWrapped() + } + document.body.setAttribute("data-ruler", dataval); + var checks = document.getElementsByClassName('rulercheck'); + for (i = 0; i < checks.length; i++) { + checks[i].innerHTML = ''; + } + document.getElementById("the" + dataval).innerHTML = "✔️"; + } + + } + } else if (e.code == "Tab" && e.shiftKey) { + e.preventDefault(); +// previous_sibling = input_region.previousElementSibling; + previous_sibling = nextsibligntabbable(input_region, "previous"); + +console.log("previous_sibling",previous_sibling); + if (previous_sibling) { previous_sibling.focus() } + else { + input_region.parentElement.parentElement.parentElement.classList.toggle("active"); + input_region.parentElement.parentElement.classList.toggle("selected"); + input_region.parentElement.parentElement.focus(); + input_region.parentElement.classList.toggle("hidden"); + } + } else if (e.code == "Tab") { + e.preventDefault(); + // next_sibling = input_region.nextElementSibling; + next_sibling = nextsibligntabbable(input_region, "next"); + if (next_sibling) { next_sibling.focus() } + else { + input_region.parentElement.parentElement.parentElement.classList.toggle("active"); + input_region.parentElement.parentElement.classList.toggle("selected"); + input_region.parentElement.parentElement.focus(); + input_region.parentElement.classList.toggle("hidden"); + } + } + } + + + return; + + editorLog("input_region", input_region); + // if we are writing something, keystrokes usually are just text input + if (document.getElementById('actively_editing')) { + editorLog(" we are actively editing"); + + if (e.code == "Tab" && !document.getElementById('local_menu_holder')) { + // disabled for now + e.preventDefault(); + return "" + create_local_menu() + } else if (document.getElementById('local_menu_holder')) { + main_menu_navigator(e); + } else { + local_editing_action(e) + } + + } else if (document.getElementById('phantomobject')) { + var the_phantomobject = document.getElementById('phantomobject'); + + if (the_phantomobject.classList.contains('move')) { + move_object(e) + } else { + alert("do not know what to do with that") + } + } else { + console.log("calling main_menu_navigator"); + main_menu_navigator(e); + } +} + diff --git a/slides-annotated/index.html b/slides-annotated/index.html new file mode 100755 index 0000000..b3bf10d --- /dev/null +++ b/slides-annotated/index.html @@ -0,0 +1,206 @@ + + + + + + + + + + + + + +Slides for MATH xxx + + + + + + +
    + +
    +

    Slides for MATH xxx

    Section 1.1

    Title

    +

    Here is some text.

    Section 1.2

    Title 2

    +

    Some more text.

    +
    +
    + + diff --git a/slides/_static/pretext/js/answer.js b/slides/_static/pretext/js/answer.js new file mode 100755 index 0000000..652dcd3 --- /dev/null +++ b/slides/_static/pretext/js/answer.js @@ -0,0 +1,552 @@ + +function escapeHTML(text) { + if (!text) { return "" } + the_ans = text; + the_ans = the_ans.replace(/&/g, "&"); + the_ans = the_ans.replace(/<([a-zA-Z])/g, '< $1'); + + return the_ans +} +function uNescapeHTML(text) { + if (!text) { return "" } + the_ans = text; + the_ans = the_ans.replace(/< /g, "<"); + the_ans = the_ans.replace(/</g, "<"); + the_ans = the_ans.replace(/>/g, ">"); + the_ans = the_ans.replace(/&/g, "&"); + the_ans = the_ans.replace(/<([a-zA-Z])/g, "< $1"); + + return the_ans +} + +function dollars_to_slashparen(text) { + the_ans = text; + the_ans = the_ans.replace(/(^|\s|-)\$([^\$\f\r\n]+)\$(\s|\.|,|;|:|\?|!|$)/g, "$1\\($2\\)$3"); + //twice, for $5$-$6$ + the_ans = the_ans.replace(/(^|\s|-)\$([^\$\f\r\n]+)\$(\s|\.|,|;|:|\?|!|-|$)/g, "$1\\($2\\)$3"); + + return the_ans +} + +/* The structure of a reading question is an erticle with an id (this_ques_id), + containing: + #this_ques_id_text the answer given (in a div with that id) + #this_ques_id_text_hidden the answer in a raw form (in a hidden div) + #this_ques_id_text_input the answer in a textinput with that id + + Try to do all operations by id, and not by carrying objects around. +*/ + +var reading_questions = document.querySelectorAll("section.reading-questions article.exercise-like, #boelkins-ACS .main #content > section:first-of-type > section:first-of-type > .project-like li"); + +var reading_answers = {}; + +console.log('reading_questions.length', reading_questions.length); + +function make_submit_button() { + if (document.getElementById("rq_submit")) { // don't make the button if it already exists + console.log("button exists", document.getElementById("rq_submit")); + return + } + last_reading_question = reading_questions[reading_questions.length - 1]; + answer_button_holder = document.createElement('div'); + answer_button_holder.setAttribute('class', 'rq_submit_wrapper'); + answer_button_holder.innerHTML = rq_submit_button; + last_reading_question.insertAdjacentElement("afterend", answer_button_holder); +} + +function save_reading_questions() { + rq_data = {"action": "save", "user": uname, "pw": emanu, "pI": pageIdentifier, "type": "readingquestions", "rq": JSON.stringify(reading_questions_object)} + $.ajax({ + url: "https://aimath.org/cgi-bin/u/highlights.py", + type: "post", + data: JSON.stringify(rq_data), + dataType: "json", + success: function(data) { + console.log("something", data, "back from highlight"); + alert(data); + }, + error: function(errMsg) { + console.log("seems to be an error?",errMsg); + alert("Error\n" + errMsg); + } + }); + + console.log("just ajax sent", JSON.stringify(reading_questions_object)); +} + +// no point in handling reading questions if there are not any + +if (reading_questions.length) { + + // retrieve the existing reading questions, if they exist + var reading_questions_object_id = pageIdentifier + "___" + "rq"; + var reading_questions_object = localStorage.getObject(reading_questions_object_id); + var reading_questions_all_answered = false; + var reading_questions_submitted = false; + + if (!reading_questions_object) { + reading_questions_object = {} + } + + if (Object.keys(reading_questions_object).length >= reading_questions.length) { + console.log("Object.keys(reading_questions_object)",Object.keys(reading_questions_object)); + console.log("reading_questions", reading_questions); + console.log("all reading questions have previously been answered"); + reading_questions_all_answered = true; + } + + answer_css = document.createElement('style'); + answer_css.type = "text/css"; + answer_css.id = "highlight_css"; + document.head.appendChild(answer_css); + var css_for_ans = '#rq_submit { background: #FDD; padding: 3px 5px; border-radius: 0.5em}\n'; + css_for_ans += '#rq_submit.submitted { background: #EFE; color: #BBB}'; + css_for_ans += '.rq_submit_wrapper { margin-top: 0.5em; float: right}'; + answer_css.innerHTML = css_for_ans; + + rq_answer_label = ''; + this_rq_controls += 'preview'; +// this_rq_controls += 'delete'; + this_rq_controls += 'typing math?'; + this_rq_controls += '
    ' + + var this_rq_answer_and_controls = document.createElement('div'); + this_rq_answer_and_controls.setAttribute('style', 'width:80%; padding-left:10%; padding-right:10%; margin-top:0.5em;'); + this_rq_answer_and_controls.setAttribute('class', 'rq_answer'); + this_rq_answer_and_controls.innerHTML = hidden_answer_div + answer_div + this_rq_controls; + console.log("appending to ", reading_question_id); + $('#'+reading_question_id).append(this_rq_answer_and_controls); + // this.parentNode.insertAdjacentElement("afterend", this_rq_answer_and_controls); + + /* typeset the math in the reading questions answers */ + // MathJax.Hub.Queue(["Typeset",MathJax.Hub]); + if (mjvers && mjvers < 3) { + MathJax.Hub.Queue(['Typeset', MathJax.Hub]); + } else if (mjvers > 3) { + MathJax.typesetPromise(); + } + + } else if(role == "instructor" || role == "student") { + + var this_answer_link = document.createElement('div'); + this_answer_link.innerHTML = rq_answer_label; + console.log("inserting afterend of",reading_question); + // reading_question.insertAdjacentElement("afterend", this_answer_link); + reading_question.append(this_answer_link); + } else { + + console.log("should not be here"); + + } + + } + + function allow_student_answers(){ + /* make a new blank area to answer a question */ + $('.readingquestion_make_answer.student').mousedown(function(e){ + console.log(".readingquestion_make_answer student"); +// $(this).addClass("hidecontrols"); + // var this_ques_id = this.parentNode.parentNode.id; + var this_ques_id = this.parentNode.parentNode.id; + var this_ques_id_text = this_ques_id + "_text"; + var this_ques_id_controls = this_ques_id + "_controls"; + console.log(".rq", this_ques_id); + answer_textarea = ''; + this_rq_controls += 'preview'; +// this_rq_controls += 'delete'; + this_rq_controls += 'typing math?'; + this_rq_controls += '
    ' + + var this_rq_answer_and_controls = document.createElement('div'); + this_rq_answer_and_controls.setAttribute('class', 'rq_answer editing'); + this_rq_answer_and_controls.setAttribute('style', 'width:80%; padding-left:10%; padding-right:10%; margin-top:0.5em;'); + + this_rq_answer_and_controls.innerHTML = answer_textarea + this_rq_controls; + this.parentNode.insertAdjacentElement("afterend", this_rq_answer_and_controls); + + this.remove(); + + // MathJax.Hub.Queue(["Typeset",MathJax.Hub]); + + console.log("adding other keypress listener"); + var this_textarea = document.getElementById(this_ques_id_text + "_input"); + console.log("to", this_textarea); + this_textarea.addEventListener("keypress", function() { + // if(this_textarea.scrollTop != 0){ + // console.log("this_textarea.scrollHeight", this_textarea.scrollHeight, "this_textarea.scrollTop", this_textarea.scrollTop); + // console.log("this_textarea.clientHeight", this_textarea.clientHeight); + this_textarea.overflow = "scroll"; + // console.log("this_textarea.scrollHeight", this_textarea.scrollHeight, "this_textarea.scrollTop", this_textarea.scrollTop); + // console.log("this_textarea.clientHeight", this_textarea.clientHeight); + this_textarea.overflow = "hidden"; + // console.log("this_textarea.scrollHeight", this_textarea.scrollHeight, "this_textarea.scrollTop", this_textarea.scrollTop); + // console.log("this_textarea.getBoundingClientRect()", this_textarea.getBoundingClientRect()); + this_textarea.style.height = this_textarea.scrollHeight + "px"; + // } + }, false); + var tmp_id = this_ques_id_text + "_input"; + console.log("want focus to ", tmp_id); + console.log("which is ", $("#" +tmp_id)); + console.log("the active ", document.hasFocus(), "element is", document.activeElement); + console.log("or maybe it is ", $(":focus")); + $("#" + tmp_id).focus(); + document.getElementById(tmp_id).focus(); + console.log("the active ", document.hasFocus(), "element is", document.activeElement); + }); + } + allow_student_answers(); + + $('.readingquestion_make_answer.instructor').mousedown(function(e){ + console.log(".readingquestion_make_answer instructor", "instId", uname, "pI", pageIdentifier); + if (jQuery.isEmptyObject(reading_answers) || this.classList.contains("reload")) { + rq_data = {"action": "retrieve", "instId": uname, "pw": emanu, "pI": pageIdentifier, "type": "readingquestions"}; + // myjson = {"action": "retrieve", "type": "readingquestions", "instId": "100002000", "pI": "beezer-FCLA___FPm"} + + $.ajax({ + url: "https://aimath.org/cgi-bin/u/highlights.py", + type: "post", + data: JSON.stringify(rq_data), + dataType: "json", + async: false, + success: function(data) { + reading_answers = data; + // console.log("something", data, "back from highlight"); + // alert(data); + }, + error: function(errMsg) { + console.log("seems to be an error?",errMsg); + alert("Error\n" + errMsg); + } + }); + } +// var this_ques_id = this.parentNode.previousSibling.id; + var this_ques_id = this.parentNode.parentNode.id; + console.log("this_ques_id", this_ques_id); + var compiled_answers = ""; + var title_of_this_section = $("section > h2 > .title").html(); + title_of_this_section = title_of_this_section.replace(/ /g, "%20"); + title_of_this_section = title_of_this_section.replace(/\?/g, ""); + var number_of_this_rq = 1 + $("#" + this_ques_id).index(".exercise-like"); +// console.log("first title_of_this_section", title_of_this_section); +// console.log("first title_of_this_section.html()", title_of_this_section.html()); +// console.log("second title_of_this_section[0]", title_of_this_section[0]); + for(var j=0; j < reading_answers.length; ++j) { + var this_answer_all = reading_answers[j]; + var this_student_id = this_answer_all[0]; + var these_specific_answers = JSON.parse(this_answer_all[1]); + console.log("these_specific_answers", these_specific_answers); + console.log("this_answer_all[2]", this_answer_all[2]); + var this_submitted_time = JSON.parse(this_answer_all[2]); + // var this_submitted_time = this_answer_all[2]; + console.log("looking for this answer:", this_ques_id + "_text"); + var this_specific_answer = these_specific_answers[this_ques_id + "_text"]; + console.log("this_answer_all",this_answer_all); + console.log("j",j,"this_specific_answer", this_specific_answer); + console.log("this_specific_time", this_submitted_time); + this_specific_answer = dollars_to_slashparen(escapeHTML(this_specific_answer)) + if (!this_specific_answer) { + this_specific_answer = "no answer submitted"; + compiled_answers += '
    '; + } else { + compiled_answers += '
    '; + } + if (this_student_id.indexOf('@') > -1) { + this_student_id = '' + this_student_id + ''; + } + compiled_answers += '
    ' + this_student_id + '
    '; + compiled_answers += '
    ' + this_submitted_time + '
    '; + compiled_answers += '
    ' + this_specific_answer + '
    '; + compiled_answers += '
    \n'; + console.log(j, "j", these_specific_answers) + } + // if the answers are being reloaded, remove the previous answers + $("#" + this_ques_id + "_ans").remove(); + var answers_to_this_question = document.createElement('div'); + answers_to_this_question.setAttribute('class', 'compiled_answers'); + answers_to_this_question.setAttribute('id', this_ques_id + "_ans"); + +// this_rq_answer_and_controls.setAttribute('style', 'width:80%; margin-left:auto; margin-right:auto; margin-top:0.5em;'); +// this_rq_answer_and_controls.setAttribute('class', 'rq_answer'); + answers_to_this_question.innerHTML = compiled_answers; + $('#'+this_ques_id).append(answers_to_this_question); +// this.parentNode.remove(); + this.innerHTML = "Reload responses"; + $(this).addClass("reload"); +// MathJax.Hub.Queue(["Typeset",MathJax.Hub]); + if (mjvers && mjvers < 3) { + MathJax.Hub.Queue(['Typeset', MathJax.Hub]); + } else if (mjvers > 3) { + MathJax.typesetPromise(); + } + + // this.parentNode.insertAdjacentElement("afterend", this_rq_answe + + }); + + + function save_one_reading_question(this_ques_id) { + this_ques_id_text = this_ques_id + "_text"; + var this_ans_text = $("#" + this_ques_id_text + "_input"); + console.log("the value:", this_ans_text.value); +// var this_ques_id = this_ans.parentNode.previousSibling.previousSibling.id; + // var this_ques_id = this_ans.id; + console.log("this_ques_id", this_ques_id); +// console.log("this_rq_ans", this_rq_ans); + var this_ans_text_value = this_ans_text.val(); + console.log("this_ans_text_value", this_ans_text_value); + if ( /[^\x00-\x7F]/.test(this_ans_text_value)) { + this_ans_text_value = this_ans_text_value.replace(/[^\x00-\x7F]/g, "XX"); + alert("Illegal characters in answer have been replaced by XX"); + } + this_ans_text_value = $.trim(this_ans_text_value); // jQuery trim (some chrome on windows had trouble with trim) + // we have the contents of the answer, so save it to local storage + console.log("saving in local storage at", this_ques_id_text, "the answer", this_ans_text_value); + reading_questions_object[this_ques_id_text] = this_ans_text_value; + localStorage.setObject(reading_questions_object_id, reading_questions_object); + console.log("Object.keys(reading_questions_object)",Object.keys(reading_questions_object)); + console.log("reading_questions", reading_questions); + if (Object.keys(reading_questions_object).length >= reading_questions.length && uname != "guest" && role=="student") { + console.log("all reading questions have been answered"); + reading_questions_all_answered = true; + make_submit_button(); + } + + // and save a copy hidden on the page + console.log("looking for", this_ques_id + "_text_hidden"); + // when the initial answer box is created, there is no hidden version + if ( !document.getElementById(this_ques_id + "_text_hidden")) { + console.log("making a place to hide the answer"); + var hidden_answer_div = document.createElement('div'); + hidden_answer_div.setAttribute('id', this_ques_id + '_text_hidden'); + hidden_answer_div.setAttribute('class', 'tex2jax_ignore asciimath2jax_ignore'); + hidden_answer_div.setAttribute('style', 'display: none'); + console.log("this_ans_text", this_ans_text); + document.getElementById(this_ques_id_text + "_input").insertAdjacentElement("beforebegin", hidden_answer_div); + // hidden_answer_div.insertBefore(this_ans_text); + } + console.log("hiding the raw answer in", this_ques_id + "_text_hidden"); + document.getElementById(this_ques_id + "_text_hidden").innerHTML = escapeHTML(this_ans_text_value); + + //and show it on the page + var this_ans_static = document.createElement('div'); + this_ans_static.setAttribute('id', this_ques_id_text); + this_ans_static.setAttribute('class', 'given_answer has_am process-math processme'); + console.log("setting this_ans_static.innerHTML to", dollars_to_slashparen(escapeHTML(this_ans_text_value))); + this_ans_static.innerHTML = dollars_to_slashparen(escapeHTML(this_ans_text_value)) + " " + +// this_rq_ans.replaceWith(this_ans_static); + console.log("about to replace this_ans_text", this_ans_text); + this_ans_text.replaceWith(this_ans_static); + +// MathJax.Hub.Queue(["Typeset",MathJax.Hub]); + if (mjvers && mjvers < 3) { + MathJax.Hub.Queue(['Typeset', MathJax.Hub]); + } else if (mjvers > 3) { + MathJax.typesetPromise(); + } + + + console.log(" this_ans_text", this_ans_text); + + console.log("this_ans_static", this_ans_static); + + $(this_ans_static).parent().addClass("rq_answer"); + + $('#' + this_ques_id + "_controls").addClass("hidecontrols"); +// $(this_q).parent().parent().addClass("rq_answer"); + +/* + var edit_button = document.createElement('span'); + edit_button.setAttribute('class', "action edit_item rq_edit"); + edit_button.innerHTML = "edit"; + this.replaceWith(edit_button); +*/ + }; + + /* edit an existing answer */ + function edit_one_reading_question(this_ans) { + console.log(".rq_edit", this_ans); + // var this_ques_id = this.parentNode.previousSibling.id; + var this_ques_id = this_ans.parentNode.parentNode.id; + console.log("edditing this_ques_id", this_ques_id); + var this_ques_id_text = this_ques_id + "_text"; + // var this_rq_ans = this.parentNode.previousSibling; + var this_rq_ans = this_ans; +// var this_rq_ans_id = this_ans.id; + console.log("now this_ques_id_text", this_ques_id_text); + console.log(".rq_edit", this_ques_id); + console.log("this_rq_ans", this_rq_ans); + var this_rq_text = this_rq_ans.innerHTML; + console.log("looking for", this_ques_id + "_hidden"); + var this_rq_text_raw = uNescapeHTML(document.getElementById(this_ques_id + "_text_hidden").innerHTML); + console.log("this_rq_text_raw",this_rq_text_raw); + + //this is copied from above. need to eliminate repeated code + + var answer_textarea_editable = document.createElement('textarea'); + answer_textarea_editable.setAttribute('id', this_ques_id_text + "_input"); + answer_textarea_editable.setAttribute('class', 'rq_answer_text'); + answer_textarea_editable.setAttribute('rows', '3'); + answer_textarea_editable.setAttribute('style', 'width:95%; height: 44px;'); + + this_rq_ans.replaceWith(answer_textarea_editable); + + console.log("this_ans is",this_ans); + console.log("adding editing to the parent of thing with id", this_ques_id_text, "which is the parent of", $('#' + this_ques_id_text)); + $('#' + this_ques_id_text + "_input").parent().addClass("editing"); + + $('#' + this_ques_id + "_controls").removeClass("hidecontrols"); + +// WHY? $(this).parent().parent().removeClass("rq_answer"); + +/* var save_button = document.createElement('span'); + save_button.setAttribute('class', "action edit_item rq_save"); + save_button.innerHTML = "save"; + this.replaceWith(save_button); +*/ + + $('#' + this_ques_id + "_text_input").val(this_rq_text_raw); + + answer_textarea_editable.style.height = answer_textarea_editable.scrollHeight + "px"; + answer_textarea_editable.addEventListener("keypress", function() { + // if(answer_textarea_editable.scrollTop != 0){ + answer_textarea_editable.style.height = answer_textarea_editable.scrollHeight + "px"; + // } + }, false); + }; + + +/* handle saving when leaving an answer box, or editing an existing answer + when hovering over an existing answer */ + + $('body').on('click','.given_answer', function(){ +// $(this).children().last().removeClass("hidecontrols"); + edit_one_reading_question(this); + $(this).attr('z-index', '2000'); + }); + + $('body').on('mousedown','.rq_save', function(){ +// $(this).children().last().addClass("hidecontrols"); +// $(this).attr('z-index', ''); + // var this_rq = $(this).find(".given_answer"); +//ooooooooo var this_ques_id = this.parentNode.id; + var this_ques_id = this.parentNode.parentNode.parentNode.id; + console.log("id of this quesiton", this_ques_id); +// var this_rq = $(this).find(".rq_answer_text"); +// console.log("this_rq iiIIIIII", this_rq); + var this_current_answer = document.getElementById(this_ques_id + "_text_input"); + console.log("this_current_answer", this_current_answer); + console.log("this_current_answer.value", this_current_answer.value); + // console.log("this_rq IIIIiiii value", this_rq.value); +// save_one_reading_question(this_rq.parentNode.id); + save_one_reading_question(this_ques_id); + console.log("left answer area"); + $(this).removeClass("editing"); + }); + + $('body').on('click','.rq_delete', function(){ + console.log(".rq_delete"); + var this_ques_id = this.parentNode.parentNode.parentNode.id; + console.log(".rq_delete", this_ques_id); + $('#' + this_ques_id + "_controls").removeClass("hidecontrols"); +//and now put in controls + var this_answer_link = document.createElement('div'); + this_answer_link.innerHTML = rq_answer_label; + console.log("inserting afterend of",reading_question); + // reading_question.insertAdjacentElement("afterend", this_answer_link); + $(this).parent().parent().replaceWith(this_answer_link); + allow_student_answers(); + + + console.log("reading_questions_object", reading_questions_object); + console.log("this_ques_id + _text", this_ques_id + "_text"); + delete reading_questions_object[this_ques_id + "_text"]; + console.log("now reading_questions_object", reading_questions_object); + // localStorage.removeItem(this_ques_id); + localStorage.setObject(reading_questions_object_id, reading_questions_object); + }); + + if(reading_questions_all_answered && uname != "guest" && role=="student") { + make_submit_button(); + console.log("made submit button"); + } + + $('body').on('click','#rq_submit', function(){ + console.log("submitting rq answers"); + $('#rq_submit').addClass('submitted'); + document.getElementById('rq_submit').textContent = "Resubmit answers"; + save_reading_questions(); + }); + + $('body').on('click','.amhelp', function(){ + var amhelpmessage = "Write math formulas as AsciiMath inside `backticks`.\n"; + amhelpmessage += "For example:\nThe Pythagorean theorem says `sin^2(x) + cos^2(x) = 1`,\n"; + amhelpmessage += "The quadratic formula is `x = (-b +- sqrt(b^2 - 4ac))/(2a)`. \n"; + amhelpmessage += "Note the use of parentheses for grouping.\n"; + amhelpmessage += "Visit http://asciimath.org for a list of AsciiMath commands.\n\n"; + amhelpmessage += "You can also use LaTeX, with either slash-parentheses \\(...\\)\n"; + amhelpmessage += "or dollar signs $...$ as delimiters for inline math."; + alert(amhelpmessage) + }); + + if(reading_questions_all_answered && uname != "guest" && role=="student") { + make_submit_button(); + } +} diff --git a/slides/_static/pretext/js/diagcess/diagcess.js b/slides/_static/pretext/js/diagcess/diagcess.js new file mode 100755 index 0000000..69acc7c --- /dev/null +++ b/slides/_static/pretext/js/diagcess/diagcess.js @@ -0,0 +1 @@ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.diagcess=e():t.diagcess=e()}(this,(function(){return(()=>{"use strict";var t={422:(t,e)=>{function n(t){return e.prefix_+"-"+t}Object.defineProperty(e,"__esModule",{value:!0}),e.noMove=e.attr=e.hasSpeech=e.getDocument=e.children=e.classList=e.getAttribute=e.getTextContent=e.toggleExpertMode=e.EXPERT_MODE=e.toArray=e.getId=e.idCounter_=e.addPrefix=e.prefix_=void 0,e.prefix_="cacc",e.addPrefix=n,e.idCounter_=0,e.getId=function(){return n("Id-"+e.idCounter_++)},e.toArray=function(t){for(var e=[],n=0;n=t.length&&(t=void 0),{value:t&&t[o++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.addStyle=e.replaceStyle_=e.removeStyle_=e.svgForegroundStyle_=e.svgBackgroundStyle_=e.svgHighlightStyle_=e.regionStyle_=e.svgBaseStyle_=e.nodeBackgroundStyle_=e.pickScheme=e.moleculeStyle=e.getScheme_=e.colorSchemes_=e.toggleMonochrome=e.toggleTextVisibility=e.documentCmlStyle=void 0;var r=n(422),i=n(807);function c(t){return e.colorSchemes_[t%e.colorSchemes_.length]}function a(t,e){var n=c(e);return[p(t,n.bkd),s(t,n.bkd),h(t,n.frgd),u(t,n.active,n.almost)]}function s(t,e){var n="";return f(n+="div.".concat(t.node.id," { background-color: ").concat(e,"; }"),"cacc-general-background")}function l(t){var e="svg."+t.id,n="";return n+="".concat(e," { overflow: hidden; }"),n+="".concat(e," text tspan[baseline-shift] { font-weight: 200; font-size: 13px; }"),n+="".concat(e," line, svg rect { stroke-width: 1.0; stroke-linecap: round; }"),n+="".concat(e," line.").concat("cacc-active",", svg g.").concat("cacc-active"," line { stroke-width: 3.0; }"),n+="".concat(e," line.").concat("cacc-almost",", svg g.").concat("cacc-almost"," line { stroke-width: 2.0; }"),n+="".concat(e," g.").concat("cacc-active"," text { font-weight: bold; }"),n+="".concat(e," g#gridSVG g.").concat("cacc-active"," text { font-weight: bold; }"),n+="ul.".concat("cacc-controls"," { list-style-type: none; }"),n+="".concat(e," polyline.").concat("cacc-active",", svg g.").concat("cacc-active"," polyline { stroke-width: 3.0; }"),n+="".concat(e," polyline.").concat("cacc-almost",", svg g.").concat("cacc-almost"," polyline { stroke-width: 2.0; }"),n+="".concat(e," polygon.").concat("cacc-active",", svg g.").concat("cacc-active"," polygon { stroke-width: 3.0; }"),n+="".concat(e," polygon.").concat("cacc-almost",", svg g.").concat("cacc-almost"," polygon { stroke-width: 2.0; }"),n+="".concat(e," path.").concat("cacc-active",", svg g.").concat("cacc-active"," path { stroke-width: 3.0; }"),n+="".concat(e," path.").concat("cacc-almost",", svg g.").concat("cacc-almost"," path { stroke-width: 2.0; }"),n+="".concat(e," rect.").concat("cacc-active",", svg g.").concat("cacc-active"," rect { stroke-width: 3.0; }"),n+="".concat(e," rect.").concat("cacc-almost",", svg g.").concat("cacc-almost"," rect { stroke-width: 2.0; }"),n+="".concat(e," circle.").concat("cacc-active",", svg g.").concat("cacc-active"," circle { stroke-width: 3.0; }"),n+="".concat(e," circle.").concat("cacc-almost",", svg g.").concat("cacc-almost"," circle { stroke-width: 2.0; }"),n+="".concat(e," ellipse.").concat("cacc-active",", svg g.").concat("cacc-active"," ellipse { stroke-width: 3.0; }"),f(n+="".concat(e," ellipse.").concat("cacc-almost",", svg g.").concat("cacc-almost"," ellipse { stroke-width: 2.0; }"),"cacc-base")}function u(t,e,n){var o="svg."+t.id,r="";return r+="".concat(o," line.").concat("cacc-active",", g.").concat("cacc-active"," line { stroke: ").concat(e,"; }"),r+="".concat(o," line.").concat("cacc-almost",", g.").concat("cacc-almost"," line { stroke: ").concat(n,"; }"),r+="".concat(o," g.").concat("cacc-active"," text { fill: ").concat(e,"; }"),r+="".concat(o," g.").concat("cacc-almost"," text { fill: ").concat(n,"; }"),r+="".concat(o," g#gridSVG g.").concat("cacc-active"," text { fill: ").concat(e,"; }"),r+="".concat(o," g#gridSVG g.").concat("cacc-almost"," text { fill: ").concat(n,"; }"),r+="".concat(o," polyline.").concat("cacc-active",", g.").concat("cacc-active"," polyline { stroke: ").concat(e,"; }"),r+="".concat(o," polyline.").concat("cacc-almost",", g.").concat("cacc-almost"," polyline { stroke: ").concat(n,"; }"),r+="".concat(o," polygon.").concat("cacc-active",", g.").concat("cacc-active"," polygon { stroke: ").concat(e,"; }"),r+="".concat(o," polygon.").concat("cacc-almost",", g.").concat("cacc-almost"," polygon { stroke: ").concat(n,"; }"),r+="".concat(o," path.").concat("cacc-active",", g.").concat("cacc-active"," path { stroke: ").concat(e,"; }"),r+="".concat(o," path.").concat("cacc-almost",", g.").concat("cacc-almost"," path { stroke: ").concat(n,"; }"),r+="".concat(o," rect.").concat("cacc-active",", g.").concat("cacc-active"," rect { stroke: ").concat(e,"; }"),r+="".concat(o," rect.").concat("cacc-almost",", g.").concat("cacc-almost"," rect { stroke: ").concat(n,"; }"),r+="".concat(o," circle.").concat("cacc-active",", g.").concat("cacc-active"," circle { stroke: ").concat(e,"; }"),r+="".concat(o," circle.").concat("cacc-almost",", g.").concat("cacc-almost"," circle { stroke: ").concat(n,"; }"),r+="".concat(o," ellipse.").concat("cacc-active",", g.").concat("cacc-active"," ellipse { stroke: ").concat(e,"; }"),f(r+="".concat(o," ellipse.").concat("cacc-almost",", g.").concat("cacc-almost"," ellipse { stroke: ").concat(n,"; }"),"cacc-color-highlight")}function p(t,e){var n="svg."+t.id,o="";return o+="".concat(n," { background-color: ").concat(e,"; }"),o+="".concat(n," g.atom rect { stroke: ").concat(e,"; fill: ").concat(e,"; }"),o+="div.container { background-color: ".concat(e,"; }"),o+="p.".concat(t.id,".").concat("cacc-message"," { background-color: ").concat(e,"; }"),f(o+="p.".concat(t.id,".").concat("cacc-message",".").concat("cacc-text-invisible"," { color: ").concat(e,"; }"),"cacc-color-background")}function h(t,e){var n="svg."+t.id,o="";return o+="".concat(n," line { stroke: ").concat(e,"; fill: ").concat(e,"; }"),o+="".concat(n,".").concat("cacc-text-monochrome"," text"),o+=" { stroke: ".concat(e,"; fill: ").concat(e,"; }"),o+="".concat(n," g#gridSVG text { stroke: ").concat(e,"; fill: ").concat(e,"; }"),o+="div.container { color: ".concat(e,"; }"),o+="p.".concat(t.id,".").concat("cacc-message"," { color: ").concat(e,"; }"),o+="ul.".concat(t.id,".").concat("cacc-controls"," { color: ").concat(e,"; }"),i.config.POLYLINE&&(o+="".concat(n," polyline { stroke: ").concat(e,"; fill: ").concat(e,"; }")),o+="".concat(n," polygon { stroke: ").concat(e,"; fill: ").concat(e,"; }"),o+="".concat(n," path { stroke: ").concat(e,"; }"),o+="".concat(n," rect { stroke: ").concat(e,"; }"),o+="".concat(n," circle { stroke: ").concat(e,"; }"),f(o+="".concat(n," ellipse { stroke: ").concat(e,"; }"),"cacc-color-elements")}function f(t,e){var n=document.createElement("style");return n.setAttribute("class",e),n.innerHTML=t,n}function d(t,e){var n=t.querySelector("style."+e);return!!n&&(t.removeChild(n),!0)}function v(t,e){t.appendChild(e)}e.documentCmlStyle=function(){document.body&&v(document.body,f("div.cml { display:none; }","cacc-cml"))},e.toggleTextVisibility=function(t){r.classList.toggle(t,"cacc-text-invisible")},e.toggleMonochrome=function(t){r.classList.toggle(t,"cacc-text-monochrome")},e.colorSchemes_=[{frgd:"#000",bkd:"#fff",active:"#fd6",almost:"#6df"},{frgd:"#fff",bkd:"#000",active:"#fd6",almost:"#6df"},{frgd:"#3C3",bkd:"#000",active:"#fd6",almost:"#6df"},{frgd:"#ee0",bkd:"#00e",active:"#4f4",almost:"#e3e"}],e.getScheme_=c,e.moleculeStyle=function(t){var e,n,r=new Map,i=l(t);r.set(i.className,i);try{for(var c=o(a(t,0)),s=c.next();!s.done;s=c.next()){var u=s.value;r.set(u.className,u)}}catch(t){e={error:t}}finally{try{s&&!s.done&&(n=c.return)&&n.call(c)}finally{if(e)throw e.error}}return r},e.pickScheme=a,e.nodeBackgroundStyle_=s,e.svgBaseStyle_=l,e.regionStyle_=function(t){v(t,f("p.".concat("cacc-message",".").concat("cacc-text-invisible"," { font-size: 0px; }"),"cacc-region"))},e.svgHighlightStyle_=u,e.svgBackgroundStyle_=p,e.svgForegroundStyle_=h,e.removeStyle_=d,e.replaceStyle_=function(t,e,n){d(t,n),v(t,f(e,n))},e.addStyle=v},446:(t,e)=>{Object.defineProperty(e,"__esModule",{value:!0}),e.CustomEvent=e.EventType=e.KeyCodes=void 0,function(t){t[t.RETURN=13]="RETURN",t[t.ENTER=13]="ENTER",t[t.ESC=27]="ESC",t[t.SPACE=32]="SPACE",t[t.LEFT=37]="LEFT",t[t.UP=38]="UP",t[t.RIGHT=39]="RIGHT",t[t.DOWN=40]="DOWN",t[t.A=65]="A",t[t.B=66]="B",t[t.C=67]="C",t[t.D=68]="D",t[t.E=69]="E",t[t.F=70]="F",t[t.G=71]="G",t[t.H=72]="H",t[t.I=73]="I",t[t.J=74]="J",t[t.K=75]="K",t[t.L=76]="L",t[t.M=77]="M",t[t.N=78]="N",t[t.O=79]="O",t[t.P=80]="P",t[t.Q=81]="Q",t[t.R=82]="R",t[t.S=83]="S",t[t.T=84]="T",t[t.U=85]="U",t[t.V=86]="V",t[t.W=87]="W",t[t.X=88]="X",t[t.Y=89]="Y",t[t.Z=90]="Z",t[t.COMMA=188]="COMMA",t[t.PERIOD=190]="PERIOD"}(e.KeyCodes||(e.KeyCodes={})),function(t){t.CLICK="click",t.DBLCLICK="dblclick",t.MOUSEDOWN="mousedown",t.MOUSEUP="mouseup",t.MOUSEOVER="mouseover",t.MOUSEOUT="mouseout",t.MOUSEMOVE="mousemove",t.SELECTSTART="selectstart",t.KEYPRESS="keypress",t.KEYDOWN="keydown",t.KEYUP="keyup",t.TOUCHSTART="touchstart",t.TOUCHMOVE="touchmove",t.TOUCHEND="touchend",t.TOUCHCANCEL="touchcancel"}(e.EventType||(e.EventType={}));var n=function(){function t(t,e,n){this.src=t,this.type=e,this.callback=n,this.src=t,this.type=e,this.callback=n}return t.prototype.add=function(){this.src.addEventListener(this.type,this.callback)},t.prototype.remove=function(){this.src.removeEventListener(this.type,this.callback)},t}();e.CustomEvent=n},807:(t,e)=>{var n,o,r,i,c,a;Object.defineProperty(e,"__esModule",{value:!0}),e.config=e.options=e.sound=e.speech=e.colors=e.regions=e.magnifiers=e.explorers=e.version=void 0,e.version="1.1.4",function(t){t.KEY="key",t.MENU="menu"}(n=e.explorers||(e.explorers={})),function(t){t.DUMMY="dummy",t.STEP="step",t.SIMPLE="simple"}(o=e.magnifiers||(e.magnifiers={})),function(t){t.SUBTITLE="subtitle",t.SPEECH="speech"}(r=e.regions||(e.regions={})),function(t){t.MONOCHROME="monochrome",t.CONTRAST="contrast"}(i=e.colors||(e.colors={})),function(t){t.LANGUAGE="language",t.EXPERT="expert",t.DETAIL="detail"}(c=e.speech||(e.speech={})),function(t){t.OSCILLATOR="oscillator",t.VOICING="voicing"}(a=e.sound||(e.sound={})),e.options={explorers:[n.KEY,n.MENU],magnifiers:[o.STEP,o.SIMPLE,o.DUMMY],regions:[r.SUBTITLE,r.SPEECH],colors:[i.MONOCHROME,i.CONTRAST],speech:[c.LANGUAGE,c.EXPERT,c.DETAIL],sound:[a.OSCILLATOR,a.VOICING]},e.config={MSG_VISIBLE:!0,MAG_ACTIVE:o.STEP,SONIFY:!1,VOICING:!0,VOICE_LANG:"en",ACTIVE_LANG:"en",SUB_VISIBLE:!1,SUBTITLE:null,POLYLINE:!1}},23:function(t,e,n){var o=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,n=e&&t[e],o=0;if(n)return n.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&o>=t.length&&(t=void 0),{value:t&&t[o++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},r=this&&this.__read||function(t,e){var n="function"==typeof Symbol&&t[Symbol.iterator];if(!n)return t;var o,r,i=n.call(t),c=[];try{for(;(void 0===e||e-- >0)&&!(o=i.next()).done;)c.push(o.value)}catch(t){r={error:t}}finally{try{o&&!o.done&&(n=i.return)&&n.call(i)}finally{if(r)throw r.error}}return c};Object.defineProperty(e,"__esModule",{value:!0}),e.Base=void 0;var i=n(966),c=n(422),a=n(911),s=n(807),l=n(402);!function(t){t.molMap={},t.init=function(){var e,n,u=l.forDocument();Object.keys(u).length>0&&(a.documentCmlStyle(),s.config.SUBTITLE=function(){var t=c.getDocument(),e=t.createElement("p");e.classList.add("explorer"),e.classList.add("cacc-message");var n=a.getScheme_(1);e.style.cssText+="position:fixed;left:0;bottom:0;"+"background-color: ".concat(n.bkd,"; color: ").concat(n.frgd),a.regionStyle_(t.body),s.config.SUB_VISIBLE||e.classList.add("cacc-text-invisible");return e}());try{for(var p=o(Object.entries(u)),h=p.next();!h.done;h=p.next()){var f=r(h.value,2),d=f[0],v=f[1];t.molMap[d]||(t.molMap[d]=new i.Container(d,v))}}catch(t){e={error:t}}finally{try{h&&!h.done&&(n=p.return)&&n.call(p)}finally{if(e)throw e.error}}}}(e.Base||(e.Base={}))},402:function(t,e,n){var o=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,n=e&&t[e],o=0;if(n)return n.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&o>=t.length&&(t=void 0),{value:t&&t[o++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.fromUri_=e.getUrlParameters_=e.getParam_=e.getData_=e.getMol_=e.forDocument=void 0;var r=n(29),i=0;function c(t){t.querySelector("div.svg")?(t.setAttribute("has-svg","true"),r.rewriteTransformations(t.querySelector("div.svg > svg"))):a(t,"svg","data-src",".svg"),t.querySelector("div.cml")?t.setAttribute("has-cml","true"):a(t,"cml","data-cml",".xml")}function a(t,e,n,o){var i=t.getAttribute(n)||s(t,n,o);i?u(i,(function(n){if(n){t.setAttribute("has-"+e,"true");var o=document.createElement("div");o.classList.add(e),"svg"===e&&r.rewriteTransformations(n),o.appendChild(n.documentElement),t.appendChild(o)}else t.setAttribute("has-"+e,"false")})):t.setAttribute("has-"+e,"false")}function s(t,e,n){if(!t.id)return null;var o=l()[t.id];return o?(o+=n,t.setAttribute(e,o),o):null}function l(){for(var t,e={},n=window.location.href.slice(window.location.href.indexOf("?")+1).split("&"),o=0;o{Object.defineProperty(e,"__esModule",{value:!0}),e.rewriteTransformations=void 0;var o=n(422);function r(t){var e=t,n=[];do{var o=e.getAttribute("transform");o&&n.unshift(o),e=e.parentNode}while("svg"!==e.tagName);return t.setAttribute("transform",n.join(" ")),t}function i(t){var e=document.createElementNS("http://www.w3.org/2000/svg","g"),n=t.getAttribute("id");return n&&(e.setAttribute("id",n),t.removeAttribute("id")),t.parentNode.replaceChild(e,t),e.appendChild(t),e}function c(t){var e=t;do{(e=e.parentNode).removeAttribute("transform")}while("svg"!==e.tagName)}e.rewriteTransformations=function(t){var e,n=o.toArray(t.querySelectorAll("text,polyline,line,rect,polygon,use,path"));return(e=n).forEach(r),e.forEach(c),e.filter((function(t){return t.hasAttribute("id")})).forEach(i),n}},113:function(t,e,n){var o,r=this&&this.__extends||(o=function(t,e){return o=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n])},o(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function n(){this.constructor=t}o(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)});Object.defineProperty(e,"__esModule",{value:!0}),e.Contrast=e.Monochrome=e.AbstractColor=void 0;var i=n(911),c=n(446),a=n(807),s=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return r(e,t),e.prototype.update=function(){},e}(n(530).AbstractController);e.AbstractColor=s;var l=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.name=a.colors.MONOCHROME,e}return r(e,t),e.prototype.customEvents=function(){var t=this;this.registerEvent(this.container.molecule.node,c.EventType.KEYDOWN,(function(e){return e.keyCode===c.KeyCodes.T&&i.toggleMonochrome(t.container.molecule.svg),e.stopPropagation(),!1}))},e}(s);e.Monochrome=l;var u=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.name=a.colors.CONTRAST,e.scheme=0,e}return r(e,t),e.prototype.customEvents=function(){var t=this;this.registerEvent(this.container.molecule.node,c.EventType.KEYDOWN,(function(e){return e.keyCode===c.KeyCodes.C&&t.container.replaceStyles(i.pickScheme(t.container.molecule,++t.scheme)),e.stopPropagation(),!1}))},e}(s);e.Contrast=u},530:function(t,e,n){var o=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,n=e&&t[e],o=0;if(n)return n.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&o>=t.length&&(t=void 0),{value:t&&t[o++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.AbstractController=void 0;var r=n(446),i=function(){function t(t){this.container=t,this._active=!1,this.internalEvents_=[],this.customEvents()}return Object.defineProperty(t.prototype,"active",{get:function(){return this._active},set:function(t){this._active=t},enumerable:!1,configurable:!0}),t.prototype.activate=function(){this.active=!0,this.addEvents()},t.prototype.deactivate=function(){this.removeEvents(),this.active=!1},t.prototype.registerEvent=function(t,e,n){this.internalEvents_.push(new r.CustomEvent(t,e,n))},t.prototype.addEvents=function(){var t,e;try{for(var n=o(this.internalEvents_),r=n.next();!r.done;r=n.next()){r.value.add()}}catch(e){t={error:e}}finally{try{r&&!r.done&&(e=n.return)&&e.call(n)}finally{if(t)throw t.error}}},t.prototype.removeEvents=function(){var t,e;try{for(var n=o(this.internalEvents_),r=n.next();!r.done;r=n.next()){r.value.remove()}}catch(e){t={error:e}}finally{try{r&&!r.done&&(e=n.return)&&e.call(n)}finally{if(t)throw t.error}}},t}();e.AbstractController=i},889:function(t,e,n){var o=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,n=e&&t[e],o=0;if(n)return n.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&o>=t.length&&(t=void 0),{value:t&&t[o++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.ControllerFactory=void 0;var r=n(807),i=n(113),c=n(172),a=n(10),s=n(556),l=n(150),u=n(916);!function(t){var e,n=((e={})[r.magnifiers.SIMPLE]=function(t){return new a.SimpleMagnifier(t)},e[r.magnifiers.STEP]=function(t){return new a.StepMagnifier(t)},e[r.magnifiers.DUMMY]=function(t){return new a.DummyMagnifier(t)},e[r.colors.MONOCHROME]=function(t){return new i.Monochrome(t)},e[r.colors.CONTRAST]=function(t){return new i.Contrast(t)},e[r.speech.DETAIL]=function(t){return new u.Detail(t)},e[r.speech.EXPERT]=function(t){return new u.Expert(t)},e[r.speech.LANGUAGE]=function(t){return new u.Language(t)},e[r.regions.SPEECH]=function(t){return new s.Speech(t)},e[r.regions.SUBTITLE]=function(t){return new s.Subtitle(t)},e[r.explorers.KEY]=function(t){return new c.KeyExplorer(t)},e[r.explorers.MENU]=function(t){return new c.MenuExplorer(t)},e[r.sound.OSCILLATOR]=function(t){return new l.Oscillator(t)},e[r.sound.VOICING]=function(t){return new l.Voicing(t)},e);t.get=function(t){var e,i,c=[],a=[].concat(r.options.magnifiers,r.options.regions,r.options.colors,r.options.speech,r.options.sound,r.options.explorers);try{for(var s=o(a),l=s.next();!l.done;l=s.next()){var u=l.value,p=n[u];if(p){var h=p(t);c.push(h)}}}catch(t){e={error:t}}finally{try{l&&!l.done&&(i=s.return)&&i.call(s)}finally{if(e)throw e.error}}return c}}(e.ControllerFactory||(e.ControllerFactory={}))},172:function(t,e,n){var o,r=this&&this.__extends||(o=function(t,e){return o=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n])},o(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function n(){this.constructor=t}o(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)});Object.defineProperty(e,"__esModule",{value:!0}),e.MenuExplorer=e.KeyExplorer=e.AbstractExplorer=void 0;var i=n(422),c=n(446),a=n(807),s=function(t){function e(e){var n=t.call(this,e)||this;return n.container=e,n.setupControls(),n}return r(e,t),e.prototype.activate=function(){this.active&&(this.container.update(),this.addControls(),t.prototype.activate.call(this))},e.prototype.deactivate=function(){this.active&&(t.prototype.deactivate.call(this),this.removeControls())},e.prototype.addEvents=function(){this.active&&t.prototype.addEvents.call(this)},e.prototype.setupControls=function(){},e.prototype.addControls=function(){},e.prototype.removeControls=function(){},e.prototype.update=function(){},e.prototype.customEvents=function(){var t=this,e=this.container.molecule.node;new c.CustomEvent(e,c.EventType.KEYDOWN,(function(e){return t.active&&e.keyCode===c.KeyCodes.ESC&&t.container.deactivate(),t.container.active||e.keyCode===t.getActivationKey()&&(t.active=!0,t.container.activate(),e.preventDefault()),e.stopPropagation(),!1})).add()},e}(n(530).AbstractController);e.AbstractExplorer=s;var l=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.name=a.explorers.KEY,e}return r(e,t),e.prototype.setupControls=function(){var t=this,e=function(e,n){var o=n();o.length>0?t.container.update(o[0]):(0,i.noMove)(),e.preventDefault()};this.registerEvent(this.container.molecule.node,c.EventType.KEYDOWN,(function(n){switch(n.keyCode){case c.KeyCodes.DOWN:case c.KeyCodes.D:e(n,(function(){return t.container.activeElement.getDown()}));break;case c.KeyCodes.UP:case c.KeyCodes.E:e(n,(function(){return t.container.activeElement.getUp()}));break;case c.KeyCodes.RIGHT:case c.KeyCodes.F:e(n,(function(){return t.container.activeElement.getRight()}));break;case c.KeyCodes.LEFT:case c.KeyCodes.S:e(n,(function(){return t.container.activeElement.getLeft()}))}return n.stopPropagation(),!1}))},e.prototype.getActivationKey=function(){return c.KeyCodes.A},e}(s);e.KeyExplorer=l;var u=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.name=a.explorers.MENU,e}return r(e,t),e.prototype.setupControls=function(){var t=this,e=this.container.molecule.createElement("ul");e.classList.add("cacc-controls"),e.setAttribute("role","navigation"),this.control_=e;var n=function(e,n){var o=n();o.length>0?t.container.update(o[0]):(0,i.noMove)()},o=function(e,o){t.registerEvent(e,c.EventType.KEYDOWN,(function(t){return t.keyCode!==c.KeyCodes.ENTER&&t.keyCode!==c.KeyCodes.SPACE||(n(0,o),t.preventDefault(),t.stopPropagation()),!1})),t.registerEvent(e,c.EventType.CLICK,(function(t){return n(0,o),t.stopPropagation(),!1}))};o(this.makeButton_("Down",e),(function(){return t.container.activeElement.getDown()})),o(this.makeButton_("Up",e),(function(){return t.container.activeElement.getUp()})),o(this.makeButton_("Next",e),(function(){return t.container.activeElement.getRight()})),o(this.makeButton_("Previous",e),(function(){return t.container.activeElement.getLeft()}))},e.prototype.addControls=function(){var t=this.container.molecule.node,e=t.querySelector(".cacc-message");e?t.insertBefore(this.control_,e):t.appendChild(this.control_)},e.prototype.removeControls=function(){this.control_.parentNode.removeChild(this.control_)},e.prototype.getActivationKey=function(){return c.KeyCodes.B},e.prototype.makeButton_=function(t,e){var n=this.container.molecule.createElement("li");return n.setAttribute("role","button"),n.setAttribute("aria-controls",this.container.molecule.node.getAttribute("id")),n.classList.add("cacc-button"),n.setAttribute("tabindex","0"),n.setAttribute("title",t),n.appendChild(document.createTextNode(t)),e.appendChild(n),n},e}(s);e.MenuExplorer=u},10:function(t,e,n){var o,r=this&&this.__extends||(o=function(t,e){return o=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n])},o(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function n(){this.constructor=t}o(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)});Object.defineProperty(e,"__esModule",{value:!0}),e.StepMagnifier=e.SimpleMagnifier=e.DummyMagnifier=e.AbstractMagnifier=void 0;var i=n(446),c=n(807),a=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return r(e,t),Object.defineProperty(e.prototype,"active",{get:function(){return this._active&&c.config.MAG_ACTIVE===this.name},set:function(t){this._active=t},enumerable:!1,configurable:!0}),e.prototype.update=function(){this.container.viewBox.select(this.container.activeElement.getDrawnElements())},e.prototype.deactivate=function(){this.active&&(this.container.viewBox.clearActive(),this.container.viewBox.reset(),t.prototype.deactivate.call(this))},e.prototype.customEvents=function(){var t=this;this.registerEvent(this.container.molecule.node,i.EventType.KEYDOWN,(function(e){return e.keyCode===t.getActivationKey()&&c.config.MAG_ACTIVE!==t.name&&(c.config.MAG_ACTIVE=t.name,t.update()),e.stopPropagation(),!1}))},e}(n(530).AbstractController);e.AbstractMagnifier=a;var s=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.name=c.magnifiers.DUMMY,e}return r(e,t),e.prototype.getActivationKey=function(){return i.KeyCodes.N},e.prototype.update=function(){this.container.viewBox.reset(),t.prototype.update.call(this)},e}(a);e.DummyMagnifier=s;var l=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.name=c.magnifiers.SIMPLE,e}return r(e,t),e.prototype.update=function(){this.container.viewBox.select(this.container.activeElement.getDrawnElements()),this.container.viewBox.zoom("cacc-active")},e.prototype.getActivationKey=function(){return i.KeyCodes.COMMA},e}(a);e.SimpleMagnifier=l;var u=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.name=c.magnifiers.STEP,e.lastActive=null,e}return r(e,t),e.prototype.update=function(){var t=this.container.activeElement,e=this.container.viewBox,n=this.lastActive?this.lastActive.getDrawnElements():[];this.container.viewBox.select(t.getDrawnElements(),"cacc-almost"),e.select(t.getDrawnElements().concat(n),"cacc-dummy"),this.lastActive=t,e.zoom("cacc-dummy",{duration:250,delay:25,wait:300,callback:function(){e.select([],"cacc-almost"),e.select(t.getDrawnElements(),"cacc-active"),e.zoom("cacc-active",{duration:250,delay:25,wait:300})}})},e.prototype.getActivationKey=function(){return i.KeyCodes.M},e}(a);e.StepMagnifier=u},556:function(t,e,n){var o,r=this&&this.__extends||(o=function(t,e){return o=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n])},o(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function n(){this.constructor=t}o(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)});Object.defineProperty(e,"__esModule",{value:!0}),e.Speech=e.Subtitle=e.LiveRegion=void 0;var i=n(422),c=n(911),a=n(446),s=n(807),l=function(t){function e(e){var n=t.call(this,e)||this;return n.container=e,n.setupMessage(),n}return r(e,t),e.prototype.setupMessage=function(){var t=this.container.molecule.createElement("p");t.classList.add("explorer"),t.setAttribute("aria-live","assertive"),i.hasSpeech()&&(speechSynthesis.onvoiceschanged=function(){speechSynthesis.getVoices().length&&t.removeAttribute("aria-live")}),t.setAttribute("tabindex","-1"),t.classList.add("cacc-message"),this.message=t},e.prototype.activate=function(){this.active||(this.addMessage(),t.prototype.activate.call(this))},e.prototype.deactivate=function(){this.active&&(t.prototype.deactivate.call(this),this.removeMessage())},e.prototype.removeMessage=function(){this.message.parentNode.removeChild(this.message)},e.prototype.update=function(){this.message.textContent=this.container.activeElement.getSpeech()},e.prototype.customEvents=function(){var t=this;this.registerEvent(this.container.molecule.node,a.EventType.KEYDOWN,(function(e){return e.keyCode===a.KeyCodes.Z&&(t.toggleMessageVisibility(),s.config.MSG_VISIBLE=!s.config.MSG_VISIBLE),e.stopPropagation(),!1}))},e.prototype.toggleMessageVisibility=function(){c.toggleTextVisibility(this.message)},e}(n(530).AbstractController);e.LiveRegion=l;var u=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.name=s.regions.SUBTITLE,e}return r(e,t),e.prototype.addMessage=function(){this.container.molecule.node.parentNode.appendChild(this.message),s.config.MSG_VISIBLE||this.message.classList.add("cacc-text-invisible")},e}(l);e.Subtitle=u;var p=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.name=s.regions.SPEECH,e}return r(e,t),e.prototype.setupMessage=function(){this.message=s.config.SUBTITLE},e.prototype.addMessage=function(){this.message.parentNode||i.getDocument().body.appendChild(this.message)},e.prototype.customEvents=function(){var t=this;this.registerEvent(this.container.molecule.node,a.EventType.KEYDOWN,(function(e){return e.keyCode===a.KeyCodes.V&&(t.toggleMessageVisibility(),s.config.SUB_VISIBLE=!s.config.SUB_VISIBLE),e.stopPropagation(),!1}))},e}(l);e.Speech=p},150:function(t,e,n){var o,r=this&&this.__extends||(o=function(t,e){return o=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n])},o(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function n(){this.constructor=t}o(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)});Object.defineProperty(e,"__esModule",{value:!0}),e.Voicing=e.Oscillator=e.AbstractSoundController=void 0;var i=n(422),c=n(446),a=n(807),s=n(530),l=n(394),u=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return r(e,t),e.prototype.activate=function(){t.prototype.activate.call(this),this.active=this.on()},e.prototype.deactivate=function(){this.stop(),t.prototype.deactivate.call(this)},e.prototype.update=function(){this.stop()},e.prototype.getStopKey=function(){return c.KeyCodes.K},e.prototype.customEvents=function(){var t=this;this.registerEvent(this.container.molecule.node,c.EventType.KEYDOWN,(function(e){return e.keyCode===t.getActivationKey()&&(t.active=!t.active,t.toggle(t.active),t.active?t.update():t.stop()),e.keyCode===t.getRepeatKey()&&t.update(),e.keyCode===t.getStopKey()&&t.stop(),e.stopPropagation(),!1}))},e}(s.AbstractController);e.AbstractSoundController=u;var p=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.name=a.sound.OSCILLATOR,e.currentOscillators=[],e}return r(e,t),e.prototype.update=function(){t.prototype.update.call(this),this.currentOscillators=l.Sonify.sonify(this.container.activeElement)},e.prototype.on=function(){return a.config.SONIFY},e.prototype.toggle=function(t){a.config.SONIFY=t},e.prototype.stop=function(){this.currentOscillators.forEach((function(t){return t.stop()}))},e.prototype.getActivationKey=function(){return c.KeyCodes.O},e.prototype.getRepeatKey=function(){return c.KeyCodes.P},e}(u);e.Oscillator=p;var h=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.name=a.sound.VOICING,e.languages=null,e}return r(e,t),e.prototype.update=function(){t.prototype.update.call(this);var e=new SpeechSynthesisUtterance(this.container.activeElement.getSpeech());e.lang=this.languages?this.languages.active:a.config.VOICE_LANG,speechSynthesis.speak(e)},e.prototype.activate=function(){i.hasSpeech()&&(this.languages=this.container.molecule.cmlStructure.getRootElement().getDiagram().languages,this.languages&&this.languages.updateActive(),t.prototype.activate.call(this))},e.prototype.getActivationKey=function(){return c.KeyCodes.Y},e.prototype.getRepeatKey=function(){return c.KeyCodes.U},e.prototype.on=function(){return a.config.VOICING},e.prototype.toggle=function(t){a.config.VOICING=t},e.prototype.stop=function(){i.hasSpeech()&&speechSynthesis.cancel()},e}(u);e.Voicing=h},916:function(t,e,n){var o,r=this&&this.__extends||(o=function(t,e){return o=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n])},o(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function n(){this.constructor=t}o(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)});Object.defineProperty(e,"__esModule",{value:!0}),e.Language=e.Expert=e.Detail=e.AbstractSpeech=void 0;var i=n(422),c=n(446),a=n(807),s=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return r(e,t),e.prototype.update=function(){},e}(n(530).AbstractController);e.AbstractSpeech=s;var l=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.name=a.speech.DETAIL,e}return r(e,t),e.prototype.customEvents=function(){var t=this;this.registerEvent(this.container.molecule.node,c.EventType.KEYDOWN,(function(e){return e.keyCode===c.KeyCodes.W&&(t.container.activeElement.link?(t.container.activeElement.links=!0,t.container.update(),t.container.activeElement.links=!1):((0,i.toggleExpertMode)(),t.container.update(),(0,i.toggleExpertMode)())),e.stopPropagation(),!1}))},e}(s);e.Detail=l;var u=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.name=a.speech.EXPERT,e}return r(e,t),e.prototype.customEvents=function(){var t=this;this.registerEvent(this.container.molecule.node,c.EventType.KEYDOWN,(function(e){return e.keyCode===c.KeyCodes.X&&((0,i.toggleExpertMode)(),t.container.update()),e.stopPropagation(),!1}))},e}(s);e.Expert=u;var p=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.name=a.speech.LANGUAGE,e}return r(e,t),e.prototype.customEvents=function(){var t=this;this.registerEvent(this.container.molecule.node,c.EventType.KEYDOWN,(function(e){e.keyCode===c.KeyCodes.L&&(t.container.molecule.cmlStructure.getMap().nextLanguage()&&t.container.active&&t.container.update());return e.stopPropagation(),!1}))},e}(s);e.Language=p},250:(t,e)=>{Object.defineProperty(e,"__esModule",{value:!0}),e.Bbox=void 0;var n=function(){function t(t,e,n,o){this.x=t,this.y=e,this.width=n,this.height=o}return t.fromSvgRect=function(e){return new t(e.x,e.y,e.width,e.height)},t.fromSvgViewBox=function(e){return new t(e.baseVal.x,e.baseVal.y,e.baseVal.width,e.baseVal.height)},t.combineBboxes=function(e){var n=function(t){var e=t.getBBox();if(0===e.x&&0===e.y&&0===e.width&&0===e.height&&t.childNodes&&t.childNodes[0]&&t.childNodes[0].transform){var n=t.childNodes[0],o=void 0,r=void 0,i=void 0,c=void 0;if(n.x&&n.y&&n.width&&n.height)o=n.x.baseVal.value,r=n.y.baseVal.value,i=n.width.baseVal.value,c=n.height.baseVal.value;else{var a=n.getBBox();o=a.baseVal.x,r=a.baseVal.y,i=a.baseVal.width,c=a.baseVal.height}var s=t.ownerSVGElement.createSVGPoint(),l=n.transform.baseVal;s.x=o,s.y=r;for(var u=l.length-1;u>=0;u--)s=s.matrixTransform(l.getItem(u).matrix);e.x=s.x,e.y=s.y,e.width=i,e.height=c}return e},o=t.fromSvgRect(n(e[0]));return 1===e.length?o:e.slice(1).reduce((function(t,e){return t.combine(n(e))}),o)},t.prototype.add=function(e){return new t(this.x+e.x,this.y+e.y,this.width+e.width,this.height+e.height)},t.prototype.difference=function(t,e,n){var o=this.add(t.scale(-1));return e&&(o.x=o.x-e/2,o.width=o.width+e),n&&(o.y=o.y-n/2,o.height=o.height+n),o},t.prototype.scale=function(e){return new t(this.x*e,this.y*e,this.width*e,this.height*e)},t.prototype.combine=function(e){var n=Math.min(this.x,e.x),o=Math.min(this.y,e.y);return new t(n,o,Math.max(this.x+this.width,e.x+e.width)-n,Math.max(this.y+this.height,e.y+e.height)-o)},t.prototype.viewBoxValue=function(){return this.x+" "+this.y+" "+this.width+" "+this.height},t}();e.Bbox=n},966:function(t,e,n){var o,r=this&&this.__extends||(o=function(t,e){return o=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n])},o(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function n(){this.constructor=t}o(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)}),i=this&&this.__awaiter||function(t,e,n,o){return new(n||(n=Promise))((function(r,i){function c(t){try{s(o.next(t))}catch(t){i(t)}}function a(t){try{s(o.throw(t))}catch(t){i(t)}}function s(t){var e;t.done?r(t.value):(e=t.value,e instanceof n?e:new n((function(t){t(e)}))).then(c,a)}s((o=o.apply(t,e||[])).next())}))},c=this&&this.__generator||function(t,e){var n,o,r,i,c={label:0,sent:function(){if(1&r[0])throw r[1];return r[1]},trys:[],ops:[]};return i={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function a(i){return function(a){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;c;)try{if(n=1,o&&(r=2&i[0]?o.return:i[0]?o.throw||((r=o.return)&&r.call(o),0):o.next)&&!(r=r.call(o,i[1])).done)return r;switch(o=0,r&&(i=[2&i[0],r.value]),i[0]){case 0:case 1:r=i;break;case 4:return c.label++,{value:i[1],done:!1};case 5:c.label++,o=i[1],i=[0];continue;case 7:i=c.ops.pop(),c.trys.pop();continue;default:if(!(r=c.trys,(r=r.length>0&&r[r.length-1])||6!==i[0]&&2!==i[0])){c=0;continue}if(3===i[0]&&(!r||i[1]>r[0]&&i[1]=t.length&&(t=void 0),{value:t&&t[o++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},s=this&&this.__read||function(t,e){var n="function"==typeof Symbol&&t[Symbol.iterator];if(!n)return t;var o,r,i=n.call(t),c=[];try{for(;(void 0===e||e-- >0)&&!(o=i.next()).done;)c.push(o.value)}catch(t){r={error:t}}finally{try{o&&!o.done&&(n=i.return)&&n.call(i)}finally{if(r)throw r.error}}return c};Object.defineProperty(e,"__esModule",{value:!0}),e.Container=void 0;var l=n(911),u=n(446),p=n(807),h=n(530),f=n(889),d=n(691),v=n(229),g=function(t){function e(e,n){var o=t.call(this,null)||this;return o.key=e,o.mol=n,o.name="Container",o.controllers=[],o.initElement().then((function(){o.viewBox=new v.ViewBox(o.molecule.svg),o.controllers=o.controllers.concat(f.ControllerFactory.get(o)),o.styles=l.moleculeStyle(o.molecule),o.activeElement=o.molecule.cmlStructure.getRootElement(),o.customEvents(),o.getDefaultExplorer()})).catch((function(){console.error("Failed to initialize element ".concat(e))})),o}return r(e,t),e.loadElement=function(t){return i(this,void 0,void 0,(function(){var e;return c(this,(function(n){return e=0,[2,new Promise((function(n,o){var r=function(){t.hasAttribute("has-svg")&&t.hasAttribute("has-cml")?n():e>5?o(t):(e++,setTimeout(r,100))};r()})).catch((function(t){"true"!==t.getAttribute("has-svg")&&console.error("Failed to load SVG diagram."),"true"!==t.getAttribute("has-cml")&&console.error("Failed to load XML annotation.")}))]}))}))},e.prototype.initElement=function(){return i(this,void 0,void 0,(function(){var t=this;return c(this,(function(n){return[2,e.loadElement(this.mol).then((function(){if("true"!==t.mol.getAttribute("has-svg")||"true"!==t.mol.getAttribute("has-cml"))return Promise.reject("Loading of components failed.");var e=t.mol.querySelector("div.svg").children[0],n=t.mol.querySelector("div.cml").children[0];return e&&n?(t.mol.setAttribute("tabindex","0"),t.mol.setAttribute("role","application"),e.setAttribute("aria-hidden","true"),n.setAttribute("aria-hidden","true"),t.mol.hasAttribute("aria-label")||t.mol.setAttribute("aria-label","Navigatable ".concat(n.tagName)),t.molecule=new d.Molecule(t.mol,e,n),Promise.resolve()):Promise.reject("Empty Diagram elements.")})).catch((function(t){console.error("Molecule Structure initialisation failed with:",t)}))]}))}))},e.prototype.update=function(t){t&&(this.activeElement=t),this.controllers.forEach((function(t){t.active&&t.update()}))},e.prototype.activate=function(){t.prototype.activate.call(this),this.addStyles(),this.controllers.forEach((function(t){return t.activate()}))},e.prototype.deactivate=function(){this.controllers.forEach((function(t){return t.deactivate()})),this.removeStyles(),t.prototype.deactivate.call(this)},e.prototype.addStyles=function(){var t,e;try{for(var n=a(this.styles),o=n.next();!o.done;o=n.next()){var r=s(o.value,2)[1];this.molecule.node.appendChild(r)}}catch(e){t={error:e}}finally{try{o&&!o.done&&(e=n.return)&&e.call(n)}finally{if(t)throw t.error}}},e.prototype.removeStyles=function(){var t,e;try{for(var n=a(this.styles),o=n.next();!o.done;o=n.next()){var r=s(o.value,2)[1];this.molecule.node.removeChild(r)}}catch(e){t={error:e}}finally{try{o&&!o.done&&(e=n.return)&&e.call(n)}finally{if(t)throw t.error}}},e.prototype.replaceStyles=function(t){var e,n;this.removeStyles();try{for(var o=a(t),r=o.next();!r.done;r=o.next()){var i=r.value;this.styles.set(i.className,i)}}catch(t){e={error:t}}finally{try{r&&!r.done&&(n=o.return)&&n.call(o)}finally{if(e)throw e.error}}this.addStyles()},e.prototype.customEvents=function(){var t,e=this;(null===(t=this.molecule)||void 0===t?void 0:t.node)&&(this.registerEvent(this.molecule.node,u.EventType.KEYDOWN,(function(t){return t.keyCode===u.KeyCodes.SPACE&&(e.update(),t.preventDefault(),t.stopPropagation()),!1})),new u.CustomEvent(this.molecule.node,u.EventType.KEYDOWN,(function(t){return e.defaultExplorer&&!e.active&&t.keyCode===u.KeyCodes.ENTER&&(e.defaultExplorer.active=!0,e.activate(),t.preventDefault(),t.stopPropagation()),!1})).add(),new u.CustomEvent(this.molecule.node,u.EventType.CLICK,(function(t){return e.defaultExplorer&&!e.active&&(e.defaultExplorer.active=!0,e.activate(),t.stopPropagation()),!1})).add())},e.prototype.getDefaultExplorer=function(){this.defaultExplorer=this.controllers.find((function(t){return t.name===p.explorers.KEY})),this.defaultExplorer||(this.defaultExplorer=this.controllers.find((function(t){return t.name===p.explorers.MENU})))},e}(h.AbstractController);e.Container=g},88:(t,e)=>{Object.defineProperty(e,"__esModule",{value:!0}),e.Diagram=void 0;var n=function(){function t(t){this.languages=t,this.diagram_={}}return t.prototype.set=function(t,e){this.diagram_[t]=e},t.prototype.get=function(t){return this.diagram_[t]},t.prototype.forEach=function(t){for(var e in this.diagram_)t.call(this,this.diagram_[e])},t.prototype.find=function(t){for(var e in this.diagram_){var n=this.diagram_[e];if(t.call(this,n))return n}return null},t.prototype.translate=function(t){return this.languages?this.languages.translate(t):t},t.prototype.nextLanguage=function(){return this.languages?(this.languages.nextLanguage(),this.languages.active):null},t}();e.Diagram=n},465:(t,e,n)=>{Object.defineProperty(e,"__esModule",{value:!0}),e.Language=void 0;var o=n(422),r=function(){function t(t){this.cmlDocument=t,this.messages={},this.language=this.getLanguage(),this.insertMessages()}return t.prototype.getLanguage=function(){var t=this.cmlDocument.querySelector("language");return t?t.textContent:""},t.prototype.insertMessages=function(){for(var t=this.cmlDocument.querySelectorAll("message"),e=0,n=void 0;n=t[e];e++)this.messages[(0,o.getAttribute)(n,"msg")]=n.textContent},t.prototype.translate=function(t){return this.messages[t]},t}();e.Language=r},495:function(t,e,n){var o=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,n=e&&t[e],o=0;if(n)return n.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&o>=t.length&&(t=void 0),{value:t&&t[o++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.Languages=void 0;var r=n(807),i=n(465),c=function(){function t(t){this.cmlDocument=t,this.languages={},this.insertLanguages(),this.active=this.defaultActive()}return t.createLanguages=function(e){return 0===Array.from(e.querySelectorAll("messages")).filter((function(t){return t.childNodes.length})).length?null:new t(e)},t.prototype.insertLanguages=function(){var t,e,n=this.cmlDocument.querySelectorAll("messages");try{for(var r=o(Array.from(n)),c=r.next();!c.done;c=r.next()){var a=c.value,s=a.querySelector("language");s&&(this.languages[s.textContent]=new i.Language(a))}}catch(e){t={error:e}}finally{try{c&&!c.done&&(e=r.return)&&e.call(r)}finally{if(t)throw t.error}}},t.prototype.updateActive=function(){this.active=this.languages[r.config.ACTIVE_LANG]?r.config.ACTIVE_LANG:this.defaultActive()},t.prototype.defaultActive=function(){return this.languages[r.config.VOICE_LANG]?r.config.VOICE_LANG:Object.keys(this.languages)[0]},t.prototype.translate=function(t){return this.languages[this.active].translate(t)},t.prototype.nextLanguage=function(){var t=Object.keys(this.languages),e=t.indexOf(this.active)+1;this.active=t.length>e?t[e]:t[0],r.config.ACTIVE_LANG=this.active},t}();e.Languages=c},229:function(t,e,n){var o=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,n=e&&t[e],o=0;if(n)return n.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&o>=t.length&&(t=void 0),{value:t&&t[o++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},r=this&&this.__read||function(t,e){var n="function"==typeof Symbol&&t[Symbol.iterator];if(!n)return t;var o,r,i=n.call(t),c=[];try{for(;(void 0===e||e-- >0)&&!(o=i.next()).done;)c.push(o.value)}catch(t){r={error:t}}finally{try{o&&!o.done&&(n=i.return)&&n.call(i)}finally{if(r)throw r.error}}return c};Object.defineProperty(e,"__esModule",{value:!0}),e.ViewBox=e.ZoomOptions=void 0;var i=n(422),c=n(250),a=function(){};e.ZoomOptions=a;var s=function(){function t(t){this.svg=t,this.focusgroups=new Map,this.defaultZoomOptions={xPad:this.svg.width.baseVal.value/5,yPad:this.svg.height.baseVal.value/5,duration:100,delay:10,wait:0,callback:function(){}},this.reset()}return t.prototype.reset=function(){this.setViewBox_(c.Bbox.fromSvgRect(this.svg.getBBox()))},t.prototype.setClass=function(t,e){for(var n=0;n=t.length&&(t=void 0),{value:t&&t[o++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.Atom=void 0;var c=n(422),a=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.speech="",e.links=!1,e}return r(e,t),e.prototype.getSpeech=function(){if(this.link&&this.links)return this.speakLinks();var t=[];this.neighbourhood.forEach((function(e){"external"!==e.location&&t.push(e.getSpeech())}));var e=this.getMessage();return this.speech=this.getDiagram().translate(e)+" "+t.join(" "),this.speech},e.prototype.getDrawnElements=function(){if(this.drawnElements)return this.drawnElements;this.drawnElements=[];var t=this.getDrawnElement();t&&this.drawnElements.push(t);for(var e=this.getAnnotation().querySelectorAll("component *"),n=0;n=t.length&&(t=void 0),{value:t&&t[o++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.AtomSet=void 0;var c=n(422),a=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return r(e,t),e.prototype.getDrawnElements=function(){var t,e,n=this;if(this.drawnElements)return this.drawnElements;this.drawnElements=[];var o={},r=function(t){o[t.id]||(o[t.id]=!0,n.drawnElements.push(t))},a=this.getDrawnElement();a&&r(a);try{for(var s=i(this.getDown()),l=s.next();!l.done;l=s.next()){(h=l.value).getDrawnElements().forEach(r),(a=h.getDrawnElement())&&r(a)}}catch(e){t={error:e}}finally{try{l&&!l.done&&(e=s.return)&&e.call(s)}finally{if(t)throw t.error}}for(var u=this.getAnnotation().querySelectorAll("component bond,passive"),p=0;p atom,active"),e=0,n=void 0;n=t[e];e++){var o=this.getDiagram().get((0,i.getTextContent)(n));o&&this.ends.push(o)}},e}(n(41).NeighbourHood);e.Bond=c},903:function(t,e,n){var o=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,n=e&&t[e],o=0;if(n)return n.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&o>=t.length&&(t=void 0),{value:t&&t[o++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.ChemObj=void 0;var r=n(422),i=["circular"],c=function(){function t(t,e){var n,c;this.annotation_=t,this.diagram_=e,this.drawnElements=null,this.attributes=new Map,this.up=null,this.down=[],this.link=!1,this.drawn_=null,this.id_=this.getId_(),this.parent_=this.getParent_(),this.position=this.getPosition_();try{for(var a=o(i),s=a.next();!s.done;s=a.next()){var l=s.value,u=r.getAttribute(t,l);u&&this.attributes.set(l,u)}}catch(t){n={error:t}}finally{try{s&&!s.done&&(c=a.return)&&c.call(a)}finally{if(n)throw n.error}}}return t.prototype.getAnnotation=function(){return this.annotation_},t.prototype.getDiagram=function(){return this.diagram_},t.prototype.getId=function(){return this.id_},t.prototype.setDrawnElement=function(t){this.drawn_=t},t.prototype.getDrawnElement=function(){return this.drawn_},t.prototype.getDrawnElements=function(){return this.drawnElements||(this.drawnElements=[],this.drawn_&&this.drawnElements.push(this.drawn_)),this.drawnElements},t.prototype.complete=function(){if(this.down.length&&this.down.sort((function(t,e){return!t.position&&e.position?1:t.position-e.position})),this.parent_){var t=this.getDiagram().get(this.parent_);this.up=t,t.down.push(this)}},t.prototype.getParent=function(){return this.parent_},t.prototype.getKey=function(){return this.id_+this.parent_},t.prototype.getUp=function(){return this.up?[this.up]:[]},t.prototype.getDown=function(){return this.down},t.prototype.getRight=function(){if(!this.up)return[];var t=this.up.getDown(),e=t.indexOf(this);if(t.length>e+1){var n=t[e+1];return n.position?[n]:[]}return this.up.attributes.get("circular")?[t[0]]:[]},t.prototype.getLeft=function(){if(!this.up)return[];var t=this.up.getDown(),e=t.indexOf(this);if(e>0){var n=t[e-1];return n.position?[n]:[]}return this.up.attributes.get("circular")?[t[t.length-1]]:[]},t.prototype.getId_=function(){var t=this.getAnnotation().firstElementChild;return t?t.textContent:""},t.prototype.getParent_=function(){var t=this.getAnnotation().querySelector("parents > *");return t?r.getTextContent(t):""},t.prototype.getPosition_=function(){return parseInt(r.getTextContent(this.getAnnotation().querySelector("position")),10)},t}();e.ChemObj=c},183:function(t,e,n){var o=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,n=e&&t[e],o=0;if(n)return n.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&o>=t.length&&(t=void 0),{value:t&&t[o++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.CML=void 0;var r=n(88),i=n(495),c=n(839),a=n(111),s=n(202),l=function(){function t(t){this.cmlDocument=t,this.root=null,this.map=new r.Diagram(i.Languages.createLanguages(t)),this.initMap_()}return t.prototype.getMap=function(){return this.map},t.prototype.getRootElement=function(){return this.root||(this.root=this.map.find((function(t){return!(t instanceof s.Bond||t.up)}))),this.root},t.prototype.initMap_=function(){var t,e,n=Array.from(this.cmlDocument.querySelectorAll("annotations > annotation"));try{for(var r=o(n),i=r.next();!i.done;i=r.next()){var l=i.value,u=l.firstElementChild;if(u){var p=void 0;switch(u.localName.toLowerCase()){case"atom":case"active":p=new c.Atom(l,this.map),this.map.set(p.getKey(),p);break;case"bond":case"passive":p=new s.Bond(l,this.map),this.map.set(p.getId(),p);break;case"atomset":case"grouped":p=new a.AtomSet(l,this.map),this.map.set(p.getId(),p)}}}}catch(e){t={error:e}}finally{try{i&&!i.done&&(e=r.return)&&e.call(r)}finally{if(t)throw t.error}}this.map.forEach((function(t){return t.complete()}))},t}();e.CML=l},691:(t,e,n)=>{Object.defineProperty(e,"__esModule",{value:!0}),e.Molecule=void 0;var o=n(422),r=n(183),i=function(){function t(t,e,n){this.node=t,this.svg=e,this.cml=n,this.id=o.getId(),o.classList.add(this.svg,this.id),o.classList.add(this.cml,this.id),this.cmlStructure=new r.CML(this.cml),this.topLevelDescription=this.cmlStructure.getRootElement().getSpeech(),this.combine_()}return t.prototype.createElement=function(t){var e=document.createElement(t);return e.classList.add(this.id),e},t.prototype.combine_=function(){var t=this;this.cmlStructure.getMap().forEach((function(e){var n=t.svg.getElementById(e.getId());n&&e.setDrawnElement(n)}))},t}();e.Molecule=i},171:(t,e,n)=>{Object.defineProperty(e,"__esModule",{value:!0}),e.Neighbour=void 0;var o=n(422),r=n(122),i=function(){function t(t,e){var n=this;this.from_=t,this.annotation_=e,this.speech=null,this.speech2=null;var i=o.children(this.annotation_);this.to_=this.from_.getDiagram().get(o.getTextContent(i[0])),this.location=o.getAttribute(this.annotation_,o.attr.LOCATION),this.via_=i.slice(1).map((function(t){return new r.Via(n,t)}))}return t.prototype.from=function(){return this.from_},t.prototype.to=function(){return this.to_},t.prototype.via=function(){return this.via_},t.prototype.getSpeech=function(){return this.from_.getDiagram().translate(this.getMessage())},t.prototype.getMessage=function(){if(o.EXPERT_MODE){if(this.speech2)return this.speech2;if(this.speech2=o.getAttribute(this.annotation_,o.attr.EXPERT_SPEECH),this.speech2)return this.speech2}return this.speech||(this.speech=o.getAttribute(this.annotation_,o.attr.SIMPLE_SPEECH)),this.speech},t}();e.Neighbour=i},41:function(t,e,n){var o,r=this&&this.__extends||(o=function(t,e){return o=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n])},o(t,e)},function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function n(){this.constructor=t}o(t,e),t.prototype=null===e?Object.create(e):(n.prototype=e.prototype,new n)});Object.defineProperty(e,"__esModule",{value:!0}),e.NeighbourHood=void 0;var i=n(422),c=n(903),a=n(171),s=function(t){function e(e,n){var o=t.call(this,e,n)||this;return o.neighbourhood=[],o}return r(e,t),e.prototype.complete=function(){t.prototype.complete.call(this);for(var e=this.getAnnotation().querySelectorAll("neighbours neighbour"),n=0,o=void 0;o=e[n];n++)this.neighbourhood.push(new a.Neighbour(this,o))},e.prototype.getSpeech=function(){return this.getDiagram().translate(this.getMessage())},e.prototype.getMessage=function(){var t=i.getAttribute(this.getAnnotation(),i.attr.EXPERT_SPEECH),e=i.getAttribute(this.getAnnotation(),i.attr.SIMPLE_SPEECH);return i.EXPERT_MODE?t||e:e||t},e.prototype.getNeighbours=function(){return this.neighbourhood},e}(c.ChemObj);e.NeighbourHood=s},122:(t,e,n)=>{Object.defineProperty(e,"__esModule",{value:!0}),e.Via=void 0;var o=n(422),r=function(){function t(t,e){this.neighbour_=t,this.annotation_=e,this.type_=o.getAttribute(this.annotation_,o.attr.TYPE);var n=o.children(this.annotation_);this.connectedBy_=this.getConnectedBy_(n[0]),"Connecting bond"===this.type_&&"external"===this.neighbour_.location&&(this.neighbour_.from().link=!0),void 0!==this.connectedBy_&&(this.connectedBy_.link=!0),this.position_=parseInt(o.getTextContent(n[1]),10)}return t.prototype.getDrawnElements=function(){return this.connectedBy_.getDrawnElements()||[]},t.prototype.getType=function(){return this.type_||""},t.prototype.getPosition=function(){return this.position_},t.prototype.getConnectedBy_=function(t){var e=this.neighbour_.from().getDiagram(),n=o.getTextContent(t),r=e.get(n);return r||e.get(n+this.neighbour_.from().getId())},t}();e.Via=r},394:function(t,e,n){var o=this&&this.__values||function(t){var e="function"==typeof Symbol&&Symbol.iterator,n=e&&t[e],o=0;if(n)return n.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&o>=t.length&&(t=void 0),{value:t&&t[o++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")};Object.defineProperty(e,"__esModule",{value:!0}),e.Sonify=void 0;var r=n(422),i=function(){function t(t){this.path=[],this.points=[],this.context=new AudioContext,this.prepare(t),this.sonifiable=this.path.length}return t.sonify=function(e){var n=e.getAnnotation().querySelectorAll("sonification > *"),o=[];n.length&&(o=Array.from(n).map((function(t){var n=(0,r.getTextContent)(t),o=t.tagName.toLowerCase(),i=n+("active"!==o&&"sre:active"!==o?"":n===e.getId()?e.getParent():e.getId());return e.getDiagram().get(i)})));var i=new t(o);return i.sonifiable?i.sonify():[]},t.prototype.prepare=function(t){var e,n;try{for(var r=o(t),i=r.next();!i.done;i=r.next()){var c=i.value;if(c.getDrawnElement()){for(var a=c.getDrawnElement().querySelectorAll("polyline"),s=0;s20?(r=n/10+.01,i=n/10+.1):(r=n/2+.01,i=n/2+.5),o.start(this.context.currentTime+r),o.stop(this.context.currentTime+i)}return e},t.prototype.sonify=function(){var e,n,o=[],r=this.context.createOscillator();o.push(r),r.type="sine",r.connect(this.context.destination);for(var i=0;i20?(e=i/10+.01,n=i/10):(e=i/2+.01,n=i/2+.5),console.log(e),i?r.frequency.setValueAtTime(this.points[i][1]?t.BASE_FREQUENCY-this.points[i][1]:0,this.context.currentTime+n):(r.frequency.setValueAtTime(t.BASE_FREQUENCY-this.points[i][1],this.context.currentTime),r.start(this.context.currentTime));return r.stop(this.context.currentTime+n),o},t.prototype.smooth=function(){if(this.points.length){for(var e=[this.points[0]],n=0;n{var t=o;Object.defineProperty(t,"__esModule",{value:!0}),t.version=t.Config=t.Base=void 0;var e=n(807),r=n(23);t.Base=r.Base,t.Config=e.config,t.version=e.version})(),o})()})); \ No newline at end of file diff --git a/slides/_static/pretext/js/edit.js b/slides/_static/pretext/js/edit.js new file mode 100755 index 0000000..1d7a66a --- /dev/null +++ b/slides/_static/pretext/js/edit.js @@ -0,0 +1,4988 @@ + +editorLog = console.log; +// editorLog = function(){}; +debugLog = function(){}; +// debugLog = console.log; +//parseLog = function(){}; +parseLog = console.log; +errorLog = console.log; + +/* the structure of each object, and its realization as PreTeXt source or in HTML, + is recorded in the objectStructure dictionary. + +{"xml:id": new_id, "sourcetag": new_tag, "parent": parent_description, "title": ""} + +In pretext, pieces are of the form ["piecename", "tag"], while +in source, pieces are of the form ["piecename", "required_component"], while + +*/ + +editing_mode = 0; + +current_page = location.host+location.pathname; +debugLog("current_page", current_page); +chosen_edit_option_key = "edit_option".concat(current_page); +chosen_edit_option = readCookie(chosen_edit_option_key) || ""; +editing_mode = chosen_edit_option; /* delete one of those variables */ +debugLog("chosen_edit_option", chosen_edit_option, "chosen_edit_option", chosen_edit_option > 0); + + +objectStructure = { + "type": { + "html": { + "tag": "span", + "attributes": ['class="type"', 'data-parent_id="<&>xml:id<;>"', 'data-editable="70XX"', 'tabindex="-1"'], + "pieces": [["(capitalize,sourcetag)", ""]] + } + }, + "type-child": { + "html": { + "tag": "span", + "attributes": ['class="type"', 'data-editable="70YY"', 'tabindex="-1"'], + "pieces": [["(capitalize,sourcetag)", ""]] + // "pieces": [["(capitalize,type-contained)", ""]] + } + }, + "type-proof": { + "html": { + "tag": "span", + "attributes": ['class="type"', 'data-editable="70YY"', 'tabindex="-1"'], + "pieces": [["(literal,Proof)", ""]] + // "pieces": [["(capitalize,type-contained)", ""]] + } + }, + "sectiontype": { + "html": { + "tag": "span", + "attributes": ['class="type"'], // the type of a section is not editable? + "pieces": [["(capitalize,sourcetag)", ""]] + } + }, + "codenumber": { + "html": { + "tag": "span", + "attributes": ['class="codenumber"'], + "pieces": [["(codenumber,)", ""]] // maybe () was a bad idea? do we need a triple? + } + }, + "period": { + "html": { + "tag": "span", + "attributes": ['class="period"'], + "pieces": [["(period,)", ""]] + } + }, + "comma": { // used in bibliography + "html": { + "tag": "span", + "attributes": ['class="comma"'], + "pieces": [["(comma,)", ""]] + } + }, + "titleperiod": { + "html": { + "tag": "span", + "attributes": ['class="period"'], + "pieces": [["(titleperiod,)", ""]] + } + }, + "space": { + "html": { + "tag": "span", + "attributes": ['class="space"'], + "pieces": [["(space,)", ""]] + } + }, + "title": { + "html": { + "tag": "span", + "attributes": ['class="title"', 'data-editable="70"', 'tabindex="-1"'], + "pieces": [["title", ""]] + } + }, + "nbsp": { + "html": { + "tag": "", + "pieces": [["(literal, )", ""]], + }, + "pretext": { + "tag": "nbsp", + "pieces": [] + }, + "source": { + "pieces": [] + } + }, + "idx": { + "html": { + "tag": "", + "pieces": [], + }, + "pretext": { + "tag": "idx", + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", ""]] + } + }, + "h": { + "html": { + "tag": "", + "pieces": [], + }, + "pretext": { + "tag": "h", + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", ""]] + } + }, + + "theorem_like_heading": { + "html": { + "tag": "h4", + "cssclass": "heading", + "attributes": ['class="<&>{cssclass}<;>"', 'data-parent_id="<&>xml:id<;>"'], + "pieces": [["{type}", ""], ["{space}", ""], ["{codenumber}", ""], ["{period}", ""], ["{space}", ""], ["{title}", ""], ["{titleperiod}", ""]] + // * means editable piece + } + }, + "proof_like_heading": { + "html": { + "tag": "h5", + "cssclass": "heading", + "attributes": ['class="<&>{cssclass}<;>"', 'xml:id="<&>xml:id<;>"'], + "pieces": [["{type-child}", ""], ["{period}", ""]] + } + }, + "proof_heading": { + "html": { + "tag": "h5", + "cssclass": "heading", + "attributes": ['class="<&>{cssclass}<;>"', 'xml:id="<&>xml:id<;>"'], + "pieces": [["{type-proof}", ""], ["{period}", ""]] + } + }, + "section_like_heading": { + "html": { + "tag": "h2", + "cssclass": "heading hide-type", + "attributes": ['class="<&>{cssclass}<;>"', 'data-parent_id="<&>xml:id<;>"'], + "pieces": [["{sectiontype}", ""], ["{codenumber}", ""], ["{space}", ""], ["{title}", ""]] + } + }, + "title_heading": { + "html": { + "tag": "h2", + "cssclass": "heading", + "attributes": ['class="<&>{cssclass}<;>"', 'data-parent_id="<&>xml:id<;>"'], + "pieces": [["{title}", ""], ["{period}", ""]] + } + }, + "task_like_heading": { + "html": { + "tag": "h6", + "cssclass": "heading", + "attributes": ['class="<&>{cssclass}<;>"', 'data-parent_id="<&>xml:id<;>"'], + "pieces": [["{codenumber}", ""], ["{space}", ""], ["{title}", ""]] + } + }, + "caption_like_heading": { + "html": { + "tag": "h5", + "cssclass": "captionheading", + "attributes": ['class="<&>{cssclass}<;>"', 'data-parent_id="<&>xml:id<;>"'], + "pieces": [["{type}", ""], ["{space}", ""], ["{codenumber}", ""], ["{period}", ""], ["{space}", ""]] + } + }, + "xref": { + "html": { + "tag": "span", + "attributes": ['class="ref tmp"', 'id="<&>xml:id<;>"', 'ref="<&>ref<;>"'], + "pieces": [["(literal,REFERENCE)", ""]], + }, + "pretext": { + "tag": "xref", + "attributes": ['ref="<&>ref<;>"', 'text="<&>text<;>"'], + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", ""]], + "attributes": [["ref", "*"], ["text", ""],["detail",""],["first",""],["last",""]] + } + }, + "introduction": { + "html": { + "tag": "section", + "attributes": ['class="introduction"', 'id="<&>xml:id<;>"', 'data-editable="XYX"', 'tabindex="-1"'], + "pieces": [["content", ""]], + }, + "pretext": { + "tag": "introduction", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["title", "title"], ["content", ""]] + }, + "source": { + "pieces": [["title", "*"], ["content", "p"]] + } + }, + "conclusion": { + "html": { + "tag": "section", + "attributes": ['class="conclusion"', 'id="<&>xml:id<;>"', 'data-editable="XYX"', 'tabindex="-1"'], + "pieces": [["{proof_like_heading}", ""], ["content", ""]], + }, + "pretext": { + "tag": "conclusion", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["title", "title"], ["content", ""]] + }, + "source": { + "pieces": [["title", "*"], ["content", "p"]] + } + }, + + "worksheet": { + "html": { + "tag": "section", + "attributes": ['class="worksheet"', 'id="<&>xml:id<;>"', 'data-editable="XYX"', 'tabindex="-1"'], + "pieces": [["{section_like_heading}", ""], ["content", ""]], + }, + "pretext": { + "tag": "worksheet", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["title", "title"], ["content", ""]] + }, + "source": { + "pieces": [["title", "*"], ["content", "p"]] + } + }, + "section": { + "html": { + "tag": "section", + "attributes": ['class="section"', 'id="<&>xml:id<;>"', 'data-editable="XYX"', 'tabindex="-1"'], + "pieces": [["{section_like_heading}", ""], ["content", ""]], + }, + "pretext": { + "tag": "section", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["title", "title"], ["content", ""]] + }, + "source": { + "pieces": [["title", "*"], ["content", "p"]] + } + }, + "subsection": { + "html": { + "tag": "section", + "attributes": ['class="subsection"', 'id="<&>xml:id<;>"', 'data-editable="XYX"', 'tabindex="-1"'], + "pieces": [["{section_like_heading}", ""], ["content", ""]], + }, + "pretext": { + "tag": "subsection", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["title", "title"], ["content", ""]] + }, + "source": { + "pieces": [["title", "*"], ["content", "p"]] + } + }, + "paragraphs": { + "html": { + "tag": "section", + "attributes": ['class="paragraphs"', 'id="<&>xml:id<;>"', 'data-editable="XYX"', 'tabindex="-1"'], + "pieces": [["{title_heading}", ""], ["content", ""]], + }, + "pretext": { + "tag": "paragraphs", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["title", "title"], ["content", ""]] + }, + "source": { + "pieces": [["title", "*"], ["content", "p"]] + } + }, + "exercises": { + "html": { + "tag": "section", + "attributes": ['class="exercises"', 'id="<&>xml:id<;>"', 'data-editable="XYX"', 'tabindex="-1"'], + "pieces": [["{section_like_heading}", ""], ["content", ""]], + }, + "pretext": { + "tag": "exercises", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["title", "title"], ["content", ""]] + }, + "source": { + "pieces": [["title", "*"], ["content", "p"]] + } + }, + "exercisegroup": { + "html": { + "tag": "section", + "attributes": ['class="exercisegroup"', 'id="<&>xml:id<;>"', 'data-editable="XYX"', 'tabindex="-1"'], + "pieces": [["{section_like_heading}", ""], ["content", ""]], + }, + "pretext": { + "tag": "exercisegroup", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["title", "title"], ["content", ""]] + }, + "source": { + "pieces": [["title", "*"], ["content", "p"]] + } + }, + + + "page": { + "html": { + "tag": "section", + "attributes": ['class="onepage"', 'id="<&>xml:id<;>"', 'data-editable="PPP"', 'tabindex="-1"'], + "pieces": [["content", ""]], + }, + "pretext": { + "tag": "page", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", "p"]] + } + }, + "objectives": { + "html": { + "tag": "article", + "cssclass": "objectives", + "pieces": [["{proof_like_heading}", ""], ["content",""]], + "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"', 'class="<&>{cssclass}<;>"'], + "data_editable": "160" + }, + "pretext": { + "tag": "objectives", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", "p"]] + } + }, + + + + "p": { + "html": { + "tag": "p", + "pieces": [["content", ""]], + "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"'], + "data_editable": "99" + }, + "pretext": { + "tag": "p", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", ""]] + } + }, +/* probably there is a better way to do it, but we have separate elements for p + that occur in + text // ip + math + text // mp + math + text // mp + math + text // fp +*/ + "ip": { + "html": { + "tag": "p", + "pieces": [["content", ""]], + "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"'], + "data_editable": "99" + }, + "pretext": { + "tag_opening": "\n\n'], + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", ""]] + } + }, + "mp": { + "html": { + "tag": "p", + "pieces": [["content", ""]], + "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"', 'class="mp"'], + "data_editable": "99" + }, + "pretext": { + "tag_opening": "", + "tag_closing": "", + "attributes": [], + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", ""]] + } + }, + "fp": { + "html": { + "tag": "p", + "pieces": [["content", ""]], + "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"', 'class="fp"'], + "data_editable": "99" + }, + "pretext": { + "tag_opening": "", + "tag_closing": "\n

    \n", + "attributes": [], + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", ""]] + } + }, + + "li": { + "html": { + "tag": "li", + "pieces": [["content", ""]], + "attributes": ['id="<&>xml:id<;>"'], + "data_editable": "98aZ" + }, + "pretext": { + "tag": "li", + "pieces": [["title", "title"], ["content", ""]] + }, + "source": { + "pieces": [["title", ""], ["content", "p"]] + } + }, + + "blockquote": { + "html": { + "tag": "blockquote", + "pieces": [["content", ""], ["attribution", ""]], + "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"', 'class="<&>{cssclass}<;>"'], + "cssclass": "blockquote", + "data_editable": "44?" + }, + "pretext": { + "tag": "blockquote", + "pieces": [["content", ""], ["attribution", "attribution"]], + }, + "source": { + "pieces": [["content", "p"], ["attribution", ""]] // attribution can contain a "line". come back to that + } + }, + + "list": { + "html": { + "tag": "ol", + "pieces": [["content", ""]], + "attributes": ['id="<&>xml:id<;>"', 'list-style-type="a"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"'], + "data_editable": "AAA" + }, + "pretext": { + "tag": "ol", + "pieces": [["content", ""]], + "attributes": ['label="A"'] + }, + "source": { + "pieces": [["title", ""], ["content", "li"]], + "attributes": [["label", "A"]] + } + }, + + "figure": { + "html": { + "tag": "figure", + "pieces": [["content",""],["{caption}", ""]], + "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"', 'class="<&>{cssclass}<;>"'], + "data_editable": "32", + "cssclass": "figure figure-like" + }, + "pretext": { + "tag": "figure", + "pieces": [["content"], ["{caption}"]], + "attributes": ['xml:id="<&>xml:id<;>"'] + }, + "source": { + "pieces": [["content", ""], ["captiontext", ""]], + "attributes": [] + } + }, + + "image": { + "html": { + "tag": "div", + "pieces": [["{bareimage}",""]], + "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"', 'class="<&>{cssclass}<;>"', 'style="width: <&>width<;>%; margin-right: <&>marginright<;>%; margin-left: <&>marginleft<;>%"'], + "data_editable": "31", + "cssclass": "image-box" + }, + "pretext": { + "tag": "image", + "pieces": [], + "attributes": ['xml:id="<&>xml:id<;>"', 'source="<&>source<;>"', 'alt="<&>alt<;>"', 'width="<&>width<;>%"', 'margins="<&>marginleft<;>% <&>marginright<;>%"'] + }, + "source": { + "pieces": [["",""]], + "attributes": [["source", ""], ["width", "40"], ["marginleft", "20"], ["marginright", "40"], ["alt", ""]] + } + }, + + "bareimage": { + "html": { + "tag": "img", + "pieces": [], + "attributes": ['src="<&>source<;>"', 'alt="<&>alt<;>"', 'class="contained"'], + } + }, + + "sidebyside": { + "html": { + "tag": "div", + "pieces": [["{sbsrow}",""]], + "attributes": ['id="<&>xml:id<;>"', 'class="<&>{cssclass}<;>"'], + "cssclass": "sidebyside" + }, + "pretext": { + "tag": "sidebyside", + "pieces": [["content", ""]], + "attributes": ['xml:id="<&>xml:id<;>"', 'margins="<&>marginleft<;>% <&>marginright<;>%"', 'width="<&>(percentlist,widths)<;>"'], + }, + "source": { + "pieces": [["content",""]], + "attributes": [["marginleft", ""], ["marginright", ""], ["widths", ""]] + } + }, + "sbsrow": { + "html": { + "tag": "div", + "pieces": [["content",""]], // ???? + "attributes": ['class="<&>{cssclass}<;>"', 'style="margin-right: <&>marginright<;>%; margin-left: <&>marginleft<;>%"'], + "data_editable": "89", + "cssclass": "sbsrow" + }, + "pretext": { + "tag": "", + "pieces": [["content",""]], + "attributes": [] + }, + "source": { + "pieces": [["content",""]], + "attributes": [["marginleft", "20"], ["marginright", "40"]] + } + }, + "sbspanel": { + "html": { + "tag": "div", + "pieces": [["content",""]], + "attributes": ['id="<&>xml:id<;>"', 'class="<&>{cssclass}<;>"', 'style="width:<&>(nthitem,widths)<;>%"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"'], + "data_editable": "90", + "cssclass": "sbspanel" + }, + "pretext": { + "tag": "stack", + "pieces": [["content", ""]], + "attributes": ['xml:id="<&>xml:id<;>"'] + }, + "source": { + "pieces": [["content",""]] + } + }, + + "proof": { + "html": { + "tag": "article", + "cssclass": "proof", + "pieces": [["{proof_heading}", ""], ["proof",""]], + "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"', 'class="<&>{cssclass}<;>"'], + "data_editable": "60" + }, + "pretext": { + "tag": "proof", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["proof", ""]] + }, + "source": { + "pieces": [["content", "p"]] + } + }, + "proof-standalone": { + "html": { + "tag": "article", + "cssclass": "proof", + "pieces": [["{XXXXXnorbeingusedXXXXXXX_proof_heading}", ""], ["content",""]], + "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"', 'class="<&>{cssclass}<;>"'], + "data_editable": "60" + }, + "pretext": { + "tag": "proof", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", "p"]] + } + }, + + "caption": { + "html": { + "tag": "figcaption", + "data_editable": "321", + // "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"'], + "attributes": ['id="<&>xml:id<;>"'], + "pieces": [["{caption_like_heading}", ""], ["{captiontext}",""]] + // "pieces": [["(literal, caption goes here)",""]] + }, + "pretext": { + "tag": "caption", + "attributes": [], + "pieces": [["captiontext", ""]] + }, + "source": { + "pieces": [["content", ""]] + } + }, + "captiontext": { /* not used */ + "html": { + "tag": "p", + "data_editable": "321", + // "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"'], +// do it like editabe title? + "attributes": ['data-source_id="<&>xml:id<;>"', 'data-component="caption"', 'class="caption"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"'], + "pieces": [["captiontext",""]] + }, + "pretext": { + "tag": "", + "attributes": [], + "pieces": [["captiontext", ""]] + }, + "source": { + "pieces": [["captiontext", ""]] + } + }, + + "references": { + "html": { + "tag": "article", + "cssclass": "bib", + "data_editable": "33", + "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"', 'class="<&>{cssclass}<;>"'], + "pieces": [["{section_like_heading}", ""], ["content",""]] + }, + "pretext": { + "tag": "references", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["title", ""], ["content",""]] + }, + "source": { + "pieces": [["title", ""], ["content",""]] + } + }, + +/* biblio, the items directly following it, and the refrences above, + are not usable at this time. references have to be handled in + a text editor. 3 Dec 2021 +*/ + "biblio": { + "html": { + "tag": "div", + "cssclass": "bibentry", + "data_editable": "333", + "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"', 'class="<&>{cssclass}<;>"'], + "pieces": [["{bib-title}", ""], ["{journal}",""], ["{volume}",""], ["{number}",""], ["{author}",""], ["{pages}",""]] + }, + "pretext": { + "tag": "biblio", + "attributes": ['xml:id="<&>xml:id<;>"', 'type="raw"'], + "pieces": [["title", ""], ["journal",""], ["volume",""], ["number",""], ["author",""], ["pages",""]] + }, + "source": { + "pieces": [["title", ""], ["journal",""], ["volume",""], ["number",""], ["author",""], ["pages",""]] + } + }, + + "bib-title": { + "html": { + "tag": "i", + "pieces": [["title", "zz"]] + } + }, + "author": { + "html": { + "tag": "", + "pieces": [["author", ""], ["{comma}", ""]] + } + }, + "journal": { + "html": { + "tag": "", + "pieces": [["journal", ""]] + } + }, + "volume": { + "html": { + "tag": "b", + "pieces": [["volume", ""]] + } + }, + "pages": { + "html": { + "tag": "jjj", + "pieces": [["pages", ""]] + } + }, + "number": { + "html": { + "tag_opening": "no. ", + "tag_closing": " ", + "pieces": [["number", ""]] + } + }, + + "remark-like": { + "html": { + "tag": "article", + "cssclass": "remark-like", + "data_editable": "92", + "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"', 'class="<&>{cssclass}<;>"'], + "pieces": [["{theorem_like_heading}", ""], ["statement",""]] + }, + "pretext": { + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["title", "title"], ["statement", "statement"]] + }, + "source": { + "pieces": [["title", ""], ["statement", "p"]] + } + }, + + "project-like-tasks": { + "html": { + "tag": "article", + "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"', 'class="<&>{cssclass}<;>"'], + "cssclass": "project-like", + "data_editable": "194", + "pieces": [["{theorem_like_heading}", ""], ["content",""], ["tasks", ""]] + }, + "pretext": { + "tag": "sourcetag", + "pieces": [["title", "title"], ["content", ""], ["tasks", ""]], + "attributes": ['xml:id="<&>xml:id<;>"'] + }, + "source": { + "pieces": [["title", ""], ["content", "p"], ["tasks", ""]] + // "attributes": [["workspace", "0"]] + } + }, + "project-like": { + "html": { + "tag": "article", + "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"', 'class="<&>{cssclass}<;>"'], + "cssclass": "project-like", + "data_editable": "94", + "pieces": [["{theorem_like_heading}", ""], ["statement",""], ["%hint%", "hint"], ["%answer%", "answer"], ["%solution%", "solution"], ["{workspace}", ""]] + }, + "pretext": { + "tag": "sourcetag", + "pieces": [["title", "title"], ["statement", "statement"], ["hint", "hint"], ["answer", "answer"], ["solution", "solution"]], + "attributes": ['xml:id="<&>xml:id<;>"', 'workspace="<&>workspace<;>"'] + }, + "source": { + "pieces": [["title", ""], ["statement", "p"], ["hint", ""], ["answer", ""], ["solution", ""]], + "attributes": [["workspace", "0"]] + } + }, + + "definition-like": { + "html": { + "tag": "article", + "cssclass": "definition-like", + "data_editable": "95a", + "pieces": [["{theorem_like_heading}", ""], ["statement", ""]], + "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"', 'class="<&>{cssclass}<;>"'] + }, + "pretext": { + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["title", "title"], ["statement", "statement"]] + }, + "source": { + "pieces": [["title", ""], ["statement", "p"]] + } + }, + + "theorem-like": { + "html": { + "tag": "article", + "cssclass": "theorem-like", + "data_editable": "93", + "pieces": [["{theorem_like_heading}", ""], ["statement", ""], ["{proof}", ""]], + "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"', 'class="<&>{cssclass}<;>"'] + }, + "pretext": { + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["title", "title"], ["statement", "statement"], ["{proof}", ""]] + }, + "source": { + "pieces": [["title", ""], ["statement", "p"], ["proof", ""]] + } + }, + + "task": { + "html": { + "tag": "article", + "cssclass": "exercise-like task", + "data_editable": "94ZZZA", + "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"', 'class="<&>{cssclass}<;>"'], + "pieces": [["{task_like_heading}", ""], ["statement",""], ["%hint%", "hint"], ["%answer%", "answer"], ["%solution%", "solution"], ["{workspace}", ""]] + }, + "pretext": { + "tag": "task", + "pieces": [["title", "title"], ["statement", "statement"], ["hint", ""], ["answer", ""], ["solution", ""]], + "attributes": ['workspace="<&>workspace<;>"'] + }, + "source": { + "pieces": [["title", ""], ["statement", "p"], ["hint", ""], ["answer", ""], ["solution", ""]], + "attributes": [["workspace", "0"]] + } + }, + +/* consolidate H/A/S */ + "hint": { + "html": { + "tag": "article", + "cssclass": "solution-like hint", + "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"', 'class="<&>{cssclass}<;>"'], + "pieces": [["{proof_like_heading}", ""], ["content",""]], +// go back and make the lack of data_editable automatically make the contained p editable +/* "data_editable": "454", */ + "data_editable": "hhhh" + }, + "pretext": { + "tag": "hint", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["title", "title"], ["content", ""]] + }, + "source": { + "pieces": [["content", "p"]] + } + }, + "answer": { + "html": { + "tag": "article", + "cssclass": "solution-like answer", + "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"', 'class="<&>{cssclass}<;>"'], + "pieces": [["{proof_like_heading}", ""], ["content",""]], +// go back and make the lack of data_editable automatically make the contained p editable +/* "data_editable": "454", */ + "data_editable": "hhhh" + }, + "pretext": { + "tag": "answer", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["title", "title"], ["content", ""]] + }, + "source": { + "pieces": [["content", "p"]] + } + }, + "solution": { + "html": { + "tag": "article", + "cssclass": "solution-like solution", + "attributes": ['id="<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"', 'class="<&>{cssclass}<;>"'], + "pieces": [["{proof_like_heading}", ""], ["content",""]], +// go back and make the lack of data_editable automatically make the contained p editable +/* "data_editable": "454", */ + "data_editable": "hhhh" + }, + "pretext": { + "tag": "solution", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["title", "title"], ["content", ""]] + }, + "source": { + "pieces": [["content", "p"]] + } + }, + + "workspace": { + "html": { + "tag": "div", + "cssclass": "workspace", + "data_editable": "WwW", + "attributes": ['data-parent_id="<&>xml:id<;>"', 'data-space="<&>workspace<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"', 'class="<&>{cssclass}<;>"'], + "pieces": [] + } + }, + + "term": { // need to mark it as inline + "html": { + "tag": "dfn", + "attributes": ['id="<&>xml:id<;>"', 'class="terminology"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"'], + "data_editable": "ttt", + "pieces": [["content", ""]] + }, + "pretext": { + "tag": "term", + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", ""]] + } + }, + + "em": { // need to mark it as inline + "html": { + "tag": "em", + "attributes": ['id="<&>xml:id<;>"', 'class="emphasis"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"'], + "data_editable": "eemm", + "pieces": [["content", ""]] + }, + "pretext": { + "tag": "em", + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", ""]] + } + }, + "fn": { // need to mark it as inline + "html": { + "tag": "a", + "attributes": ['id="<&>xml:id<;>"', 'class="id-ref fn-knowl original"', 'data-knowl=" "', 'href=" "', 'data-refid="hk-<&>xml:id<;>"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"'], + "data_editable": "fnfnfn", +// "pieces": [["content", "sup"]] + "pieces": [["(literal,footnote)", "sup"]] + }, + "pretext": { + "tag": "fn", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", ""]] + } + }, + "alert": { // need to mark it as inline + "html": { + "tag": "em", + "attributes": ['id="<&>xml:id<;>"', 'class="alert"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"'], + "data_editable": "aall", + "pieces": [["content", ""]] + }, + "pretext": { + "tag": "alert", + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", ""]] + } + }, + "init": { // need to mark it as inline + "html": { + "tag": "abbr", + "attributes": ['id="<&>xml:id<;>"', 'class="initialism"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"'], + "data_editable": "iinniitt", + "pieces": [["content", ""]] + }, + "pretext": { + "tag": "init", + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", ""]] + } + }, + "c": { // need to mark it as inline + "html": { + "tag": "code", + "attributes": ['id="<&>xml:id<;>"', 'class="code-inline tex2jax_ignore"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"'], + "data_editable": "ccoo", + "pieces": [["content", ""]] + }, + "pretext": { + "tag": "c", + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", ""]] + } + }, + + "ellipsis": { // need to mark it as inline + "html": { + "tag": "span", + "attributes": ['id="<&>xml:id<;>"', 'class="abbrev"', 'contenteditable="false"'], + "data_editable": "abbr", + "pieces": [["(literal,…)", ""]] + }, + "pretext": { + "tag": "ellipsis", + "pieces": [] + }, + "source": { + } + }, + "etc": { // need to mark it as inline + "html": { + "tag": "span", + "attributes": ['id="<&>xml:id<;>"', 'class="abbrev"', 'contenteditable="false"'], + "pieces": [["(literal,etc)", ""]] + }, + "pretext": { + "tag": "etc", + "pieces": [] + }, + "source": { + } + }, + + "q": { // need to mark it as inline + "html": { + "tag": "q", + "attributes": ['id="<&>xml:id<;>"', 'class="quote"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"'], + "data_editable": "qqq", + "pieces": [["content", ""]] + }, + "pretext": { + "tag": "q", + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", ""]] + } + }, + + "m": { // need to mark it as inline + "html": { + "tag_opening": "\\(", + "tag_closing": "\\)", + // "tag": "script", + // "attributes": ['type="math/tex"'], + "pieces": [["content", ""]] + }, + "pretext": { + "tag": "m", + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", ""]] + } + }, + + "me": { + "html": { + "tag": "div", + "attributes": ['id="<&>xml:id<;>"', 'class="displaymath process-math"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"'], + "data_editable": "42", + "pieces": [["{me_raw}", ""]] + }, + "pretext": { + "tag": "me", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", ""]] + } + }, + "men": { + "html": { + "tag": "div", + "attributes": ['id="<&>xml:id<;>"', 'class="displaymath process-math"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"'], + "data_editable": "42", + "pieces": [["{me_raw}", ""]] + }, + "pretext": { + "tag": "men", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", ""]] + } + }, + "md": { + "html": { + "tag": "div", + "attributes": ['id="<&>xml:id<;>"', 'class="displaymath process-math"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"'], + "data_editable": "42", + "pieces": [["{md_raw}", ""]] + }, + "pretext": { + "tag": "md", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", ""]] + } + }, + "mdn": { + "html": { + "tag": "div", + "attributes": ['id="<&>xml:id<;>"', 'class="displaymath process-math"', 'data-editable="<&>{data_editable}<;>"', 'tabindex="-1"'], + "data_editable": "42", + "pieces": [["{me_raw}", ""]] + }, + "pretext": { + "tag": "mdn", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", ""]] + } + }, + "me_raw": { + "html": { + "tag_opening": "\\begin{equation*}", + "tag_closing": "\\end{equation*}", + "pieces": [["content", ""]] + } + }, + "md_raw": { + "html": { + "tag_opening": "\\begin{align*}", + "tag_closing": "\\end{align*}", + "pieces": [["content", ""]] + } + }, +// check if mrow is actually used + "mrow": { + "html": { + "tag_opening": "", + "tag_closing": "\\cr", + "data_editable": "422", + "pieces": [["content", ""]] + }, + "pretext": { + "tag": "mrow", + "attributes": ['xml:id="<&>xml:id<;>"'], + "pieces": [["content", ""]] + }, + "source": { + "pieces": [["content", ""]] + } + } + +} + +var environment_instances = { + "definition-like": ["definition", "conjecture", "axiom", "principle", "heuristic", "hypothesis", "assumption"], + "theorem-like": ["lemma", "proposition", "theorem", "corollary", "claim", "fact", "identity", "algorithm"], + "remark-like": ["remark", "warning", "note", "observation", "convention", "insight"], + "project-like-tasks": ["investigation", "exploration", "project"], + "project-like": ["exercise", "activity"] +} + +for (const [owner, instances] of Object.entries(environment_instances)) { + var data_editable_base = objectStructure[owner].html.data_editable; + var cssclass_base = objectStructure[owner].html.cssclass; + var source_pieces = objectStructure[owner].source.pieces; + var pretext_pieces = objectStructure[owner].pretext.pieces; + var pretext_attributes = (objectStructure[owner].pretext.attributes || []); + var source_attributes = (objectStructure[owner].source.attributes || []); + for (var j=0; j < instances.length; ++j) { + var this_tag = instances[j]; + objectStructure[this_tag] = { + "owner": owner, + "html": { + "tag": objectStructure[owner].html.tag, + "pieces": objectStructure[owner].html.pieces, + "attributes": objectStructure[owner].html.attributes, + "cssclass": cssclass_base + " " + this_tag, + "data_editable": data_editable_base + j.toString(), + "heading": objectStructure[owner].html.heading + }, + "pretext": { + "tag": this_tag, + "pieces": pretext_pieces, + "attributes": pretext_attributes + }, + "source": { + "tag": this_tag, + "pieces": source_pieces, + "attributes": source_attributes + } + } + } +} + +editorLog('objectStructure["exercise"]', objectStructure["exercise"]); + +var sidebyside_instances = { +"sbs": [["2 panels", "sbs2"], ["3 panels", "sbs3"], ["4 panels", "sbs4"]], +//"sbs2": [["full across XX", "sbs2_0_50_50_0"], ["gap but no margin", "sbs2_0_40_40_0"], ["spaced equally", "sbs2_5_40_40_5"]], +"sbs2": [["full across", "sbs_0_50_50_0"], ["gap but no margin", "sbs_0_45_45_0"], ["spaced equally", "sbs_5_40_40_5"]], +"sbs3": [["full across", "sbs_0_34_32_34_0"], ["gap but no margin", "sbs_0_28_28_38_0"], ["spaced equally", "sbs_5_25_26_25_5"]], +"sbs4": [["full across", "sbs_0_25_25_25_25_0"], ["gap but no margin", "sbs_0_20_20_20_20_0"], ["spaced equally", "sbs_3_18_18_18_18_3"]] +} + +Object.assign(objectStructure, sidebyside_instances); + +// shoudl we distinguish empty tags by format? +var always_empty_tags = ["img", "image", "xref"]; +// eventially xref will move to allowed_empty_tags +var allowed_empty_tags = ["div", "span", "p", "stack"]; +var tag_display = { /* the default is "block" */ + "inline": ["m", "em", "ellipsis", "span", "term", "dfn", "q", "c", "code", "alert", "nbsp", "xref", "idx", "h", "init"], + "title": ["title", "idx", "h1", "h2", "h3", "h4", "h5", "h6", "div", "usage", "image"], + "block-tight": [] // ["mp", "mrow"] +} + +inline_tags = tag_display["inline"]; +inline_math = ["m"]; +inline_abbrev = ["ellipsis", "ie", "eg", "etc"]; + +// contained_objects are a componnet of another object, not just buried in content +var contained_objects =["title", "statement", "caption", "captiontext", "proof", + "author", "journal", "volume", "number", "pages"]; + +function tag_type(tag) { + var this_type; + + if (["p", "ip", "mp", "fp"].includes(tag)) { this_type = "p" } + else if (["me", "men", "md", "mdn"].includes(tag)) { this_type = "md" } + else { this_type = tag } + + return this_type +} + +function process_value_from_source(fcn, piece, src) { + + editorLog(fcn, "process_value_from_source", piece, "in", src); + var content_raw = ""; + if (["literal", "codenumber", "period", "titleperiod", "space"].includes(fcn)) { + content_raw = piece; + } else { + if (piece in src) { + content_raw = src[piece] + } else { + if ("parent" in src) { + var parent_src = internalSource[src["parent"][0]]; + if (piece in parent_src) { + content_raw = parent_src[piece] + } else { + errorLog("Error: piece", piece, "fcn", fcn, "from", parent_src, "not in src or parent_src") + } + } else { + errorLog("at the top, or else there is an error") + } + } + } /* fcn != literal */ + +/* + if (typeof myVar == 'string') { + content_raw = content_raw.replace(/-standalone$/, "") // hack because we need alternate to sourcetag + } +*/ + if (fcn == "capitalize") { + content = content_raw.charAt(0).toUpperCase() + content_raw.slice(1); + } else if (fcn == "literal") { + editorLog("literally", piece); + // alert(piece); + content = piece + } else if (fcn == "space") { + content = content_raw + " " + } else if (fcn == "period") { + content = content_raw + "." + } else if (fcn == "comma") { + content = content_raw + "," + } else if (fcn == "titleperiod") { + if (src.title && !(/[!?]$/.test(src.title))) { + content = content_raw + "." + } + } else if (fcn == "percentlist") { + if (content_raw) { + content = content_raw.join("% ") + "%" + } else { + content = "MISSING" + "%" + } + } else if (fcn == "nthitem") { + + editorLog("calculating nthitem", piece, "from ", content_raw, "within", src); + var item_index = 0; // need to be its location among siblings + content = content_raw[item_index]; + } else if (fcn == "codenumber") { + editorLog(" CODENUMBER", src); + content = internalSource.root_data.number_base; + if (src["xml:id"] == top_level_id) { + // no need to add locan number + } else { + content += ".N" + } + } else { + content = content_raw + } + + return content +} + +$(".autopermalink > a").attr("tabindex", -1); + +var editable_objects = [["p", 99], ["ol", 97], ["ul", 96], ["article", 95], ["blockquote", 80], ["section", 66], + ["title", 20], ["caption", 890]]; +for(var j=0; j < editable_objects.length; ++j) { + $(editable_objects[j][0]).attr("data-editable", editable_objects[j][1]); + $(editable_objects[j][0]).attr("tabindex", -1); +} + +/* the code */ + +editorLog(" enabling edit"); + +user_level = "novice"; + +// we have to keep track of multiple consecutive carriage returns +this_char = ""; +prev_char = ""; +prev_prev_char = ""; + +// for resizing and layout changes +// clobal variables so we can adjust them dynamically +move_scale = 1; +magnify_scale = 1; + +final_added_object = ""; +previous_added_object = ""; + +// sometimes we have to prevent Tab from changing focus +this_focused_element = ""; +prev_focused_element = ""; +prev_prev_focused_element = ""; + +var menu_neutral_background = "#ddb"; +var menu_active_background = "#fdd"; + +var recent_editing_actions = []; // we unshift to this, so most recent edit is first. + // currently just a human-readable list +var ongoing_editing_actions = []; +var old_content = {}; // to hold old versions of changed materials + +// what will happen with internationalization? +var keyletters = ["KeyA", "KeyB", "KeyC", "KeyD", "KeyE", "KeyF", "KeyG", "KeyH", "KeyI", "KeyJ", "KeyK", "KeyL", "KeyM", "KeyN", "KeyO", "KeyP", "KeyQ", "KeyR", "KeyS", "KeyT", "KeyU", "KeyV", "KeyW", "KeyX", "KeyY", "KeyZ"]; + +var movement_location_options = []; +var movement_location = 0; +var first_move = true; // used when starting to move, because object no longer occupies its original location + +Storage.prototype.setObject = function(key, value) { +// this.setItem(key, JSON.stringify(value)); + this.setItem(key, JSON.stringify(value, function(key, val) { +// console.log("key", key, "value", value, "val", val); + return val.toFixed ? Number(val.toFixed(3)) : val; +})); +} + +Storage.prototype.getObject = function(key) { + var value = this.getItem(key); + return value && JSON.parse(value); +} + +function randomstring(len) { + if (!len) { len = 10 } + return "tMP" + (Math.random() + 1).toString(36).substring(2,len) +} + +function removeItemFromList(lis, value) { + var index = lis.indexOf(value); + if (index > -1) { + lis.splice(index, 1); + } + return lis; +} + +function rescale(width, max, margin_left, margin_right) { + var available_width = max - parseFloat(margin_left) - parseFloat(margin_right); + return width * 100 / available_width +} + +function spacemath_to_tex(text) { + thetext = text; + + thetext = thetext.replace(/ d([a-zA-Z])(\s|$)/, " \\,d$1$2"); + thetext = thetext.replace(/ *< */, " < "); + + return thetext +} + +function split_paragraphs(paragraph_content) { + + // does the textbox contain more than one paragraph? + var paragraph_content_list = paragraph_content.split("

    "); + editorLog("there were", paragraph_content_list.length, "paragraphs, but some may be empty"); + for (var j=0; j < paragraph_content_list.length; ++j) { + editorLog("paragraph", j, "begins", paragraph_content_list[j].substring(0,20)) + } + + var paragraph_content_list_trimmed = []; + + for (var j=0; j < paragraph_content_list.length; ++j) { + // probably each paragraph is wrapped in meaningless div tags + var this_paragraph_contents_raw = paragraph_content_list[j]; + this_paragraph_contents_raw = this_paragraph_contents_raw.replace(/<\/div>
    /g, "\n"); + this_paragraph_contents_raw = this_paragraph_contents_raw.replace(/
    /g, ""); + this_paragraph_contents_raw = this_paragraph_contents_raw.replace(/<\/div>/g, ""); + this_paragraph_contents_raw = this_paragraph_contents_raw.replace(/ /g, " "); + this_paragraph_contents_raw = this_paragraph_contents_raw.replace(/ +
    /g, "\n"); + this_paragraph_contents_raw = this_paragraph_contents_raw.replace(/
    /g, "\n"); + this_paragraph_contents_raw = this_paragraph_contents_raw.trim(); + if (!this_paragraph_contents_raw) { editorLog("empty paragraph") } + else { paragraph_content_list_trimmed.push(this_paragraph_contents_raw) } + editorLog("this_paragraph_contents_raw", this_paragraph_contents_raw); + } + + if (!paragraph_content_list_trimmed.length ) { + // empty, so insert it and delete it later + paragraph_content_list_trimmed = [""]; + } + + return paragraph_content_list_trimmed +} + +var submenu_options = { // revise as these are handled previously +"math-like": [["me"], ["chem"], ["code"]], +"image-like": [["image"], ["video"], ["audio"]], +"aside-like": [["aside"], ["historical"], ["biographical"]], +"list-like": [["itemized list", "list"], ["dictionary list", "dl"], ["table"]], +"layout-like": [["side-by-side panels", "sbs"], ["assemblage"], ["biographical aside"], ["titled paragraph", "paragraphs"]], +"section-like": ["section", "subsection", "paragraphs", "rq", "exercises"], +"sbs": [["2 panels", "sbs2"], ["3 panels", "sbs3"], ["4 panels", "sbs4"]], +"list-like": [["itemized list", "list"], ["dictionary list", "dl"], ["table"]], +"example-like": ["example", "question", "problem"] +} + +Object.assign(submenu_options, sidebyside_instances); + +Object.assign(submenu_options, environment_instances); // wrong because string not list + // or not wrong, if the code is more clever + +/* need to distinguish between the list of objects of a type, + and the list of types that can go in a location. + OR, is it okay that these are all in one list? + It seems to not be okay, because the "blockquote" entry + says that only a "p" can go in a blockquote. But blockquote + is an entry under "quoted". +*/ +base_menu_for = { +"section": [["paragraph", "p"], + ["display math/chemistry/code", "math-like", "c"], + ["list or table", "list-like"], + ["example-like", "example-like"], + ["definition-like", "definition-like"], + ["theorem-like", "theorem-like"], + ["remark-like"], + ["project/exercise-like", "project-like", "j"], + ["image/video/sound", "image-like", "v"], + ["blockquote/poem/music/etc", "quoted"], + ["aside-like", "aside-like", "d"], + ["interactives"], + ["proof", "proof-standalone", "o"], + ["layout-like"], + ["section-like"], + // ["Save", "save"], + ["PreTeXt source", "source"]], + // need to unify section with paragraphs (when sections are managed by the CAT) +"paragraphs": [["paragraph", "p"], + ["display math/chemistry/code", "math-like", "c"], + ["list or table", "list-like"], + ["example-like", "example-like"], + ["definition-like", "definition-like"], + ["theorem-like", "theorem-like"], + ["remark-like"], + ["project/exercise-like", "project-like", "j"], + ["image/video/sound", "image-like", "v"], + ["blockquote/poem/music/etc", "quoted"], + ["aside-like", "aside-like", "d"], + ["interactives"], + ["proof", "proof-standalone", "o"], + ["layout-like"], + // ["Save", "save"], + ["PreTeXt source", "source"]], +"blockquote": [["paragraph", "p"]], +// "ol": [["list item", "li"]], +"article": [["paragraph", "p"], // this is for theorem-like and similar + ["math/chemistry/code", "math-like", "c"], + ["list or table", "list-like"], + ["image/video/sound", "image-like", "v"]], +"li": [["new list item", "li", "i"], + ["paragraph", "p"], + ["list or table", "list-like"], + ["math/chemistry/code", "math-like", "c"], + ["image/video/sound", "image-like", "v"]], +"p": [["emphasis-like"], ["formula"], ["abbreviation"], ["symbol"], ["ref or link", "ref"]] +} +base_menu_for["proof"] = base_menu_for["article"]; +base_menu_for["page"] = base_menu_for["section"]; + + +// not currently used +function past_edits() { + + var the_past_edits = []; + if(recent_editing_actions.length) { + the_past_edits = recent_editing_actions.map(x => [x.join(" ")])} + else { the_past_edits = [["no chnages yet"]] } + + return the_past_edits +} + +// this should be created from inner_menu_for +editing_container_for = { "p": 1, "ip": 1, "mp": 1, "fp": 1, + "theorem-like": ["theorem", "proposition", "lemma", "corollary", "claim", "fact", "identity", "algorithm"], + "definition-like": ["definition", "conjecture", "axiom", "hypothesis", "principle", "heuristic", "assumption"], +"remark-like": ["remark", "warning", "note", "observation", "convention", "insight"], +"example-like": ["example", "question", "problem"], +"exercise-like": ["exercise"], +"ol": ["item"], +"li": [""], +"list": [""], +"image": [""], +"sbs2": [""], +"sbs3": [""], +"sbs4": [""], +"source": 1, +"proof-standalone": [""], //just a guess +"proof": [""] //just a guess +} + +function make_current_editing_tree_from_id(theid) { + +//current_editing keeps track of where we are in the tree. maybe need a better name? + + editorLog(" OOOOO make_current_editing_tree_from_id", theid); + editorLog(" which has internalSource", internalSource[theid]); + editorLog(" within", internalSource); + editorLog(" and the DOM object is", document.getElementById(theid)); + // the existing current_editing know the top level id +// var top_id = current_editing["tree"][0][0].id; + var top_id = top_level_id; + + // but now we need to start over and go bottom-up + current_editing = { + "level": -1, + "location": [], + "tree": [ ] + } + + var current_id = theid; + var current_element = document.getElementById(current_id); + console.log("current_id", current_id, "current_element", current_element); + var selectable_parent, current_element_siblings, selectable_parent_id; + var ct=0; + while (current_id != top_id && ct < 10) { + ct += 1; + editorLog("ct", ct); + editorLog("looking to match current_element", current_element, "until we hit", top_id); + selectable_parent = current_element.parentElement.closest("[data-editable]"); + current_element_siblings = next_editable_of(selectable_parent, "children"); + current_id = selectable_parent.id; + editorLog("current_id", current_id); + current_editing["tree"].unshift(current_element_siblings); + editorLog("looking for", current_element, "in", current_element_siblings); + for (var j=0; j < current_element_siblings.length; ++j) { + if (current_element == current_element_siblings[j]) { + current_editing["level"] += 1; + current_editing["location"].unshift(j); + editorLog("this is item", j); + break + } else { + editorLog(current_element == current_element_siblings[j], "aaa", current_element,"zzz", current_element_siblings[j]) + } + } + current_element = selectable_parent + } + current_editing["level"] += 1; + current_editing["location"].unshift(0); + current_editing["tree"].unshift([document.getElementById(top_id)]); + + editorLog("built current_editing after", ct, "levels"); + editorLog("current_editing[level]", current_editing["level"]); + editorLog("current_editing[location]", current_editing["location"]); + editorLog("current_editing[tree]", current_editing["tree"]) + + editorLog(" OOOOO done with make_current_editing_tree_from_id", theid, document.getElementById(theid)); + +} + +function standard_title_form(object_id) { + var the_object = internalSource[object_id]; + var the_title = the_object.title; + + var title_form = '' + the_title + ''; + + return title_form +} +function standard_caption_form(object_id) { + var the_object = internalSource[object_id]; + editorLog("editing caption of object_id", object_id, "which contains", the_object); + var the_caption = the_object.caption; + + var caption_form = '' + the_caption + ''; + + return caption_form +} + + +function menu_options_for(object_id, component_type, level) { + // this should be a function of the object, not just its tag + // p in li vs p child of section, for example + var menu_for; + + if (!component_type) { component_type = internalSource[object_id]["sourcetag"] } + editorLog("component_tag", component_type); + if (level == "base") { + menu_for = base_menu_for + } else if (level == "move-or-delete") { + editorLog("C0 menu options for", component_type); + var m_d_options; + var component_parent = internalSource[object_id]["parent"][0]; + var component_parent_tag = internalSource[component_parent]["sourcetag"]; + if (component_type == "p" && component_parent_tag == "li") { + m_d_options = [ + ["move-local-p", "Move these words within this page"], + ["move-local-li", "Move this list item within this page"], + ["move-global", "Move this another page (not implemented yet)"], + ["delete", "Delete"] // does it matter whether there are other p in this li? + ]; + } else if(tag_type(component_type) == "p") { + m_d_options = [ + ["move-local-p", "Move this text within this page"], + ["move-global", "Move to another page (not implemented yet)"], + ["delete", "Delete"] + ]; + } else { + m_d_options = [ + ["move-local", "Move within this page"], + ["move-global", "Move to another page (not implemented yet)"], + ["delete", "Delete"] + ]; + } + var this_menu = ""; + for (var i=0; i < m_d_options.length; ++i) { + this_menu += '
  • ' + this_item_shortcut + ''); + } + } else { + this_menu += 'data-jump="' + this_item_name.charAt(0) + '"'; + } + if (i==0) { this_menu += ' id="choose_current"'} + this_menu += '>'; + + if (this_item_name.match(/^[a-z]/i)) { + first_character = this_item_name.charAt(0); + this_item_name = this_item_name.replace(first_character, "" + first_character + ""); + } + + this_menu += this_item_name + // little right triangle if there is a submenu + // if (this_item_label in inner_menu_for()) { this_menu += '
    ▻
    ' } + if (this_item_label in submenu_options) { this_menu += '
    ▻
    ' } + this_menu += '
  • '; + } + + return this_menu +} + +function top_menu_options_for(this_obj) { + editorLog("top menu options for aa", this_obj); + var this_id = this_obj.id; + +// maybe the "classList" in this function shoudl instead look at the internalSource? + + var this_list = ""; + + if (this_obj.classList.contains("heading")) { + var this_obj_parent = this_obj.parentElement; + editorLog("heading options for bbb", this_obj_parent); + var this_obj_parent_id = this_obj_parent.id; + var this_obj_parent_source = internalSource[this_obj_parent_id]; + var this_obj_environment = this_obj_parent_source["sourcetag"]; + + editorLog("this_obj_environment", this_obj_environment); + + this_list = '
  • Change the title
  • '; + this_list += '
  • Change "' + this_obj_environment + '" to
    ▻
  • '; + } else { + var this_object_type = this_obj.tagName; // needs to examine other attributes and then look up a reasonable name +//consolidate this redundancy + this_obj_id = this_obj.id; + if (!this_obj_id) { + this_obj_id=this_obj.getAttribute("data-parent_id") + editorLog("now has id", this_obj_id); + } + this_obj_source = internalSource[this_obj_id]; + editorLog("this_obj_source", this_obj_source); + editorLog("this_obj", this_obj, "classList", this_obj.classList, "T/F", this_obj.classList.contains("image-box")); + this_obj_environment = this_obj_source["sourcetag"]; + if (this_object_type == "P" || this_obj.classList.contains("displaymath")) { + this_list = '
  • Edit ' + this_obj_environment + '
  • '; + var editable_children = next_editable_of(this_obj, "children"); + editorLog("editable_children", editable_children); + if (editable_children.length && !(this_object_type == "P")) { + this_list += '
  • Enter ' + this_obj_environment + '
  • '; + } + if (editable_children.length > 2 && (this_object_type == "SECTION")) { + this_list += '
  • Go to end of ' + this_obj_environment + '
  • '; + } + } else if (this_obj.classList.contains("image-box")) { + editorLog("found an image-box"); + this_list = '
  • Modify layout
    ▻
  • '; + } else if (this_obj.classList.contains("sbspanel")) { + this_list = '
  • Modify layout
    ▻
  • '; + } else { + this_list += '
  • Enter ' + this_obj_environment + '
  • '; + if (this_object_type == "SECTION") { + this_list += '
  • Go to end of ' + this_obj_environment + '
  • '; + } + } + + if (this_obj.classList.contains("sbspanel")) { + this_list += '
  • Insert in panel
    ▻
  • '; + } + + if (this_obj.classList.contains("project-like")) { + this_list += '
  • Add a task
  • '; + } + + if (this_id != top_level_id) { + this_list += '
  • Insert before
    ▻
  • '; + this_list += '
  • Insert after
    ▻
  • '; + + this_list += '
  • Move or delete
    ▻
  • '; + } + this_list += '
  • Metadata
    ▻
  • '; + if (this_id != top_level_id) { + this_list += '
  • Revert
    ▻
  • '; + } + if (previous_editing() && this_id == top_level_id) { + this_list += '
  • Resume previous editing
  • '; + } + this_list += '
  • Save
  • '; + this_list += '
  • Stop editing
  • '; + } + return this_list +} + +function edit_menu_from_current_editing(motion) { + // obviously we need to think a bit about current_editing and how it is used + var object_of_interest = current_editing["tree"][ current_editing["level"] ][ current_editing["location"][ current_editing["level"] ] ]; + edit_menu_for(object_of_interest, motion); +} + +function edit_menu_for(this_obj_or_id, motion) { + editorLog("make edit menu", motion, "for", this_obj_or_id); + + // delete the old menu, if it exists + if (document.getElementById('edit_menu_holder')) { + var current_menu = document.getElementById('edit_menu_holder'); + editorLog("current_menu", current_menu); + editorLog("this_choice", document.getElementById('enter_choice')); + if (this_choice = document.getElementById('enter_choice')) { + if (this_choice.getAttribute("data-location") == "stay") { + current_menu.previousSibling.classList.remove("may_leave") + } else { + current_menu.parentElement.classList.remove("may_select"); + current_menu.parentElement.classList.remove("may_enter"); + } + } + current_menu.parentElement.classList.remove("may_select"); + current_menu.parentElement.classList.remove("may_enter"); + current_menu.remove(); + } + + if (!this_obj_or_id) { + errorLog("error: empty this_obj_or_id", motion); + return "" + } else { + editorLog("this_obj_or_id", this_obj_or_id, "string?", typeof this_obj_or_id === 'string'); + editorLog("which has parent", this_obj_or_id.parentElement) + } + + console.log("this_obj_or_id", this_obj_or_id, typeof this_obj_or_id); + if (typeof this_obj_or_id === 'string') { + this_obj = document.getElementById(this_obj_or_id) + } else { + this_obj = this_obj_or_id + } + console.log("this_obj", this_obj); + var this_id = this_obj.id; +// this_obj = document.getElementById(this_obj); // because is is an id? Maybe need cases here + + if (motion == "entering") { + menu_location = "afterbegin"; + this_obj.classList.remove("may_leave"); + if (next_editable_of(this_obj, "children").length && !(this_obj.tagName == "P")) { + this_obj.classList.add("may_enter"); + } else { + this_obj.classList.add("may_select"); + } + if (inline_tags.includes(this_obj.tagName.toLowerCase())) { + this_obj.classList.add("inline"); + } + } else { menu_location = "afterend"; + this_obj.classList.remove("may_select"); + this_obj.classList.remove("may_enter"); + this_obj.classList.add("may_leave"); + editorLog("added may_leave to", this_obj) + } // when motion is 'leaving' + + var edit_menu_holder = document.createElement('div'); + edit_menu_holder.setAttribute('id', 'edit_menu_holder'); + edit_menu_holder.setAttribute('class', 'edit_menu_holder'); + edit_menu_holder.setAttribute('tabindex', '-1'); + editorLog("adding menu for", this_obj_or_id, "menu_location", menu_location); + editorLog("which has tag", this_obj.tagName); + editorLog("does", this_obj.classList, "include type", this_obj.classList.contains("type")); + + this_obj.insertAdjacentElement(menu_location, edit_menu_holder); + editorLog("added edit_menu_holder", document.getElementById("edit_menu_holder"), motion); + + var edit_option = document.createElement('span'); + edit_option.setAttribute('id', 'enter_choice'); + + if (motion == "entering") { + editorLog("inline_tags", inline_tags, "tag", this_obj.tagName.toLowerCase()); + editorLog("next_editable_of(this_obj, children)", next_editable_of(this_obj, "children")); + if (false && inline_tags.includes(this_obj.tagName.toLowerCase())) { + edit_option.innerHTML = "change this?"; + edit_option.setAttribute('data-location', 'inline'); + } else if (this_obj.classList.contains("type")) { + // e.g., changing "proposition" to "theorem" + // need to code this better: over-writing edit_option + edit_option = document.createElement('ol'); + edit_option.setAttribute('id', 'edit_menu'); + edit_option.setAttribute('class', 'edit_menu'); + this_obj_parent_id = this_obj.parentElement.parentElement.id; + this_obj_environment = internalSource[this_obj_parent_id]["sourcetag"]; + edit_option.innerHTML = '
  • Change "' + this_obj_environment + '" to
    ▻
  • '; + edit_option.setAttribute('data-location', 'inline'); + } else if (this_obj.classList.contains("image-box")) { + edit_option.innerHTML = "modify this image layout, or add near here?"; + } else if (this_obj.classList.contains("sbspanel")) { + edit_option.innerHTML = "modify this panel layout, or change panel contents?"; + } else if (this_obj.classList.contains("title") || this_obj.classList.contains("caption")) { + var this_contained_type = "title"; + if (this_obj.classList.contains("caption")) { this_contained_type = "caption" } + edit_option = document.createElement('ol'); + edit_option.setAttribute('id', 'edit_menu'); + edit_option.setAttribute('class', 'edit_menu'); + editorLog("this_obj", this_obj); + editorLog("this_obj.innerHTML", this_obj.innerHTML); + editorLog("menu only?", this_obj.innerHTML == '
    '); + this_obj_parent_id = this_obj.parentElement.parentElement.id; + this_obj_environment = internalSource[this_obj_parent_id]["sourcetag"]; + if (this_obj.innerHTML == '
    ') { + edit_option.innerHTML = '
  • Add a ' + this_contained_type + '
  • '; + } else { + edit_option.innerHTML = '
  • Change ' + this_contained_type + '
  • '; + } + edit_option.setAttribute('data-location', 'inline'); + } else if ((this_obj.classList.contains("placeholder") && (this_obj.classList.contains("hint") || + this_obj.classList.contains("answer") || + this_obj.classList.contains("solution") || + this_obj.classList.contains("proof")) ) ) { + var theverb = "add" + var thenoun = this_obj.getAttribute("data-HAS"); + edit_option.setAttribute('id', 'choose_current'); + edit_option.setAttribute('data-env', thenoun); + edit_option.setAttribute('data-parent_id', this_obj.getAttribute("data-parent_id")); + edit_option.innerHTML = "" + theverb + "" + " " + thenoun; + } else if (this_obj.classList.contains('workspace')) { + edit_option.setAttribute('id', 'choose_current'); + edit_option.setAttribute('data-env', 'workspace'); + edit_option.setAttribute('data-action', 'modify'); + edit_option.setAttribute('data-parent_id', this_obj.getAttribute("data-parent_id")); + + edit_option.innerHTML = "" + "adjust" + "" + " " + "workspace"; + } else { + if (next_editable_of(this_obj, "children").length && this_obj.tagName != "P") { + editorLog("this_obj", this_obj); + if (this_id == top_level_id) { + edit_option.innerHTML = "enter this " + internalSource[this_obj.id]["sourcetag"] + "?"; + } else { + edit_option.innerHTML = "enter this " + internalSource[this_obj.id]["sourcetag"] + ", or add near here?"; + } + } else { + edit_option.innerHTML = "edit this passage, or add near here?"; + } + edit_option.setAttribute('data-location', 'next'); + } + } else { + edit_option.setAttribute('data-location', 'stay'); + edit_option.innerHTML = "continue editing this " + internalSource[this_obj.id]["sourcetag"]; + } + editorLog("edit_option", edit_option); + document.getElementById("edit_menu_holder").insertAdjacentElement("afterbegin", edit_option); + document.getElementById('edit_menu_holder').focus(); +} + +function next_editable_of(obj, relationship) { + var next_to_edit; + editorLog("finding", relationship, "editable of", obj); + if (relationship == "children") { + next_to_edit = $(obj).find(' > [data-editable], > .sidebyside > .sbsrow > [data-editable], > li > [data-editable], > .heading > [data-editable], > .hint > [data-editable], > .answer > [data-editable]') + } else if (relationship == "outer-block") { // for example, a direct child of a section + next_to_edit = $(obj).find(' > [data-editable]') + } else if (relationship == "inner-block") { // typically a paragraph + next_to_edit = $(obj).find('section > [data-editable], [data-editable="99"], [data-editable="42"]') + } else if (relationship == "li-only") { // typically a paragraph + next_to_edit = $(obj).find('li') + } else { + editorLog("unimplemented next_editable_of") + } + + editorLog("next_to_edit", next_to_edit); + return next_to_edit +} + +function create_new_internal_object(new_tag, new_id, parent_description) { + + var new_source = {"xml:id": new_id, "sourcetag": new_tag, "parent": parent_description, "title": ""} + + editorLog("create new internal object", new_tag, "new_id", new_id, "parent_description", parent_description); + editorLog("within", internalSource[parent_description[0]]); + if (new_tag.startsWith("sbs")) { // creating an sbs, which contains one sbsrow, which contains several sbspanels + new_source.sourcetag = "sidebyside"; + var sbs_layout = new_tag.split("_"); + var [margin_left, margin_right] = [sbs_layout[1], sbs_layout[sbs_layout.length - 1]]; + editorLog("sbs side margins", margin_left, "jj", margin_right); + new_source.marginleft = margin_left; + new_source.marginright = margin_right; + + var col_content = ""; + var widths = []; + for (var j=2; j <= sbs_layout.length - 2; ++j) { + widths.push(sbs_layout[j]); + var new_col_id = randomstring(); + col_content += "<&>" + new_col_id + "<;>"; + internalSource[new_col_id] = {"xml:id": new_col_id, "sourcetag": "sbspanel", + "content": "", "parent": [new_id, "content"]} + } + new_source.widths = widths; + new_source.content = col_content; + + editorLog("new sbs", new_source); + + } else { + + editorLog("new_tag", new_tag); + var thisstructure = objectStructure[new_tag]; + var thisownersourcestructure = {}; + if (!thisstructure) { errorLog(new_tag + " not implemented yet"); return "" } + if ("owner" in thisstructure) { + var thisownerstructure = objectStructure[thisstructure.owner]; + thisownersourcestructure = thisownerstructure.source; + } + var thissourcestructure = Object.assign({},thisownersourcestructure, thisstructure.source); + + editorLog("thissourcestructure", thissourcestructure); + + if ("attributes" in thissourcestructure) { + these_source_attributes = thissourcestructure.attributes; + for (var j=0; j < these_source_attributes.length; ++j) { + editorLog("adding", j, "attribute", these_source_attributes[j]); + new_source[these_source_attributes[j][0]] = these_source_attributes[j][1] + } + } + +/* here need to also use the owner structure */ + var these_source_pieces = thissourcestructure.pieces; + for (var j=0; j < these_source_pieces.length; ++j) { + editorLog("adding a piece", these_source_pieces[j]); + var [this_piece, this_piece_contains] = these_source_pieces[j]; + + if (this_piece_contains) { + var new_child_id = randomstring(); + new_source[this_piece] = "<&>" + new_child_id + "<;>"; + create_new_internal_object(this_piece_contains, new_child_id, [new_id, this_piece]); + } else { + new_source[this_piece] = "" + } + } + } + + editorLog("made the new_source", new_source); + + internalSource[new_id] = new_source; + + editorLog("parent_description", parent_description, "new_tag", new_tag, "new_id", new_id); + editorLog("internalSource", internalSource); + if (new_tag == "list") { + // do nothing, because it is the child "li" which we are really creating + // chack that: maybe do add, if the stack is in the proper order + } else if (tag_type(new_tag) == "p"){ + // p is the default, so no need to keep track of it + } else if (new_tag == "source"){ + // not really a tag + } else if (new_tag.startsWith("sbs")){ + ongoing_editing_actions.push(["new", "sbs", new_id]); + } else { + ongoing_editing_actions.push(["new", new_tag, new_id]); + } + return new_tag +} + +function show_source(sibling, relative_placement) { +// var current_source = document.getElementById("newpretextsource"); +// if (current_source) { editorLog("curr sou", current_source); alert("curr sour"); current_source.remove() } + var edit_placeholder = document.createElement("span"); + edit_placeholder.setAttribute('id', "newsource"); + sibling.insertAdjacentElement(relative_placement, edit_placeholder); + + editorLog("just added", edit_placeholder); + + var the_pretext_source = output_from_id("", top_level_id, "pretext"); + + the_pretext_source = the_pretext_source.replace(/\n\n/g, '\n'); + + // remove temporary ids + the_pretext_source = the_pretext_source.replace(/ xml:id="tMP[0-9a-z]+"/g, ''); + the_pretext_source = the_pretext_source.replace(/^ +$/mg, ''); // m = multiline + +// var the_old_source = document.getElementById("newsource"); +// if (the_old_source) { the_old_source.remove() } + edit_placeholder.insertAdjacentHTML('afterend', ''); +} + +// unify with show_source, and maybe make the clean up part of output_from_id +function save_source() { +// var current_source = document.getElementById("newpretextsource"); +// if (current_source) { editorLog("curr sou", current_source); alert("curr sour"); current_source.remove() } + editorLog(" QQ saving"); + + var the_pretext_source = output_from_id("", top_level_id, "pretext"); + + the_pretext_source = the_pretext_source.replace(/\n\n/g, '\n'); + // remove temporary ids + the_pretext_source = the_pretext_source.replace(/ xml:id="tMP[0-9a-z]+"/g, ''); + the_pretext_source = the_pretext_source.replace(/^ +$/mg, ''); // m = multiline + + editorLog(" RR saving", top_level_id, "which begins", the_pretext_source.substring(0,50)); + parent.save_file(top_level_id, the_pretext_source) +} + +function create_object_to_edit(new_tag, new_objects_sibling, relative_placement) { + + // when relative_placement is "afterbegin", the new_objects_sibling is actually its parent + editorLog("create object to edit", new_tag, new_objects_sibling, relative_placement); + // first insert a placeholder to edit-in-place + var new_id = randomstring(); + recent_editing_actions.push(["new", new_tag, new_id]); + // we won;t need all of these, so re-think when these are created + var edit_placeholder = document.createElement("span"); + edit_placeholder.setAttribute('id', new_id); + + // when adding an li, you are actually focused on somethign inside an li + // but, maybe that distinction shoud be mede before calling create object to edit ? + if (new_tag == "li") { new_objects_sibling = new_objects_sibling.parentElement } + + var sibling_id, parent_description, object_neighbor; + + if (["hint", "answer", "solution", "proof"].includes(new_tag)) { // this is only for the case that a solution does not already exist +// create_new + + sibling_id = new_objects_sibling.parentElement.id; + parent_description = [sibling_id, new_tag] + + editorLog(new_tag, "parent_description", parent_description); + object_neighbor = "" ; // will not be used + + } else if(new_objects_sibling.classList.contains("sbspanel")) { //bad hack! use internalCOntents! + sibling_id = new_objects_sibling.id; + editorLog("special case for empty sbspanel",sibling_id); + // alert("special sbspanel case"); + parent_description = [sibling_id, "content"] + object_neighbor = "" ; // will not be used??? + } else { + // and describe where it goes + editorLog("new_objects_sibling",new_objects_sibling); + sibling_id = new_objects_sibling.id; + parent_description = internalSource[sibling_id]["parent"]; + object_neighbor = new RegExp('(<&>' + sibling_id + '<;>)'); + } + + if (relative_placement == "afterbegin" && new_tag != "image") { // when adding to a sbs panel + // redo the condition so that it explicitly uses sbs + // redundant with the "special case" above? + // parent_description = [new_id, "content"]; + } + var object_neighbor = new RegExp('(<&>' + sibling_id + '<;>)'); + if (new_tag == "task" && !(new_objects_sibling.classList.contains("task"))) { + // when task is being added by selecting from the parent exercise + relative_placement = "atend"; + parent_description = [sibling_id, "tasks"]; + } + if (["hint", "answer", "solution", "proof"].includes(new_tag)) { // this is only for the case that a solution does not already exist + relative_placement = "replace"; + } + editorLog(new_tag, "parent_description", parent_description); + + // then create the empty internalSource for the new object + var new_obj = create_new_internal_object(new_tag, new_id, parent_description); + if (!new_obj) { return "" } + + // we have made the new object, but we still have to put it in the correct location + + var the_current_arrangement = internalSource[parent_description[0]][parent_description[1]]; + editorLog(" the_current_arrangement", the_current_arrangement); + editorLog(" from parent_description", parent_description, "internalSource[parent_description[0]]", internalSource[parent_description[0]]); + editorLog(" current_editing", current_editing); + +// maybe the changes to current_editing is different for lists? + + var neighbor_with_new = ''; + var current_level = current_editing["level"]; + var current_location = current_editing["location"][current_level]; + editorLog(" UUU current_editing", current_editing["level"], current_editing["location"].length, current_editing["tree"].length, current_editing["tree"][current_editing["level"]]) + if (relative_placement == "beforebegin" || relative_placement == "afterbegin") { + neighbor_with_new = '<&>' + new_id + '<;>' + '$1'; + } + else if (relative_placement == "afterend" || relative_placement == "beforeend"){ + neighbor_with_new = '$1' + '<&>' + new_id + '<;>' + current_location += 1 + } + editorLog("makking new_arrangement from", object_neighbor, "by", neighbor_with_new); + + if (relative_placement == "atend" || relative_placement == "replace" ) { + // this is not quire gight, but it works for hint/answer/solution + new_arrangement = the_current_arrangement + '<&>' + new_id + '<;>'; + } else if(the_current_arrangement) { + new_arrangement = the_current_arrangement.replace(object_neighbor, neighbor_with_new); + } else { + new_arrangement = '<&>' + new_id + '<;>'; + } + editorLog("which became", new_arrangement); +// alert("here"); + internalSource[parent_description[0]][parent_description[1]] = new_arrangement; + if (new_tag == "list") { + // current_editing["level"] += 1; + // current_editing["location"].push(0); + // current_editing["tree"].push([document.getElementById(new_p_id)]) + } else { + current_editing["location"][current_level] = current_location; + } + editorLog(" new_arrangement", new_arrangement); + editorLog("tried to insert", new_id, "next to", sibling_id, "in", the_current_arrangement) + editorLog(" updated current_editing", current_editing); + editorLog(" VVV current_editing", current_editing["level"], current_editing["location"].length, current_editing["tree"].length, current_editing["tree"][current_editing["level"]]) + editorLog("relative_placement", relative_placement, "edit_placeholder", edit_placeholder); + + if (relative_placement == "atend") { + new_objects_sibling.insertAdjacentElement("beforeend", edit_placeholder); + } else if (relative_placement == "replace") { + new_objects_sibling.replaceWith(edit_placeholder); + } else { + new_objects_sibling.insertAdjacentElement(relative_placement, edit_placeholder); + } + + return edit_placeholder +} + +function edit_in_place(obj, oldornew) { + // currentlt old_or_new is onlu use as "new" for a new li, so that we know + // to immediately make a new li to edit + // previous comment probebly wrong/out of date + + var thisID; + editorLog("in edit in place", obj); + if (thisID = obj.getAttribute("id")) { + editorLog("will edit in place id", thisID, "which is", obj); + thisTagName = obj.tagName.toLowerCase(); + } else { // editing somethign without an id, so probably is a title or caption + if (obj.classList.contains("heading")) { + editorLog("changing a heading"); + editorLog("except we don;t know how to do that") + } else { + errorLog("error: I don't know how to edit", obj) + } + return "" + } + + // this only works for paragraphs, + // which may be right, because existing content is mostly titles and paragraphs + if ( internalSource[thisID] ) { + var new_tag = internalSource[thisID]["sourcetag"]; + var new_id = thisID; // track down why new_id is in the code + editorLog("new_tag is", new_tag, "from thisID", thisID, "from", internalSource[thisID]); + if (tag_type(new_tag) == "p" || tag_type(new_tag) == "md") { // make into a category? + var this_content_container = document.createElement('div'); + this_content_container.setAttribute('id', "actively_editing"); + this_content_container.setAttribute('data-age', oldornew); + editorLog("thing with thisID", thisID, "is",document.getElementById(thisID)); + $("#" + thisID).replaceWith(this_content_container); + + var idOfEditText = 'editing' + '_input_text'; + var paragraph_editable = document.createElement('div'); + paragraph_editable.setAttribute('contenteditable', 'true'); + if (tag_type(new_tag) == "md") { + paragraph_editable.setAttribute('class', 'text_source displaymath_input'); + } else { + paragraph_editable.setAttribute('class', 'text_source paragraph_input'); + } + paragraph_editable.setAttribute('id', idOfEditText); + paragraph_editable.setAttribute('data-source_id', thisID); + paragraph_editable.setAttribute('data-parent_id', internalSource[thisID]["parent"][0]); + paragraph_editable.setAttribute('data-parent_component', internalSource[thisID]["parent"][1]); + + document.getElementById('actively_editing').insertAdjacentElement("afterbegin", paragraph_editable); + + editorLog("setting", $('#' + idOfEditText), "to have contents", internalSource[thisID]["content"]); + +// from https://stackoverflow.com/questions/21257688/paste-rich-text-into-content-editable-div-and-only-keep-bold-and-italics-formatt + +// figure out better how to do this as needed. + $('[contenteditable]').on('paste',function(e) { + e.preventDefault(); + var text = (e.originalEvent || e).clipboardData.getData('text/plain') || prompt('Paste something..'); + document.execCommand('insertText', false, text); + }); + the_contents = internalSource[thisID]["content"]; + the_contents = expand_condensed_source_html(the_contents, "edit"); + the_contents = the_contents.replace(/\\cr/g, "

    "); + $('#' + idOfEditText).html(the_contents); + document.getElementById(idOfEditText).focus(); + editorLog("made edit box for", thisID); + editorLog("which is", document.getElementById(idOfEditText)); + editorLog("Whth content CC" + document.getElementById(idOfEditText).innerHTML + "DD"); + editorLog("Whth content EE" + document.getElementById(idOfEditText).innerText + "FF"); + editorLog("Whth content GG" + document.getElementById(idOfEditText).textContent + "HH"); + this_char = ""; + prev_char = ""; + + } else if (new_tag == "image") { + var this_content_container = document.createElement('div'); + this_content_container.setAttribute('id', "actively_editing"); + this_content_container.setAttribute('data-age', oldornew); + this_content_container.setAttribute('style', "width:50%; margin-left:auto; margin-right: auto; padding: 2em 3em 3em 3em; background: #fed"); + $("#" + thisID).replaceWith(this_content_container); + + var idOfEditText = 'editing' + '_input_image'; + var image_editable = document.createElement('div'); + image_editable.setAttribute('contenteditable', 'true'); + image_editable.setAttribute('class', 'image_source'); + image_editable.setAttribute('id', idOfEditText); + image_editable.setAttribute('style', "background: #fff"); + image_editable.setAttribute('data-source_id', thisID); + image_editable.setAttribute('data-parent_id', internalSource[thisID]["parent"][0]); + image_editable.setAttribute('data-parent_component', internalSource[thisID]["parent"][1]); + + document.getElementById('actively_editing').insertAdjacentElement("afterbegin", image_editable); + + var edit_instructions = document.createElement('span'); + edit_instructions.setAttribute('style', "font-size: 90%"); + edit_instructions.innerHTML = "URL of image:" + document.getElementById('actively_editing').insertAdjacentElement("afterbegin", edit_instructions); + + document.getElementById(idOfEditText).focus(); + editorLog("made edit box for", thisID); + editorLog("which is", document.getElementById(idOfEditText)); + editorLog("Whth content CC" + document.getElementById(idOfEditText).innerHTML + "DD"); + editorLog("Whth content EE" + document.getElementById(idOfEditText).innerText + "FF"); + editorLog("Whth content GG" + document.getElementById(idOfEditText).textContent + "HH"); + this_char = ""; + prev_char = ""; + + } else { + + editorLog(new_tag, "create the object, then edit p in place", obj); + + var this_object = html_from_internal_id(new_id, ""); + var where_it_goes = document.getElementById(thisID); + where_it_goes.insertAdjacentHTML('afterend', this_object[0]); + where_it_goes.remove(); + var where_it_is = document.getElementById(thisID); + + + editorLog("added this_object", this_object); + editorLog("where it is", where_it_is); + + var this_source = internalSource[thisID]; + if (this_source.sourcetag == "sidebyside") { // generalize to container was added + editorLog("added an sbs", document.getElementById(thisID)); + editorLog("its .firstChild", document.getElementById(thisID).firstElementChild); + editorLog("its .firstChild.firstchild", document.getElementById(thisID).firstElementChild.firstElementChild); + var first_panel_id = document.getElementById(thisID).firstElementChild.firstElementChild.id; + editorLog("first_panel_id", first_panel_id, document.getElementById(first_panel_id)); + make_current_editing_tree_from_id(first_panel_id); + edit_menu_from_current_editing("entering"); + } else { + var empty_p_child = $(where_it_is).find("p"); + if (empty_p_child[0]) { + editorLog("found the empty p", empty_p_child); + editorLog("found the empty p[0]", empty_p_child[0]); + edit_in_place(empty_p_child[0], "new"); + } else { + errorLog("error: no empty p to edit") + } + } + } + + } else { + editorLog("Error: edit in place of object that is not already known", obj); + editorLog("What is known:", internalSource) + } +} + +function resume_editing() { + internalSource = previous_editing(); + replace_by_id(internalSource["root_data"]["id"], "html"); + edit_menu_for(top_level_id, "entering"); + console.log("editing resumed"); +} + +function replace_by_id(theid, format) { + + if (format != "html") { return "" } + + var this_object_new = output_from_id("",theid, format); + + console.log("id", theid); +// GO BACK AND REVISIT THE NEXT 3 LINES +// document.getElementById(theid).setAttribute("id", "delete_me"); +// document.getElementById("delete_me").insertAdjacentHTML('beforebegin', this_object_new); +// document.getElementById("delete_me").remove(); + +// need to also work with MJ3 +// MathJax.Hub.Queue(['Typeset', MathJax.Hub, document.getElementById(theid)]); + console.log("MathJax on", theid, document.getElementById(theid)); +// MathJax.typesetPromise(document.getElementById(theid)); + MathJax.typesetPromise(); + + console.log("adjusting workspace"); +window.setTimeout(adjustWorkspace, 1000); +} + +// temporary: need to unify img and sbs layout +function modify_by_id(theid, modifier) { + editorLog("modifying by id", theid); + if (internalSource[theid]["sourcetag"] == "sbspanel") { + modify_by_id_sbs(theid, modifier) + } else if (environment_instances["project-like"].includes(internalSource[theid]["sourcetag"]) + || internalSource[theid]["sourcetag"] == "task") { + modify_by_id_workspace(theid, modifier) + } else { + modify_by_id_image(theid, modifier) + } + save_edits() +} + +function modify_by_id_workspace(theid, modifier) { + + var the_height = internalSource[theid]["workspace"]; + editorLog("the_height", the_height, "from", internalSource[theid]); + +//modify: enlarge, shrink, enlargeslightly, shrinkslightly, done + + the_height = parseInt(the_height); + editorLog('the_height, ', the_height); + + if (modifier == "enlarge") { the_height += 10 } + else if (modifier == "shrink") { the_height -= 10 } + else if (modifier == "enlargeslightly") { the_height += 1 } + else if (modifier == "shrinkslightly") { the_height -= 1 } + + if (the_height < 0) { the_height = 0 } + + internalSource[theid]["workspace"] = the_height; + editorLog("the_height is now", the_height); + + var this_workspace = document.getElementById(theid).querySelector(".workspace"); + this_workspace.setAttribute("style", "height:" + the_height*10 + "px"); + this_workspace.setAttribute("data-space", the_height); +} + +function modify_by_id_image(theid, modifier) { + + var width = internalSource[theid]["width"]; + var marginleft = internalSource[theid]["marginleft"]; + var marginright = internalSource[theid]["marginright"]; + editorLog('width, , marginright, , marginleft', width, "mr", marginright, "ml", marginleft); + width = parseInt(width); + marginright = parseInt(marginright); + marginleft = parseInt(marginleft); + + var scale_direction = 1; + var moving_direction = 1; + if (modifier == "shrink") { scale_direction = -1 } + else if (modifier == "left") { moving_direction = -1 } + if ("enlarge shrink".includes(modifier)) { + if ((width >= 100 && scale_direction == 1) || (width <= 0 && scale_direction == -1)) { + editorLog("can't go above 100 or below 0"); + return + } else { + width += 2*scale_direction*magnify_scale; + } + if (marginleft > 0 && marginright > 0) { + marginleft += -1*scale_direction*magnify_scale; + marginright += -1*scale_direction*magnify_scale; + } else if (marginleft > 0) { + marginleft += -2*scale_direction*magnify_scale; + } else if (marginright > 0) { + marginright += -2*scale_direction*magnify_scale; + } else if (scale_direction < 0) { // applies when we shrink a 100 width image + marginleft += -1*scale_direction*magnify_scale; + marginright += -1*scale_direction*magnify_scale; + } else { + // do nothing: this is a placeholder which is reached when both margins are 0 + editorLog("already have no margins, width is", width); + } + } else if ("left right".includes(modifier)) { + editorLog("marginleft*moving_direction", marginleft*moving_direction, "marginright*moving_direction", marginright*moving_direction); + if ((marginleft > 0 && marginright > 0) || (marginright*moving_direction > 0) || (marginleft*moving_direction < 0)) { + marginleft += moving_direction*move_scale; + marginright += -1*moving_direction*move_scale; + } else { + // do nothing: this is a placeholder which is reached when both margins are 0 + // we choose to prioritize scale, so a 100% image cannot be shifted + editorLog("already at 100%, width is", width); + } + } + + var the_new_sizes = "width: " + width + "%;"; + the_new_sizes += "margin-right: " + marginright + "%;"; + the_new_sizes += "margin-left: " + marginleft + "%;"; + + internalSource[theid]["width"] = width; + internalSource[theid]["marginleft"] = marginleft; + internalSource[theid]["marginright"] = marginright; + + document.getElementById(theid).setAttribute("style", the_new_sizes); +} + +function modify_by_id_sbs(theid, modifier) { + + var this_panel_source = internalSource[theid]; +// var this_width = this_panel_source["width"]; + var this_sbs_id = this_panel_source["parent"][0]; + var this_sbs_source = internalSource[this_sbs_id]; + editorLog("this_sbs_source", this_sbs_source); + var marginleft = parseInt(this_sbs_source["marginleft"]); + var marginright = parseInt(this_sbs_source["marginright"]); + var these_siblings = this_sbs_source["content"]; + these_siblings = these_siblings.replace(/^\s*<&>\s*/, ""); + these_siblings = these_siblings.replace(/\s*<;>\s*$/, ""); + these_siblings = these_siblings.replace(/>\s*<"); + editorLog("these_siblings", these_siblings); + var these_siblings_list = these_siblings.split("<;><&>"); + var this_panel_index = these_siblings_list.indexOf(theid); + editorLog("this panel", theid, "is", this_panel_index, "within", these_siblings_list); + var these_panel_widths = this_sbs_source.widths; + var this_width = parseInt(these_panel_widths[this_panel_index]); + var total_width = 0; + editorLog("these html siblings",document.getElementById(these_siblings_list[0])," and ", document.getElementById(these_siblings_list[1])) + editorLog("these siblings source", internalSource[these_siblings_list[0]], "and", internalSource[these_siblings_list[1]]); + for(var j=0; j < these_siblings_list.length; ++j) { + // var t_wid = parseInt(internalSource[these_siblings_list[j]]["width"]); + var t_wid = parseInt(these_panel_widths[j]); + these_panel_widths[j] = t_wid; // put it back as an integer + editorLog("adding width", t_wid); + total_width += t_wid; + // these_panel_widths.push(t_wid); + } +// if (this_width != these_panel_widths[this_panel_index]) { +// errorLog("error: width", this_width, "not on list", these_panel_widths) +// } else { +// editorLog("width", this_width, "on list", these_panel_widths) +// } + + editorLog("occ", marginleft, "u", total_width, "pi", marginright, "total", marginleft + total_width + marginright, "ratio", marginright/total_width) + var remaining_space = 100 - (marginleft + total_width + marginright); + editorLog("remaining_space", remaining_space); + +//modify: enlarge, shrink, left, right, ??? done + +// make the data structure better, then delete this comment +// currently style looks like "width: 66%; margin-right: 17%; margin-left: 17%" + editorLog('width', this_width, "mr", marginright, "ml", marginleft); + + editorLog("modifier", modifier); + + var scale_direction = 1; + var moving_direction = 1; + + if (modifier == "enlargeall") { + editorLog("enlarging all", "remaining space", remaining_space); + if (remaining_space >= these_panel_widths.length) { + for (var j=0; j < these_panel_widths.length; ++j) { + these_panel_widths[j] += 1 + } + // probablu the next case handles the first case + } else if (remaining_space + marginleft + marginright >= these_panel_widths.length) { + for (var j=0; j < these_panel_widths.length; ++j) { + these_panel_widths[j] += 1 + } + var missing_length = these_panel_widths.length - remaining_space; + while (missing_length) { + missing_length -= 1; + if (missing_length % 2) { + if (marginleft) { marginleft -= 1 } + else { marginright -= 1 } + } else { + if (marginright) { marginright -= 1 } + else { marginleft -= 1 } + } + } + } else { + editorLog("Problem: not implemented yet") + } + } else if (modifier == "shrinkall") { + for (var j=0; j < these_panel_widths.length; ++j) { + if (these_panel_widths[j]) { these_panel_widths[j] -= 1 } + } + } else if (modifier == "enlarge") { + editorLog("enlarging one"); + if (remaining_space) { these_panel_widths[this_panel_index] += 1 } + } else if (modifier == "shrink") { + editorLog("shrinking one"); + if (these_panel_widths[this_panel_index]) { these_panel_widths[this_panel_index] -= 1 } + } else if (modifier == "leftplus") { + if (remaining_space) { marginleft += 1 } + } else if (modifier == "leftminus") { + if (marginleft) { marginleft -= 1 } + } else if (modifier == "rightplus") { + if (remaining_space) { marginright += 1 } + } else if (modifier == "rightminus") { + if (marginright) { marginright -= 1 } + } + editorLog("now these_panel_widths", these_panel_widths); + +// missing cases?? + + internalSource[this_sbs_id]["marginleft"] = marginleft; + internalSource[this_sbs_id]["marginright"] = marginright; + internalSource[this_sbs_id]["widths"] = these_panel_widths; + +// next is wrong, becuase the sbsrow does not have an id +/* + document.getElementById(this_sbs_id).style.marginLeft = marginleft + "%"; + document.getElementById(this_sbs_id).style.marginRight = marginright + "%"; +*/ + document.getElementById(theid).parentElement.style.marginLeft = marginleft + "%"; + document.getElementById(theid).parentElement.style.marginRight = marginright + "%"; + + for (var j=0; j < these_siblings_list.length; ++j) { + var this_id = these_siblings_list[j]; +// internalSource[this_id]["width"] = these_panel_widths[j]; + var width = rescale(these_panel_widths[j], 100, marginleft, marginright) + // document.getElementById(this_id).style.width = these_panel_widths[j] + "%"; + document.getElementById(this_id).style.width = width + "%"; + } + editorLog("NOW these html siblings",document.getElementById(these_siblings_list[0])," and ", document.getElementById(these_siblings_list[1])) + editorLog("NOW these siblings source", internalSource[these_siblings_list[0]], "and", internalSource[these_siblings_list[1]]); + editorLog("and internalSource[this_id]", internalSource[this_id]); + editorLog("and also internalSource[this_sbs_id]", internalSource[this_sbs_id]); +} + +function move_by_id_local(theid, thehandleid) { + // when moving an object within a page, we create a phantomobject that is manipulated + // the actual movement is handled by move_object(e) + + first_move = true; + + document.getElementById("edit_menu_holder").remove() + document.getElementById(theid).classList.remove("may_select"); + + moved_content = internalSource[theid]; + moved_content_tag = moved_content["sourcetag"]; + ongoing_editing_actions.push(["moved", moved_content_tag, theid]); + moved_parent_and_location = moved_content["parent"]; + editorLog("moving", theid); + editorLog("moved_parent_and_location", moved_parent_and_location); + // code duplicated elsewhere + var where_it_was = internalSource[moved_parent_and_location[0]][ moved_parent_and_location[1] ]; + editorLog("where_it_was", where_it_was); + var object_in_parent = '<&>' + theid + '<;>'; + var where_it_is = where_it_was.replace(object_in_parent, ""); + editorLog("where_it_is ZZ" + where_it_is + "EE"); + internalSource[moved_parent_and_location[0]][ moved_parent_and_location[1] ] = where_it_is; + + // but first, remember the initial location of the object + + moving_object = document.getElementById(theid); + editorLog("moving", moving_object, "within this page"); + editorLog("moving_id", theid); + editorLog("current_editing[tree][0]", current_editing["tree"][0]); + if (moved_content_tag == "li") { + movement_location_neighbors = next_editable_of(current_editing["tree"][0][0], "li-only"); + } else if (tag_type(moved_content_tag) == "p") { + movement_location_neighbors = next_editable_of(current_editing["tree"][0][0], "inner-block"); + } else { + movement_location_neighbors = next_editable_of(current_editing["tree"][0][0], "outer-block"); + } + editorLog("movement_location_neighbors", movement_location_neighbors); + var foundit = false; + var movement_location_tmp = 0; + for (var j=0; j < movement_location_neighbors.length; ++j) { + if (movement_location_neighbors[j] == moving_object) { + movement_location_tmp = j; + // delete the one which is being moved, because + // we are making a list of slots to place it, and its slot will still be there + // isn't there a better way to delete one item from a list? + movement_location_neighbors.splice(j, 1); + foundit = true; + break; + } + } + + if (!foundit) { errorLog("serious error: trying to move an object that is not movable", theid) } + + editorLog("movement_location_tmp", movement_location_tmp); + // a paragraph by itself in an item or a statement can have a new paragraph before or after it + movement_location_options = [[movement_location_neighbors[0], "beforebegin"], + [movement_location_neighbors[0], "afterend"]]; + movement_location = 0; + var movement_location_ct = 1; + for (var j=1; j < movement_location_neighbors.length; ++j) { + if (movement_location_tmp == j) { movement_location = movement_location_ct } + movement_location_ct += 1; + if (movement_location_neighbors[j-1].parentElement == movement_location_neighbors[j].parentElement) { + movement_location_options.push([movement_location_neighbors[j], "afterend"]) + } else { + movement_location_ct += 1; + movement_location_options.push([movement_location_neighbors[j], "beforebegin"]) + movement_location_options.push([movement_location_neighbors[j], "afterend"]) + } + } + editorLog("movement_location_ct", movement_location_ct); + editorLog("movement_location", movement_location); + + editorLog("made", movement_location_options.length, "movement_location_options", movement_location_options); + editorLog("from", movement_location_neighbors.length, "movement_location_neighbors", movement_location_neighbors); + + var the_phantomobject = document.createElement('div'); + the_phantomobject.setAttribute("id", "phantomobject"); + the_phantomobject.setAttribute("data-moving_id", theid); + the_phantomobject.setAttribute("data-handle_id", thehandleid); + the_phantomobject.setAttribute("class", "phantomobject move"); + the_phantomobject.setAttribute("tabindex", "-1"); + var these_instructions = '
    ↑

    "shift-tab", or "up arrow", to move up

    '; + these_instructions += '

    "return" or "escape" to set in place

    '; + these_instructions += '
    ↓

    "tab" or "down arrow" to move down

    '; + the_phantomobject.innerHTML = these_instructions; + // if we are moving a p which has parent li, and it is the only p there, then delete the parent li + // note: this will be wrong if there is other non-p siblings inside the li + var moving_object_replace = moving_object; + if (tag_type(moved_content_tag) == "p" && internalSource[moved_parent_and_location[0]]["sourcetag"] == "li") { + // check if that p is the only thing inside the li (so the li is empty when we move the p), and if so, + // remove that li from internalSource and also the HTML, and the reverence to it in internalSource + if (moving_object.parentElement.getElementsByTagName("p").length == 1) { + moving_object_replace = moving_object.parentElement + var now_empty_li_id = moved_parent_and_location[0]; + editorLog("list item now empty:", now_empty_li_id); + var now_empty_li_parent_and_location = internalSource[now_empty_li_id]["parent"]; + var where_it_was = internalSource[now_empty_li_parent_and_location[0]][ now_empty_li_parent_and_location[1] ]; + var object_in_parent = '<&>' + now_empty_li_id + '<;>'; + var where_it_is = where_it_was.replace(object_in_parent, ""); + delete internalSource[now_empty_li_id]; + editorLog("where_it_is II" + where_it_is + "OO"); + internalSource[now_empty_li_parent_and_location[0]][ now_empty_li_parent_and_location[1] ] = where_it_is; + } + } + moving_object_replace.replaceWith(the_phantomobject) + document.getElementById("phantomobject").focus(); +} + +function move_object(e) { + // we have alread set movement_location_options and movement_location + editorLog("movement_location",movement_location); + if ((e.code == "Tab" && e.shiftKey) || e.code == "ArrowUp") { // Shift-Tab up the page + e.preventDefault(); + if (movement_location == 0) { + alert("can't move past the top") + } else { + if (first_move) { first_move = false; } + movement_location -= 1 + } + } else if ((e.code == "Tab" || e.code == "ArrowDown") && !e.shiftKey) { + e.preventDefault(); + if (first_move) { first_move = false; editorLog("did first move") } + if (movement_location == movement_location_options.length - 1) { + alert("can't move past the bottom") + } else { + movement_location += 1 + } + } else if (e.code == "Escape" || e.code == "Enter" || e.code == "ArrowRight") { + e.preventDefault(); + editorLog(" decided where to put moving_object", moving_object); + var id_of_moving_object = document.getElementById('phantomobject').getAttribute("data-moving_id"); + editorLog("moving object started as", internalSource[id_of_moving_object]); + var handle_of_moving_object = document.getElementById('phantomobject').getAttribute("data-handle_id"); + document.getElementById('phantomobject').remove(); + var new_anchor_and_position = movement_location_options[movement_location] + editorLog("new_location_anchor",new_anchor_and_position); + new_anchor_and_position[0].insertAdjacentElement(new_anchor_and_position[1], moving_object); + + // the html appears to be updated, but we still need to update both the internal source: + var new_neighbor_id = new_anchor_and_position[0].id; + editorLog("new_neighbor_id", new_neighbor_id); + editorLog("which has source", internalSource[new_neighbor_id]); + var new_neighbor_rel_pos = new_anchor_and_position[1]; + var [new_neighbor_parent, new_neighbor_location] = internalSource[new_neighbor_id]["parent"]; + var new_neighbor_in_context = internalSource[new_neighbor_parent][new_neighbor_location]; + editorLog("new_neighbor_in_context was", new_neighbor_in_context); + var neighbor_tag = '<&>' + new_neighbor_id + '<;>'; + var neighbor_tag_re = new RegExp(neighbor_tag); + + var moving_object_tag = '<&>' + id_of_moving_object + '<;>'; + if (new_neighbor_rel_pos == "beforebegin") { + // new_neighbor_in_context.replace(neighbor_tag, moving_object_tag + "\n" + neighbor_tag) +// RegExp + new_neighbor_in_context = new_neighbor_in_context.replace(neighbor_tag_re, moving_object_tag + "\n" + neighbor_tag) + } else { + new_neighbor_in_context = new_neighbor_in_context.replace(neighbor_tag_re, neighbor_tag + "\n" + moving_object_tag) + } + editorLog("new_neighbor_in_context is", new_neighbor_in_context); + internalSource[new_neighbor_parent][new_neighbor_location] = new_neighbor_in_context; + internalSource[id_of_moving_object]["parent"] = [new_neighbor_parent, new_neighbor_location]; + editorLog("moving object ended as", internalSource[id_of_moving_object]); + + save_edits(); + + // and the navigation information + make_current_editing_tree_from_id(handle_of_moving_object); + + var most_recent_edit = ongoing_editing_actions.pop(); + recent_editing_actions.unshift(most_recent_edit); + + edit_menu_from_current_editing("entering"); + return + + } else { + editorLog("don't know how to move with", e.code) + } + + editorLog("now movement_location", movement_location); + var the_phantomobject = document.getElementById('phantomobject'); + movement_location_options[movement_location][0].insertAdjacentElement(movement_location_options[movement_location][1], the_phantomobject); + document.getElementById("phantomobject").focus(); +} + +function delete_by_id(theid, thereason) { + // reasons to delete something: author wants it deleted, it is empty, ... + // first delete the specific object + final_added_object = ""; + editorLog("deleting by theid", theid, "with content", internalSource[theid]); + var deleted_content = internalSource[theid]; + var parent_and_location = deleted_content["parent"]; + delete internalSource[theid]; + editorLog("deleted", theid, "so", theid in internalSource, "now", internalSource); + // and save what was deleted + if (theid in old_content) { + old_content[theid].push(deleted_content) + } else { + old_content[theid] = [deleted_content] + } + if (thereason != "newempty") { // not sure newempty can still happen + ongoing_editing_actions.push(["deleted ", deleted_content["sourcetag"], theid]); + } + // update the parent of the object + var current_level = current_editing["level"]; + var where_it_was = internalSource[parent_and_location[0]][ parent_and_location[1] ]; + var object_in_parent = '<&>' + theid + '<;>'; + var where_it_is = where_it_was.replace(object_in_parent, ""); + editorLog("where_it_is ZZ" + where_it_is + "EE"); + internalSource[parent_and_location[0]][ parent_and_location[1] ] = where_it_is; + // if the parent is empty, delete it + if (!(where_it_is.trim()) && (parent_and_location[1] == "content" || parent_and_location[1] == "statement")) { + editorLog(" deleted from within", internalSource[parent_and_location[0]]); + document.getElementById(theid).removeAttribute("data-editable"); // so it is invisible to next-editable-of as we delete its parent + if (internalSource[parent_and_location[0]][ "sourcetag" ] == "li") { + editorLog("not going up a level, because it is a list element") + } else { + current_editing["level"] -= 1; + } + delete_by_id(parent_and_location[0], thereason) + } else { // else, because the parent is going to be deleted, so no need to delete the child + // delete from the html + var current_index = current_editing["location"][current_level] + 1; // +1 because the deleted item is still in the current_editing tree + if (thereason == "empty" || thereason == "newempty") { + editorLog("removing from DOM", document.getElementById(theid)); + document.getElementById(theid).remove(); + current_index -= 1; + editorLog("current_index", current_index, "current_level", current_level); + editorLog(current_editing["tree"][ current_level ]); + // hack because adding a list (inside a defn?) is not updating the tree properly + if (current_index > current_editing["tree"][ current_level ].length - 1) { + current_index = current_editing["tree"][ current_level ].length - 1 + } + if (current_editing["tree"][ current_level ][ current_index ].id == theid) { + current_editing["tree"][ current_level ].splice(current_index, 1) + } + editorLog("empty or newempty", thereason) + } else { + editorLog("deleting for another reason", thereason) + document.getElementById("edit_menu_holder").remove() + document.getElementById(theid).setAttribute("id", "deleting"); + document.getElementById("deleting").removeAttribute("data-editable"); // so it is invisible to next-editable-of + setTimeout(() => { document.getElementById("deleting").remove(); }, 600); + } + + if (current_index >= current_editing["tree"][ current_level ].length) { + current_index = current_editing["tree"][ current_level ].length - 2; + } + editorLog("current_index", current_index, "in", current_editing["tree"][ current_level ]); + editorLog("object of interest", current_editing["tree"][ current_level ][ current_index ]); + editorLog("current_level", current_level, "on", current_editing["tree"]); + make_current_editing_tree_from_id(current_editing["tree"][ current_level ][ current_index].id); + edit_menu_from_current_editing("entering"); + } + save_edits() +} + + +var internalSource = { // currently the key is the HTML id + "root_data": {"id": "page-1", "number_base": "0.1" } +} + +/* top_level_id is a mistake: just use internalSource.root_data.id */ +var top_level_id = internalSource["root_data"]["id"]; + +var current_editing = { + "level": 0, + "location": [0], + "tree": [ [document.getElementById(top_level_id)] ] +} + + +function create_local_menu() { + +// this does not work, but local menu navigator would not have worked either + + editorLog("make local edit menu for", this_obj_id); + var local_menu_holder = document.createElement('div'); + local_menu_holder.setAttribute('id', 'local_menu_holder'); + local_menu_holder.setAttribute('tabindex', '-1'); + editorLog("adding local menu for", this_obj_id); + document.getElementById(this_obj_id).insertAdjacentElement("afterbegin", local_menu_holder); + + var enter_option = document.createElement('ol'); + enter_option.setAttribute('id', 'edit_menu'); + enter_option.setAttribute('class', 'edit_menu'); + + enter_option.innerHTML = menu_options_for(this_obj_id, "XunusedX", "base"); + + document.getElementById("local_menu_holder").insertAdjacentElement("afterbegin", enter_option); + +} + +function extract_internal_contents(some_text) { + + // some_text must be a paragraph with mixed content only contining + // non-nested tags + the_text = some_text; + editorLog(" xxxxxxxxxx the_text is", the_text); + editorLog("extract_internal_contents"); + + // delete class information + the_text = the_text.replace(/ class="[^"]"/g, ""); + + // inline from previous editing + editorLog("extracting where 'data-editable...'"); + the_text = the_text.replace(/<([^<]+) data-editable="[^"]+" tabindex="-1">(.*?)<[^<]+>/g, save_internal_cont); + editorLog("extracting where 'contenteditable...'"); + the_text = the_text.replace(/<([^<]+) contenteditable="false">(.*?)<[^<]+>/g, save_internal_cont); + // new $math$ + editorLog("extracting new $math$"); + the_text = the_text.replace(/(^|\s)\$([^\$]+)\$(\s|$|[.,!?;:])/mg, extract_new_math); + // new \\(math\\) + editorLog("extracting new \\(math\\)"); + the_text = the_text.replace(/(^|.)\\\(([^\$]+)\\\)(.|$)/g, extract_new_math); + // new math + editorLog("extracting new math"); + the_text = the_text.replace(/(^|.)<m>(.*?)<\/m>(.|$)/g, extract_new_math); + + // "..." to , which will then be processed + the_text = the_text.replace(/\.\.\./g, '<ellipsis\/>'); + // same for etc + the_text = the_text.replace(/(\s)etc\.?([^a-zA-Z])/g, '$1<etc\/>$2'); + + for (var j=0; j < inline_abbrev.length; ++j) { + var this_tag = inline_abbrev[j]; + editorLog("this_tag", this_tag); + var this_tag_search = "<(" + this_tag + ")\\/>"; + editorLog("searching for", this_tag_search); + var this_tag_search_re = new RegExp(this_tag_search,"g"); + the_text = the_text.replace(this_tag_search_re, extract_new_inline) + } + + // "quote" to quote, which will then be processed + the_text = the_text.replace(/(^|\s)"([^"]+)"(\s|$|[.,!?;:])/g, '$1<q>$2<\/q>$3'); + the_text = the_text.replace(/(^|\s)“([^”]+)”(\s|$|[.,!?;:])/g, '$1<q>$2<\/q>$3'); + the_text = the_text.replace(/(^|\s)‘([^’]+)’(\s|$|[.,!?;:])/g, '$1<q>$2<\/q>$3'); + + for (var j=0; j < inline_tags.length; ++j) { + var this_tag = inline_tags[j]; + editorLog("this_tag", this_tag); + var this_tag_search = "<(" + this_tag + ") *>" + "(.*?)" + "<\\/" + this_tag + ">"; + editorLog("searching for", this_tag_search); + var this_tag_search_re = new RegExp(this_tag_search,"g"); + the_text = the_text.replace(this_tag_search_re, extract_new_inline) + } + + return the_text +} + +function extract_new_math(match, sp_before, math_content, sp_after) { + var new_math_id = randomstring(); + internalSource[new_math_id] = { "xml:id": new_math_id, "sourcetag": "m", + "content": math_content} + return sp_before + "<&>" + new_math_id + "<;>" + sp_after +} +function extract_new_inline(match, the_tag, the_content) { + var new_id = randomstring(); + editorLog("extracting", the_content, "inside", the_tag); + internalSource[new_id] = { "xml:id": new_id, "sourcetag": the_tag, + "content": the_content}; + return "<&>" + new_id + "<;>" +} +function extract_new_abbrev(match, the_tag) { + var new_id = randomstring(); + editorLog("extracting", the_content, "inside", the_tag); + internalSource[new_id] = { "xml:id": new_id, "sourcetag": the_tag}; + return "<&>" + new_id + "<;>" +} + +// rename this next function +function save_internal_cont(match, contains_id, the_contents) { + this_id = contains_id.replace(/.*id="(.+?)".*/, '$1'); + + editorLog("id", this_id, "now has contents", the_contents); + if ("content" in internalSource[this_id]) { // not all objects have content + internalSource[this_id]["content"] = the_contents; + } else if (internalSource[this_id]["sourcetag"] == "xref") { + // this needs work once we have text="custom" references + internalSource[this_id]["ref"] = the_contents; + } + editorLog("all of it is now", internalSource[this_id]); + return "<&>" + this_id + "<;>" +} +function assemble_internal_version_changes(object_being_edited) { + editorLog("in assemble_internal_version_changes"); + editorLog("current active element to be saved", object_being_edited); + editorLog("which has parent", object_being_edited.parentElement); + editorLog("whose age is", object_being_edited.parentElement.getAttribute("data-age")); + + var oldornew = object_being_edited.parentElement.getAttribute("data-age"); + if (!oldornew) { oldornew = object_being_edited.getAttribute("data-age") } + editorLog(" OLDorNEW", oldornew); + + var possibly_changed_ids_and_entry = []; + var nature_of_the_change = ""; + +// var object_being_edited = document.activeElement; + var location_of_change = object_being_edited.parentElement; + var this_arrangement_of_objects = ""; + + if (object_being_edited.classList.contains("paragraph_input")) { + editorLog("found paragraph_input"); + nature_of_the_change = "replace"; + var paragraph_content = object_being_edited.innerHTML; + // editorLog("paragraph_content from innerHTML", paragraph_content); + paragraph_content = paragraph_content.trim(); + + var cursor_location = object_being_edited.selectionStart; + + editorLog("cursor_location", cursor_location, "out of", paragraph_content.length, "paragraph_content", paragraph_content); + + var parent_and_location = [object_being_edited.getAttribute("data-parent_id"), object_being_edited.getAttribute("data-parent_component")]; + editorLog("parent_and_location", parent_and_location); + editorLog("of ", object_being_edited); + + var prev_id = object_being_edited.getAttribute("data-source_id"); + editorLog("prev_id", prev_id); + editorLog("which contains", internalSource[prev_id]); + + // need to replace the below by split_paragraphs + + // does the textbox contain more than one paragraph? + var paragraph_content_list = paragraph_content.split("

    "); + editorLog("there were", paragraph_content_list.length, "paragraphs, but some may be empty"); + for (var j=0; j < paragraph_content_list.length; ++j) { + editorLog("paragraph", j, "begins", paragraph_content_list[j].substring(0,20)) + } + + var paragraph_content_list_trimmed = []; + + for (var j=0; j < paragraph_content_list.length; ++j) { + // probably each paragraph is wrapped in meaningless div tags + var this_paragraph_contents_raw = paragraph_content_list[j]; + this_paragraph_contents_raw = this_paragraph_contents_raw.replace(/<\/div>
    /g, "\n"); + this_paragraph_contents_raw = this_paragraph_contents_raw.replace(/
    /g, ""); + this_paragraph_contents_raw = this_paragraph_contents_raw.replace(/<\/div>/g, ""); + this_paragraph_contents_raw = this_paragraph_contents_raw.replace(/ /g, " "); + this_paragraph_contents_raw = this_paragraph_contents_raw.replace(/ +
    /g, "\n"); + this_paragraph_contents_raw = this_paragraph_contents_raw.replace(/
    /g, "\n"); + this_paragraph_contents_raw = this_paragraph_contents_raw.trim(); + if (!this_paragraph_contents_raw) { editorLog("empty paragraph") } + else { paragraph_content_list_trimmed.push(this_paragraph_contents_raw) } + // editorLog("this_paragraph_contents_raw", this_paragraph_contents_raw); + editorLog("done transforming paragraph", j, "with object_being_edited",object_being_edited); + editorLog("which has contents", this_paragraph_contents_raw) + } + + if (!paragraph_content_list_trimmed.length ) { + // empty, so insert it and delete it later + paragraph_content_list_trimmed = [""]; + } + + for (var j=0; j < paragraph_content_list_trimmed.length; ++j) { + editorLog("_trimmed paragraph", j, "begins", paragraph_content_list_trimmed[j].substring(0,20)) + } + for (var j=0; j < paragraph_content_list_trimmed.length; ++j) { + var this_paragraph_contents = paragraph_content_list_trimmed[j]; + editorLog("this_paragraph_contents", this_paragraph_contents.substring(0,20)); + if (j == 0 && prev_id) { + if (prev_id in internalSource) { + // the content is referenced, so we update the referenced content + // need to check internal content, such as em or math + this_paragraph_contents = extract_internal_contents(this_paragraph_contents); + if (internalSource[prev_id]["content"] != this_paragraph_contents) { + if (internalSource[prev_id]["content"]) { + ongoing_editing_actions.push(["changed", "p", prev_id]); + } else if (this_paragraph_contents) { + ongoing_editing_actions.push(["new", "p", prev_id]); + } + internalSource[prev_id]["content"] = this_paragraph_contents; + editorLog("changed content of", prev_id) + } else if (!this_paragraph_contents) { // adding an empty paragraph + ongoing_editing_actions.push(["empty", "p", prev_id]); + } else { + // this means the contents are nonempty and unchanged, so don't record is as a change + } + possibly_changed_ids_and_entry.push([prev_id, "content", oldornew]); + this_arrangement_of_objects = internalSource[parent_and_location[0]][parent_and_location[1]]; + } else { + errorLog("error: existing tag from input", prev_id, "not in internalSource") + } + } else { // a newly created paragraph + var this_object_internal = {"sourcetag": "p", "title": ""}; //p don't have title + this_object_label = randomstring(); + this_object_internal["xml:id"] = this_object_label; + this_object_internal["parent"] = parent_and_location; + + // put the new p after the previous p in the string describing the neighboring contents + var object_before = new RegExp('(<&>' + prev_id + '<;>)'); + this_arrangement_of_objects = this_arrangement_of_objects.replace(object_before, '$1' + '\n<&>' + this_object_label + '<;>'); + prev_id = this_object_label; + + this_paragraph_contents = extract_internal_contents(this_paragraph_contents); + this_object_internal["content"] = this_paragraph_contents; + internalSource[this_object_label] = this_object_internal + editorLog("just inserted at label", this_object_label, "content starting", this_paragraph_contents.substring(0,11), "which is now", internalSource[this_object_label]); + ongoing_editing_actions.push(["added", "p", this_object_label]); +// here is where we can record that somethign is empty, hence should be deleted + possibly_changed_ids_and_entry.push([this_object_label, "content", "new"]); + } + } + editorLog("parent_and_location", parent_and_location); + editorLog("this_arrangement_of_objects was", internalSource[parent_and_location[0]][parent_and_location[1]]); + internalSource[parent_and_location[0]][parent_and_location[1]] = this_arrangement_of_objects; + editorLog("this_arrangement_of_objects is", this_arrangement_of_objects); + } else if (object_being_edited.classList.contains("displaymath_input")) { + editorLog("found displaymath_input"); + nature_of_the_change = "replace"; + var paragraph_content = object_being_edited.innerHTML; + // editorLog("paragraph_content from innerHTML", paragraph_content); + paragraph_content = paragraph_content.trim(); + + var cursor_location = object_being_edited.selectionStart; + + editorLog("cursor_location", cursor_location, "out of", paragraph_content.length, "paragraph_content", paragraph_content); + + var parent_and_location = [object_being_edited.getAttribute("data-parent_id"), object_being_edited.getAttribute("data-parent_component")]; + + editorLog("parent_and_location", parent_and_location); + editorLog("of ", object_being_edited); + + var prev_id = object_being_edited.getAttribute("data-source_id"); + editorLog("prev_id", prev_id); + editorLog("which contains", internalSource[prev_id]); + + // textbox may contain more than one paragraph + var paragraph_content_list = split_paragraphs(paragraph_content); + var this_paragraph_contents = paragraph_content_list.join("\n\\cr\n"); + this_paragraph_contents = extract_internal_contents(this_paragraph_contents); + + if (!internalSource[prev_id]["content"]) { + ongoing_editing_actions.push(["new", "md", prev_id]); + } else { + ongoing_editing_actions.push(["changed", "md", prev_id]); + } + + internalSource[prev_id]["content"] = this_paragraph_contents + possibly_changed_ids_and_entry.push([prev_id, "content", oldornew]); + + } else if (object_being_edited.getAttribute('data-component') == "title" || + object_being_edited.getAttribute('data-component') == "caption") { + + var this_content_type = object_being_edited.getAttribute('data-component'); + nature_of_the_change = "replace"; + var line_being_edited = object_being_edited; + var line_content = line_being_edited.innerHTML; + line_content = line_content.trim(); + editorLog("the content (is it a title or caption?) is", line_content); + var owner_of_change = object_being_edited.getAttribute("data-source_id"); + var component_being_changed = object_being_edited.getAttribute("data-component"); + editorLog("component_being_changed", component_being_changed, "within", owner_of_change); + if (internalSource[owner_of_change][component_being_changed]) { + ongoing_editing_actions.push(["changed", this_content_type, owner_of_change]); + } else { + ongoing_editing_actions.push(["added", this_content_type, owner_of_change]); + } + // update the title of the object + internalSource[owner_of_change][component_being_changed] = line_content; + possibly_changed_ids_and_entry.push([owner_of_change, this_content_type]); + + } else if (object_being_edited.classList.contains("image_source")) { + // currently this only handles images by URL. + // later do the case of uploading an image. + var image_src = object_being_edited.innerHTML; + + // what is the right way to do this? + image_src = image_src.replace(/
    /g, ""); + image_src = image_src.replace(/<\/div>/g, ""); + image_src = image_src.trim(); + editorLog("changing image src to", image_src); + + var image_being_changed = object_being_edited.getAttribute("data-source_id"); + editorLog("image_being_changed ", image_being_changed); + + if (internalSource[image_being_changed]["source"]) { + ongoing_editing_actions.push(["changed", "source", image_being_changed]); + } else { + ongoing_editing_actions.push(["added", "source", image_being_changed]); + } + internalSource[image_being_changed]["source"] = image_src; + editorLog("image being changed is", internalSource[image_being_changed]); + // possibly_changed_ids_and_entry.push([owner_of_change, "image"]); + possibly_changed_ids_and_entry.push([image_being_changed, "image"]); + + + } else if (inline_tags.includes(object_being_edited.tagName.toLowerCase())) { + editorLog(object_being_edited, "is inline, so processing parent"); + return assemble_internal_version_changes(object_being_edited.parentElement) + } else if (object_being_edited.classList.contains("edit_math_row")) { + nature_of_the_change = "replace"; + var line_being_edited = object_being_edited; + var line_content = line_being_edited.innerHTML; + line_content = line_content.trim(); + editorLog("the content (is it a title?) is", line_content); + var owner_of_change = object_being_edited.getAttribute("id"); + // var component_being_changed = object_being_edited.getAttribute("data-component"); + var component_being_changed = "content"; + editorLog("component_being_changed", component_being_changed, "within", owner_of_change); + // update the title of the object + if (internalSource[owner_of_change][component_being_changed]) { + ongoing_editing_actions.push(["changed", "mrow", owner_of_change]); + } else { + ongoing_editing_actions.push(["added", "mrow", owner_of_change]); + } + internalSource[owner_of_change][component_being_changed] = line_content; + possibly_changed_ids_and_entry.push([owner_of_change, "mrow"]); + + } else { + errorLog("trouble editing", object_being_edited, "AAA", object_being_edited.tagName.toLowerCase(), "not in", inline_tags); + alert("don;t know how to assemble internal_version_changes of " + object_being_edited.tagName) + } + editorLog("finished assembling internal version, which is now:",internalSource); + editorLog(" NUMBER of things chagnged:", possibly_changed_ids_and_entry.length); + return [nature_of_the_change, location_of_change, possibly_changed_ids_and_entry] +} + +function wrap_tag(tag, content, attribute_values) { + // tag is either an XML tag name, or [opening_tag, closing_tag] + + // layout: inline or block or title + // is this the right place to handle empty content? +// editorLog("calling wrap_tag", "tag", tag, "content", content, "attribute_values", attribute_values); + if (!content && !tag) { return "" } + if (!content && !always_empty_tags.includes(tag) && !allowed_empty_tags.includes(tag)) { return "" } + if (!tag) { return content } + + var opening_tag = closing_tag = ""; + + if (typeof tag == "string") { + if (tag) { + opening_tag = "<" + tag; + for (var j=0; j < attribute_values.length; ++j) { + opening_tag += ' ' + attribute_values[j] + } + closing_tag = ""; + } + if (!content && (always_empty_tags.includes(tag) || allowed_empty_tags.includes(tag))) { + opening_tag += "/>"; + closing_tag = ""; + } else { + opening_tag += ">"; + } + + if (tag_display["inline"].includes(tag)) { + // do nothing + } else if (tag_display["title"].includes(tag)) { + opening_tag = "\n" + opening_tag; + closing_tag = closing_tag + "\n" + } else if (tag_display["block-tight"].includes(tag)) { + // do nothing + } else { //the default + opening_tag = "\n" + opening_tag + "\n"; + if (closing_tag) { + closing_tag = "\n" + closing_tag + "\n" + } + } + } else if (tag) { + [opening_tag, closing_tag] = tag + for (var j=0; j < attribute_values.length; ++j) { + opening_tag += ' ' + attribute_values[j] + } + } else { + opening_tag = ""; + closing_tag = ""; + } + if (!opening_tag) { opening_tag = "" } + if (!closing_tag) { closing_tag = "" } + + if (content.includes("N.m")) { editorLog("3 ----- content",content); editorLog("opening_tag", opening_tag) } + + if (opening_tag.startsWith("(.*?)<;>/g, function (match, newid) { + if (newid.startsWith("{")) { + newid = newid.slice(1,-1); + if (newid in output_structure) { + return output_structure[newid] + } else { // don't want to return 'undefined' + return "" + } + } else if (newid.startsWith("(")) { + var this_piece = newid.slice(1,-1); + var this_fcn; + [this_fcn, this_piece] = this_piece.split(","); + return process_value_from_source(this_fcn, this_piece, the_object) + } else { + if (newid in the_object) { + return the_object[newid] + } else { + return "" + } + } + }); + editorLog(" attr_val", attr_val); + if (attr_val && !attr_val.includes('""')) { + output_attributes_values.push(attr_val) + } + } + + editorLog("output_structure", output_tag, "is", output_structure); + editorLog("output_attributes_values", output_attributes_values); + editorLog("output_structure.pieces", output_structure.pieces); + for (var j=0; j < output_structure.pieces.length; ++j) { + var this_piece_output = ""; + var [this_piece, this_tag] = output_structure.pieces[j]; + editorLog("output_structure", output_structure); + // when this_piece is provisional, then this_tag is actually the key for the required content + editorLog(j, "this_piece", this_piece, "this_tag", this_tag, "output_tag", output_tag); + if (this_piece.startsWith("{")) { + this_piece = this_piece.slice(1,-1); + this_piece_output += output_from_source(the_object, objectStructure[this_piece][format], format); + editorLog("wrapping in bracketed tag", this_tag); + the_answer += wrap_tag(this_tag, this_piece_output, []) + } else if (this_piece.startsWith("%")) { + // need to distinguish between the case where this object exists, + // and when it does not exist and we want a placeholder + this_piece = this_piece.slice(1,-1); + editorLog("% % % % % % % % % ", this_piece, "this_tag", this_tag, "the_object", the_object); + editorLog("% % % % % % % % % ", the_object[this_piece]); + editorLog("% % % % % % % % % ", the_object[this_tag]); + if (the_object[this_tag]) { + // var sub_object = {}; + +// Object.assign(sub_object, the_object); + + // sub_object['type-contained'] = this_tag; + // editorLog("sub_object", sub_object); + // this_piece_output = output_from_source(sub_object, objectStructure[this_piece][format], format); + this_piece_output = expand_condensed_source_html(the_object[this_tag],"html?"); + // this_piece_output = output_from_source(sub_object, objectStructure[this_piece][format], format); + the_answer += this_piece_output; + editorLog("this piece exists", this_piece, "this_piece_output", this_piece_output) + } else { + editorLog("making placeholder for", this_piece); + the_answer += wrap_tag("div", "", ['class="placeholder ' + this_piece + '"', 'data-parent_id="' + the_object['xml:id'] + '"', 'data-has="' + this_piece + '"', 'tabindex="-1"', 'data-editable="123456"', 'data-placeholder=""']) + } + } else if (this_piece.startsWith("(")) { + editorLog("whole this_piece", this_piece); + this_piece = this_piece.slice(1,-1); + var this_fcn; + [this_fcn, this_piece] = this_piece.split(","); + var this_content = process_value_from_source(this_fcn, this_piece, the_object) + editorLog(j, "parenthesized content", this_content, "this_piece", this_piece, "XX", this_tag) + the_answer += wrap_tag(this_tag, this_content, []) + } else if (this_piece in the_object) { + this_piece_output = output_from_text(the_object[this_piece], format); + if (format == "pretext" && output_tag == "md" && this_piece == "content") { + // convert \cr to mrow + this_piece_output = this_piece_output.replace(/\s*\\cr\b\s*/g, "\n\n"); + this_piece_output = "\n" + this_piece_output + "\n"; + } + the_answer += wrap_tag(this_tag, this_piece_output, []) + } else { + editorLog("missing piece:", this_tag, "with no", this_piece) + } + } + + // pretty print the output + if (format == "pretext" && !(inline_tags.includes(output_tag)) && !(inline_math.includes(output_tag))) { + the_answer = the_answer.replace(/(^|\n)( *(\w|<))/g, "$1 $2"); + } + the_answer = wrap_tag(output_tag, the_answer, output_attributes_values) +// editorLog("now the answer is", the_answer); + + return the_answer +} + +function output_from_text(text, format) { + editorLog("output_from_text of ", text, "with format", format); + if (text.includes("<&>")) { + // return text.replace(/\s*<&>(.*?)<;>\s*/g, function (match, newid) { return output_from_id(match, newid, format)}) + return text.replace(/<&>(.*?)<;>/g, function (match, newid) { return output_from_id(match, newid, format)}) + } else { + return text + } +} + +function output_from_id(match, the_id, format) { + var the_answer = ""; + editorLog("expanding the_id", the_id); + debugLog("expanding the_id", the_id); + var the_object = internalSource[the_id]; + if (!the_object) { + errorLog("error: no content for", the_id); + return the_id + } + var src_tag = the_object.sourcetag; + editorLog("the_object",the_object); + var output_structure; + if (src_tag in objectStructure) { + output_structure = objectStructure[src_tag][format]; + } else { + errorLog("error: unknown structure:", src_tag); + // so make reasonable assumptions about the structure + output_structure = { + "tag": src_tag, + "pieces": [["content", ""]] + } + } + + editorLog("output_structure", output_structure); + // var output_tag = output_structure.tag; + + the_answer = output_from_source(the_object, output_structure, format); + + return the_answer +} + +function expand_condensed_source_html(text, context) { + editorLog("iiiiiii in expand_condensed_source_html"); + if (text.includes("<&>")) { + editorLog(" qqqqq expand_condensed_source_html", text); + if (context == "edit") { + return text.replace(/<&>(.*?)<;>/g,expand_condensed_src_edit) + } else { + return text.replace(/<&>(.*?)<;>/g,expand_condensed_src_html) + } + } else { + editorLog("returning text XX" + text.substring(0,17) + "YY"); + editorLog("returning from expand_condensed_source_html"); + return text + } +} +function expand_condensed_src_html(match, the_id) { + return html_from_internal_id(the_id, "inner") +} +function expand_condensed_src_edit(match, the_id) { + return html_from_internal_id(the_id, "edit") +} + +function html_from_internal_id(the_id, is_inner) { + // the outer element needs to be constructed as document.createElement + // but the inner content is just plain text + // maybe saying it better: sometimes we want to create an object and + // insert it into the DOM. Other times we just want to construct the + // HTML markup fo rthe object and return that. + var the_object = internalSource[the_id]; + editorLog("making html of", the_object, "is_inner", is_inner, "the_id", the_id); + var sourcetag = the_object["sourcetag"]; + editorLog("which has tag", sourcetag); + + editorLog("m in inline_math", inline_math.includes("m")); + + var html_of_this_object; + var the_html_objects = []; + + if (sourcetag == "image") { + html_of_this_object = output_from_id("", the_id, "html"); + editorLog("html_of_this_object", html_of_this_object); + the_html_objects.push(html_of_this_object); + } else if (tag_type(sourcetag) == "p") { + html_of_this_object = output_from_id("", the_id, "html"); + editorLog("html_of_this_object", html_of_this_object); + the_html_objects.push(html_of_this_object); + } else if (inline_math.includes(sourcetag) && is_inner == "edit") { + // here we are assuming the tag is 'm' + var opening_tag = ''; + return opening_tag + spacemath_to_tex(the_object["content"]) + closing_tag + } else if (["me","men"].includes(sourcetag) && is_inner == "edit") { + var opening_tag = '
    '; + return opening_tag + spacemath_to_tex(the_object["content"]) + closing_tag + } else if (["md","mdn"].includes(sourcetag) && is_inner == "edit") { + var opening_tag = '
    '; + this_content = the_object["content"].replace(/<&>(.*?)<;>/g,expand_condensed_src_edit); + this_content = this_content.replace("MROWsepARATOR", "\n yyyy \n"); + return opening_tag + this_content + closing_tag + } else if (["mrow"].includes(sourcetag) && is_inner == "edit") { + var opening_tag = '
    '; + return opening_tag + "MROW" + spacemath_to_tex(the_object["content"]) + closing_tag + } else if (sourcetag == "xref" && is_inner == "edit") { + // here we are assuming the tag is 'm' + var opening_tag = ''; + return opening_tag + the_object["ref"] + closing_tag + } else { + html_of_this_object = output_from_id("", the_id, "html"); + editorLog("html_of_this_object", html_of_this_object); + the_html_objects.push(html_of_this_object); + + } + + editorLog(" RRRR returning the_html_objects", the_html_objects); + +// the_html_objects = the_html_objects.replace("MROWsepARATOR", "\n\\cr\n"); + return the_html_objects +} + +function insert_html_version(these_changes) { + + var nature_of_the_change = these_changes[0]; + var location_of_change = these_changes[1]; + var possibly_changed_ids_and_entry = these_changes[2]; + + editorLog("nature_of_the_change", nature_of_the_change); + editorLog("location_of_change", location_of_change); + editorLog("possibly_changed_ids_and_entry", possibly_changed_ids_and_entry); + + if (!possibly_changed_ids_and_entry.length) { + editorLog("nothing to change"); + // return "" + } + // we make HTML version of the objects with ids possibly_changed_ids_and_entry, + // and then insert those into the page. + +// here is where we detect deleting? +// or is that after this function is done? + if (nature_of_the_change != "replace") { + editorLog("should be replace, since it is the edit form we are replacing"); + } + + var object_as_html = ""; + var this_object_id, this_object_entry, this_object_oldornew, this_object; + + editorLog(" there are", possibly_changed_ids_and_entry.length, "items to process"); + + for (var j=0; j < possibly_changed_ids_and_entry.length; ++j) { + this_object_id = possibly_changed_ids_and_entry[j][0]; + this_object_entry = possibly_changed_ids_and_entry[j][1]; + this_object_oldornew = possibly_changed_ids_and_entry[j][2]; + editorLog("j=", j, "this thing", possibly_changed_ids_and_entry[j]); + this_object = internalSource[this_object_id]; + editorLog(j, "this_object", this_object); + if (tag_type(this_object["sourcetag"]) == "p" || this_object["sourcetag"] == "li" || tag_type(this_object["sourcetag"]) == "md") { + + var this_new_object = html_from_internal_id(this_object_id); + editorLog("inserting",this_new_object,"before",location_of_change); + location_of_change.insertAdjacentHTML('beforebegin', this_new_object[0]); + object_as_html = document.getElementById(this_object_id); + + } else if (this_object_entry == "title") { + var object_as_html = document.createElement('span'); + object_as_html.setAttribute("class", "title"); + object_as_html.setAttribute('data-editable', 20); + object_as_html.setAttribute('tabindex', -1); + // next line should apply a transform to the source + object_as_html.innerHTML = this_object[this_object_entry]; + editorLog("inserting",object_as_html,"before",location_of_change); + // location_of_change is the .header . We want it to be the .title + location_of_change = location_of_change.querySelector("#actively_editing"); + editorLog("now location_of_change",location_of_change); + location_of_change.insertAdjacentElement('beforebegin', object_as_html); + } else if (this_object_entry == "caption") { + console.log("Error: don't know what to do with 'caption'") + } else if (this_object_entry == "image") { + editorLog("image, this_object", this_object); + var this_new_object = html_from_internal_id(this_object_id); + editorLog("inserting",this_new_object,"before",location_of_change); + location_of_change.insertAdjacentHTML('beforebegin', this_new_object[0]); + object_as_html = document.getElementById(this_object_id); + + } else { + editorLog("trouble making", this_object); + } + MathJax.typesetPromise(); +// MathJax.Hub.Queue(['Typeset', MathJax.Hub, this_object_id]); + } + location_of_change.remove(); + + editorLog("returning from insert html version", object_as_html); + // call mathjax, in case the new content contains math + return object_as_html // the most recently added object, which we may want to + // do something, like add an editing menu +} // insert html version + +function save_edits() { + + var currentState = internalSource; + + editorLog("saving", currentState); + localStorage.setObject("savededits", currentState); + return ""; +} + +function previous_editing() { + var old_internal_source = localStorage.getObject("savededits"); + return (old_internal_source || "") +} + +function local_editing_action(e) { + editorLog("in local editing action for" ,e.code); + var most_recent_edit; + if (e.code == "Escape" || e.code == "Enter") { + editorLog("I saw a Rettttt"); + if (document.activeElement.getAttribute('data-component') == "title" || + document.activeElement.getAttribute('data-component') == "caption") { + editorLog("probably saving a ", document.activeElement.getAttribute('data-component')); + e.preventDefault(); + these_changes = assemble_internal_version_changes(document.activeElement); + final_added_object = insert_html_version(these_changes); + most_recent_edit = ongoing_editing_actions.pop(); + recent_editing_actions.unshift(most_recent_edit); + editorLog("most_recent_edit should be title change", most_recent_edit); + editorLog("final_added_object", final_added_object); + this_char = ""; + prev_char = ""; + save_edits(); + + // .title is in a .heading, and neither have an id + make_current_editing_tree_from_id(final_added_object.parentElement.parentElement.id); + edit_menu_from_current_editing("entering"); + +// editing_input_image + } else if (e.code == "Escape" || (prev_char.code == "Enter" && prev_prev_char.code == "Enter") || document.getElementById("editing_input_image")) { + editorLog("need to save"); +editorLog(" HHH current_editing", current_editing, "with active element", document.activeElement); + + e.preventDefault(); + this_char = ""; + prev_char = ""; + these_changes = assemble_internal_version_changes(document.activeElement); + editorLog(" CCC these_changes", these_changes); + editorLog(" CCC0 these_changes[0]", these_changes[0]); + editorLog("ongoing_editing_actions", ongoing_editing_actions); + editorLog("actively_editing", document.getElementById("actively_editing")); +editorLog(" III current_editing", current_editing, current_editing["tree"][current_editing["level"]]); + previous_added_object = final_added_object; + final_added_object = insert_html_version(these_changes); + editorLog("final_added_object, previous_added_object", final_added_object, previous_added_object); +editorLog(" LLL current_editing", current_editing, current_editing["tree"][current_editing["level"]]); + editorLog("the final_added_object", final_added_object); + editorLog("the actively_editing", document.getElementById("actively_editing")); + editorLog("OO", ongoing_editing_actions.length, " ongoing_editing_actions", ongoing_editing_actions); +/* + editorLog("ongoing_editing_actions[0]", ongoing_editing_actions[0]); + editorLog("ongoing_editing_actions[0][2]", ongoing_editing_actions[0][2]); +*/ + // maybe this next if only handles when we delete by removing the letters in a p? + if (these_changes[0] == "empty") { + editorLog("NN", ongoing_editing_actions.length, " ongoing_editing_actions", ongoing_editing_actions); + editorLog("ongoing_editing_actions[0]", ongoing_editing_actions[0]); + editorLog("ongoing_editing_actions[0][2]", ongoing_editing_actions[0][2]); + editorLog(" going to delete", these_changes[2][0]); + // this is sort-of a hack to detext the end of inserting li + if (ongoing_editing_actions.length == 2 && + ongoing_editing_actions[1][0] == "empty" && + ongoing_editing_actions[1][1] == "p" && + ongoing_editing_actions[0][0] == "new" && + ongoing_editing_actions[0][1] == "li") { + ongoing_editing_actions.pop(); // content was empty, so there is no editing action + ongoing_editing_actions.pop(); + delete_by_id(these_changes[2][0][0], "newempty"); + current_editing["location"][current_editing["level"]] -= 1 + final_added_object = previous_added_object; // this approach makes the updating of current_editing moot? + } else { + delete_by_id(these_changes[2][0][0], "empty"); + } + editorLog("MM", ongoing_editing_actions.length, " ongoing_editing_actions", ongoing_editing_actions); + for (var j=0; j= 0; --j) { + editorLog(" X", j, "the_whole_object[j]", the_whole_object[j]); + document.getElementById("actively_editing").insertAdjacentElement("afterend", the_whole_object[j]) + MathJax.typesetPromise(); + // MathJax.Hub.Queue(['Typeset', MathJax.Hub, the_whole_object[j]]); + } + + editorLog("here is where we need to update current_editing", "parent:", this_parent,"which is",document.getElementById(this_parent[0]), "level:", current_editing["level"], "loation:", current_editing["location"], "tree:", current_editing["tree"]); + $("#actively_editing").remove(); + +editorLog(" DDD current_editing", current_editing, current_editing["tree"][current_editing["level"]]); + + } + + most_recent_edit = ["","",""]; + while (ongoing_editing_actions.length) { + most_recent_edit = ongoing_editing_actions.pop(); + recent_editing_actions.unshift(most_recent_edit); + editorLog(" most_recent_edit", most_recent_edit); + } + editorLog(" 8888 final_added_object", final_added_object); + + save_edits() + + // is this in the right place? + editorLog("most_recent_edit", most_recent_edit); + + // sometimes, such as when adding items to a list, you want to + // automatically start adding something else. + // maybe refactor theorem to add proof after? + if (most_recent_edit[1] == "li") { // added to a list, so try adding again + // note that when adding an li, the neichbor is a p within the actual li neighbor + var new_obj = create_object_to_edit("li", document.getElementById(most_recent_edit[2]).firstElementChild, "afterend") + edit_in_place(new_obj, "new"); + editorLog("now editing the assumed new li"); +editorLog(" GGG current_editing", current_editing, current_editing["tree"][current_editing["level"]]); + + } else { + + editorLog("re-making the tree from final_added_object", final_added_object); + make_current_editing_tree_from_id(final_added_object.id); + editorLog("and then adding a menu"); + edit_menu_from_current_editing("entering"); + } + + } else if ( document.getElementById("actively_editing")) { + document.getElementById("actively_editing").remove(); + // were actively editing, and now just re-making the menu + edit_menu_from_current_editing("entering"); + } else { + // default makng the menu + edit_menu_from_current_editing("entering"); + } + } // esc or enter enter enter + editorLog ("processed an enter"); + } // esc or enter + else { + editorLog("e.code was not one of those we were looking for", e) + } + editorLog("leaving local editing action") +} + +function main_menu_navigator(e) { // we are not currently editing + // so we are building the menu, and possibly moving aroung the document, + //for the user to decide what/how to edit + +// There are 3 modes: +// #enter_choice, data-location="next" +// #enter_choice, data-location="stay" +// above means we are deciding whenter to edit/enter/leave and object, or to move on +// #choose_current +// 3rd option means we already have a menu + + if (document.getElementById("enter_choice")) { + theEnterChoice = document.getElementById("enter_choice"); + editorLog("enter_choice", e); + var theMotion = theEnterChoice.getAttribute("data-location"); + var object_of_interest; + if (theMotion == "stay") { + object_of_interest = theEnterChoice.parentElement.previousSibling; + } else { + object_of_interest = theEnterChoice.parentElement.parentElement; + } + editorLog(" MMN: want to", theMotion, "on", object_of_interest, "from", theEnterChoice) + + editorLog("current_editing", current_editing); + editorLog("theEnterChoice", theEnterChoice); + var current_level = current_editing["level"]; + var current_location = current_editing["location"][current_level]; + var current_siblings = current_editing["tree"][current_level]; + editorLog("current_level", current_level, "current_location", current_location, "current_siblings", current_siblings); + + if ((e.code == "Tab" || e.code == "ArrowDown") && !e.shiftKey) { + e.preventDefault(); + if (current_level == 0) { // at the top, so no "next" object + return "" + } + // go to next sibling, or stage to exit if on last sibling + if (current_location == (current_siblings.length - 1)) { // on last sibling + editorLog("on last sibling, level was", current_level,"siblings was", current_siblings, "tree", current_editing["tree"]); + editorLog("current_location was", current_location); + current_level -= 1; + current_location = current_editing["location"][current_level]; + current_editing["level"] = current_level; + current_editing["location"][current_level] = current_location; + current_siblings = current_editing["tree"][current_level]; + editorLog("current_location is", current_location); + editorLog("stay menu A"); + object_of_interest.classList.remove("may_leave"); + object_of_interest.classList.remove("may_elect"); + edit_menu_from_current_editing("leaving") + } else { + editorLog("moving to the next editable sibling"); + editorLog("level was", current_level,"siblings was", current_siblings, "tree", current_editing["tree"]); + editorLog("current_location was", current_location); + editorLog(current_location, "was", current_editing); + current_location += 1; + object_of_interest.classList.remove("may_leave"); + object_of_interest.classList.remove("may_elect"); + editorLog("current_location is", current_location); + editorLog("stay menu B"); + current_editing["location"][current_level] = current_location; + editorLog(current_location, "is", current_editing); + edit_menu_from_current_editing("entering") + } + } else if ((e.code == "Tab" && e.shiftKey) || e.code == "ArrowUp") { // Shift-Tab to prevous object + e.preventDefault(); + // go to previous sibling, or up one if on first sibling + editorLog("Arrow Up:", "current_location", current_location, "current_level", current_level); + if (theMotion == "stay") { // about to leave, to return to the top of that region + edit_menu_from_current_editing("entering") + } else if (current_location == 0) { + if (!current_level) { // already at the top, so nowhere to go, so do nothing + editorLog("at the top, so can't go up"); + return "" + } + current_level -= 1; + current_editing["level"] = current_level; + current_location = current_editing["location"][current_level]; + editorLog("AA new current_location", current_location, " current_editing['tree']", current_editing["tree"]); + editorLog(" current_editing['tree'][0]", current_editing["tree"][0]); + current_siblings = current_editing["tree"][current_level]; + editorLog("current_siblings", current_siblings); + edit_menu_from_current_editing("entering") + } else { + current_location -= 1; + current_editing["location"][current_level] = current_location; + editorLog("current_siblings", current_siblings); + editorLog("BB new current_location", current_location, "at level", current_level, " current_editing['tree']", current_editing["tree"]); + edit_menu_from_current_editing("entering") + } + } else if (e.code == "Escape" || e.code == "ArrowLeft") { + e.preventDefault(); + if (current_level == 0) { return "" } // already at the top, so nowhere to go, so do nothing +// copied from A1 + editorLog("At ArrowLeft, level was", current_level, "with location", current_editing["location"], "and tree", current_editing["tree"][current_level]); + current_level -= 1; + current_editing["level"] = current_level; + current_location = current_editing["location"][current_level]; + current_siblings = current_editing["tree"][current_level]; + editorLog("now level id", current_level, "with location", current_editing["location"], "and tree", current_editing["tree"][current_level]); + edit_menu_from_current_editing("entering") + } else if (e.code == "Enter" || e.code == "ArrowRight") { + e.preventDefault(); + if (theMotion == "stay") { + edit_menu_from_current_editing("entering"); + return "" + } + var edit_submenu = document.createElement('ol'); + edit_submenu.setAttribute('id', 'edit_menu'); + edit_submenu.setAttribute('class', 'edit_menu'); + + var to_be_edited = object_of_interest; + editorLog("to_be_edited", to_be_edited); + edit_submenu.innerHTML = top_menu_options_for(to_be_edited); + $("#enter_choice").replaceWith(edit_submenu); + document.getElementById('choose_current').focus(); + } + editorLog(" Just handled the case of enter_choice"); + return "" + + } else if (document.getElementById("choose_current")) { + var theChooseCurrent = document.getElementById("choose_current"); + var dataLocation = theChooseCurrent.getAttribute("data-location"); // may be null + var dataAction = theChooseCurrent.getAttribute("data-action"); // may be null + var dataModifier = theChooseCurrent.getAttribute("data-modifier"); // may be null + var dataEnv = theChooseCurrent.getAttribute("data-env"); // may be null + // a hack because of how the menus were originally set up + if (dataEnv == "save") { dataAction = "save" } + var dataEnvParent = theChooseCurrent.getAttribute("data-env-parent"); // may be null + var object_of_interest; + if (document.getElementById("edit_menu_holder")) { + object_of_interest = document.getElementById("edit_menu_holder").parentElement + } else if (document.getElementById("local_menu_holder")) { + object_of_interest = document.getElementById("local_menu_holder").parentElement + } else { + editorLog("something is confused: should be a menu, but isn't"); + if (theChooseCurrent.id) { + make_current_editing_tree_from_id(theChooseCurrent.id); + object_of_interest = theChooseCurrent + } else { + make_current_editing_tree_from_id(theChooseCurrent.parentElement.id); + object_of_interest = theChooseCurrent.parentElement + } + editorLog("made entering menu for", object_of_interest); + edit_menu_from_current_editing("entering") + } + current_level = current_editing["level"]; + current_location = current_editing["location"][current_level]; + current_siblings = current_editing["tree"][current_level]; + editorLog("in choose_current", dataLocation, "of", object_of_interest); + editorLog("dataAction ", dataAction); + editorLog("dataLocation ", dataLocation); + // we have an active menu, and have selected an item + // there are three main sub-cases, depending on whether there is a data-location attribute, + // a data-action attribute, or a data-env attribute. + // That is the primary order in which those attributes are considered + // however, some actions (such as Tab and shift-Tab) are the same + // in each sub-case (because all we are doing is moving up and down the + // current list of options), so we handle those first. + + if ((e.code == "Tab" || e.code == "ArrowDown") && !e.shiftKey) { + e.preventDefault(); + next_menu_item = theChooseCurrent.nextSibling; + editorLog("theChooseCurrent", theChooseCurrent, "next_menu_item", next_menu_item); + if (!next_menu_item) { next_menu_item = theChooseCurrent.parentNode.firstChild } + editorLog("theChooseCurrent", theChooseCurrent, "next_menu_item", next_menu_item); + if (theChooseCurrent == next_menu_item) { //only one item on menu, so Tab shold move to the next editable item + // if last editable child, go up one + if (current_location == (current_siblings.length - 1)) { + current_level -= 1; + current_location = current_editing["location"][current_level]; + current_editing["level"] = current_level; + current_editing["location"][current_level] = current_location; + edit_menu_from_current_editing("leaving") + } else { + current_location += 1; + editorLog("single item menu, current_location now", current_location); + current_editing["location"][current_level] = current_location; + edit_menu_from_current_editing("entering"); + } + } + theChooseCurrent.removeAttribute("id"); + editorLog("theChooseCurrent", theChooseCurrent, "next_menu_item", next_menu_item); + next_menu_item.setAttribute("id", "choose_current"); + editorLog("setting focus on",next_menu_item); + next_menu_item.focus(); + } // Tab + else if ((e.code == "Tab" && e.shiftKey) || e.code == "ArrowUp") { // Shift-Tab to prevous object + e.preventDefault(); + editorLog("just saw a", e.code); + editorLog("focus is on", $(":focus")); + editorLog("saw an",e.code); + next_menu_item = theChooseCurrent.previousSibling; + editorLog("W1 theChooseCurrent", theChooseCurrent, "next_menu_item", next_menu_item); + if (!next_menu_item) { next_menu_item = theChooseCurrent.parentNode.lastChild } + editorLog("W2 theChooseCurrent", theChooseCurrent, "next_menu_item", next_menu_item); + if (theChooseCurrent == next_menu_item) { //only one item on menu, so Shift-Tab shold move to previous or up one level + if (current_editing["location"][ current_editing["level"] ] == 0) { + current_editing["level"] -= 1; + } else { + current_editing["location"][ current_editing["level"] ] -= 1; + } + editorLog("single item menu, current_level now", current_level); + edit_menu_from_current_editing("entering"); + } else { + theChooseCurrent.removeAttribute("id"); + editorLog("W3 theChooseCurrent", theChooseCurrent, "next_menu_item", next_menu_item); + theChooseCurrent.classList.remove("chosen"); + next_menu_item.setAttribute("id", "choose_current"); + editorLog("setting focus on",next_menu_item); + next_menu_item.focus(); + } + } + else if (e.code == "Escape" || e.code == "ArrowLeft") { + editorLog("processing ESC"); + editorLog("At ArrowLeft, level was", current_level, "xx", current_editing["level"], "with location", current_editing["location"], "and tree", current_editing["tree"][current_level]); + // I think the next if can never be true, because of how to route keystrokes + if (document.getElementById("local_menu_holder")) { // hack for when the interface gets confused + document.getElementById("local_menu_holder").remove() + } else { + editorLog("W4 theChooseCurrent", theChooseCurrent); + // need to go up one level in the menu + var previous_menu_item = theChooseCurrent.parentElement.parentElement; // li > ol > li + if (previous_menu_item.id == "edit_menu_holder") { + var thenewchoice = ''; + thenewchoice += 'edit or add nearby'; + thenewchoice += ''; + previous_menu_item.innerHTML = thenewchoice + } else { + theChooseCurrent.parentElement.remove(); + previous_menu_item.classList.remove('chosen'); + previous_menu_item.parentElement.classList.remove('past'); + previous_menu_item.setAttribute("id", "choose_current"); + previous_menu_item.focus(); + } + } + } + else if (keyletters.includes(e.code)) { + key_hit = e.code.toLowerCase().substring(3); // remove forst 3 characters, i.e., "key" + editorLog("key_hit", key_hit); + theChooseCurrent = document.getElementById('choose_current'); + editorLog('theChooseCurrent', theChooseCurrent ); + editorLog( $(theChooseCurrent) ); + // there can be multiple data-jump, so use ~= to find if the one we are looking for is there + // and start from the beginning in case the match is earlier (make the second selector better) + if ((next_menu_item = $(theChooseCurrent).nextAll('[data-jump~="' + key_hit + '"]:first')[0]) || + (next_menu_item = $(theChooseCurrent).prevAll('[data-jump~="' + key_hit + '"]:last')[0])) { // check there is a menu item with that key + theChooseCurrent.removeAttribute("id", "choose_current"); + next_menu_item.setAttribute("id", "choose_current"); + next_menu_item.focus(); + } else { + // not sure what to do if an irrelevant key was hit + editorLog("that key does not match any option") + } + } + +// Now only Enter and ArrowRight are meaningful in this context. +// The effect will depend on the other attributes of #choose_current: +// dataLocation, dataAction, dataEnv + + else if (e.code == "Enter" || e.code == "ArrowRight") { + e.preventDefault(); + if (dataLocation) { + if (dataLocation == "enter") { // we are moving down into an object + + editorLog("theChooseCurrent", theChooseCurrent); + var object_to_be_entered = object_of_interest; + editorLog("object_to_be_entered", object_to_be_entered); + object_to_be_entered.classList.remove("may_select"); + object_to_be_entered.classList.remove("may_enter"); + object_to_be_entered.classList.remove("may_leave"); + editorLog('next_editable_of(object_to_be_entered, "children")'); + editableChildren = next_editable_of(object_to_be_entered, "children"); + current_level += 1; + current_editing["level"] = current_level; + current_editing["location"][current_level] = 0; + current_editing["tree"][current_level] = editableChildren; + editorLog("current_editing", current_editing); + + editorLog("object_to_be_entered", object_to_be_entered); + editorLog("with some children", editableChildren); + // put menu on the item at the top of the block_we_are_reentering + // this is a repeat of a Tab case, so consolidate + editorLog("menu place 10"); + editorLog("document.activeElement", document.activeElement); + + editorLog("menu on", editableChildren[0]); + edit_menu_for(editableChildren[0], "entering"); + + return "" + // combine the next with previous, because the ony difference is which object receives focus + } else if (dataLocation == "end") { // move to the end of an object + + editorLog("theChooseCurrent", theChooseCurrent); + var object_to_be_entered = object_of_interest; + editorLog("object_to_be_entered", object_to_be_entered); + object_to_be_entered.classList.remove("may_select"); + object_to_be_entered.classList.remove("may_enter"); + object_to_be_entered.classList.remove("may_leave"); + editorLog('next_editable_of(object_to_be_entered, "children")'); + editableChildren = next_editable_of(object_to_be_entered, "children"); + var num_editableChildren = editableChildren.length; + current_level += 1; + current_editing["level"] = current_level; + // current_editing["location"][current_level] = 0; + current_editing["location"][current_level] = num_editableChildren - 1; + current_editing["tree"][current_level] = editableChildren; + editorLog("current_editing", current_editing); + + editorLog("object_to_be_entered", object_to_be_entered); + editorLog("with some children", editableChildren); + // put menu on the item at the top of the block_we_are_reentering + // this is a repeat of a Tab case, so consolidate + editorLog("menu place 10end"); + editorLog("document.activeElement", document.activeElement); + + editorLog("menu on", editableChildren[num_editableChildren - 1]); + edit_menu_for(editableChildren[num_editableChildren - 1], "entering"); + + return "" + } else if ((dataLocation == "beforebegin") || (dataLocation == "afterend") || (dataLocation == "afterbegin")) { // should be the only other options + theChooseCurrent.parentElement.classList.add("past"); + theChooseCurrent.removeAttribute("id"); + theChooseCurrent.classList.add("chosen"); + + var parent_id = document.getElementById('edit_menu_holder').parentElement.parentElement.id; + if (!parent_id) { + editorLog(document.getElementById('edit_menu_holder').parentElement.parentElement, "has no id, so going down one level"); + parent_id = document.getElementById('edit_menu_holder').parentElement.id; + } + editorLog("making a menu for", parent_id); + var edit_submenu = document.createElement('ol'); + edit_submenu.innerHTML = menu_options_for(parent_id, "", "base"); + editorLog("just inserted inner menu_options_for(" + parent_id + ")", menu_options_for(parent_id, "", "base")); + theChooseCurrent.insertAdjacentElement("beforeend", edit_submenu); + document.getElementById('choose_current').focus(); + editorLog("focus is on", $(":focus")); + + return "" + } else { + editorLog("Error: unknown dataLocation:", dataLocation) + } + } // dataLocation + + else if (dataAction) { + if (dataAction == "edit") { + editorLog("going to edit", object_of_interest); + edit_in_place(object_of_interest, "old"); + } else if (dataAction == "replace") { // no longer used? + editorLog("replace", object_of_interest, "by id", object_of_interest.id); + replace_by_id(object_of_interest.id, "html") + } else if (dataAction == "stop_editing") { + editorLog("stop_editing", object_of_interest, "by id", object_of_interest.id); + replace_by_id(object_of_interest.id, "html") + eraseCookie(chosen_edit_option_key) + } else if (dataAction == "save") { + save_source(); + edit_menu_from_current_editing("entering"); + } else if (dataAction == "resume") { + editorLog("resuming previous editing session"); + resume_editing() + } else if (dataAction == "change-env-to") { + // shoudl use dataEnv ? + var new_env = theChooseCurrent.getAttribute("data-env"); + editorLog("changing environment to", new_env); + // #edit_menu_holder is in span.type, inside .heading, inside article + to_be_edited = document.getElementById('edit_menu_holder').parentElement.parentElement.parentElement; + editorLog("to_be_edited", to_be_edited); + var id_of_object = to_be_edited.id; + var this_object_source = internalSource[id_of_object]; + editorLog("current envoronemnt", this_object_source); + var old_env = internalSource[id_of_object]["sourcetag"]; + internalSource[id_of_object]["sourcetag"] = new_env; + recent_editing_actions.push([old_env, new_env, id_of_object]); + editorLog("the change was", "changed " + old_env + " to " + new_env + " " + id_of_object); + var the_whole_object = html_from_internal_id(id_of_object); + editorLog("B: the_whole_object", the_whole_object); + $("#" + id_of_object).replaceWith(the_whole_object[0]); // later handle multiple additions + editorLog("just edited", $("#" + id_of_object)); + // since we changed an object which is in + editorLog("curent_editing level", current_editing["level"], "with things", current_editing["tree"][current_editing["level"]]); + current_editing["level"] -= 1; + current_editing["tree"][current_editing["level"]] = next_editable_of(document.getElementById(id_of_object).parentElement, "children"); + editorLog("now curent_editing level", current_editing["level"], "with things", current_editing["tree"][current_editing["level"]]); + edit_menu_from_current_editing("entering"); + return "" + + } else if (dataAction == 'change-env') { + // #edit_menu_holder is in span.type, inside .heading, inside article + current_env = document.getElementById('edit_menu_holder').parentElement.parentElement.parentElement; + current_env_id = current_env.id; + + theChooseCurrent.parentElement.classList.add("past"); + theChooseCurrent.removeAttribute("id"); + theChooseCurrent.classList.add("chosen"); + + var edit_submenu = document.createElement('ol'); + editorLog("J1 lookinh for menu options for", current_env_id); + edit_submenu.innerHTML = menu_options_for(current_env_id, "", "change"); + editorLog("just inserted inner menu_options_for(parent_type)", menu_options_for(current_env_id, "", "change")); + theChooseCurrent.insertAdjacentElement("beforeend", edit_submenu); + document.getElementById('choose_current').focus(); + editorLog("focus is on", $(":focus")); + } else if (dataAction == 'modify') { + // #edit_menu_holder is in span.type, inside .heading, inside article + current_env = document.getElementById('edit_menu_holder').parentElement; + current_env_id = current_env.id; + + if (!dataModifier) { + theChooseCurrent.parentElement.classList.add("past"); + theChooseCurrent.removeAttribute("id"); + theChooseCurrent.classList.add("chosen"); + + var edit_submenu = document.createElement('ol'); + // this may only hapen when adjusting workspace: + if (!current_env_id) { + edit_submenu.setAttribute('id', 'edit_menu'); + edit_submenu.setAttribute('class', 'edit_menu'); + current_env_id = current_env.getAttribute("data-parent_id"); + editorLog("current_env_id", current_env_id); + } + editorLog("J2a looking for menu options for", current_env_id); + edit_submenu.innerHTML = menu_options_for(current_env_id, "", "modify"); + editorLog("just inserted inner menu_options_for(parent_type)", menu_options_for(current_env_id, "", "modify")); + if (theChooseCurrent.tagName == "SPAN") { // when adjusting workspace + theChooseCurrent.replaceWith(edit_submenu); + } else { + theChooseCurrent.insertAdjacentElement("beforeend", edit_submenu); + } + document.getElementById('choose_current').focus(); + editorLog("focus is on", $(":focus")); + } else if (dataModifier == "done") { + edit_menu_from_current_editing("entering"); + } else if (dataModifier == "arrows") { + // setup_arrow_modify() // is different for images and SBSs + } else { + // this may only hapen when adjusting workspace: + if (!current_env_id) { + current_env_id = current_env.getAttribute("data-parent_id"); + editorLog("current_env_id from parent", current_env_id); + } + modify_by_id(current_env_id, dataModifier) + } + } else if (dataAction == "move-or-delete") { + // almost all repeats from dataAction == 'change-env' + // except for current_env and menu options for. Consolidate + // maybe also separate actions which give anotehr menu, from actions which change content + current_env = document.getElementById('edit_menu_holder').parentElement; + editorLog("current_env", current_env); + current_env_id = current_env.id; + + theChooseCurrent.parentElement.classList.add("past"); + theChooseCurrent.removeAttribute("id"); + theChooseCurrent.classList.add("chosen"); + + var edit_submenu = document.createElement('ol'); + editorLog("J3 looking for menu options for", current_env_id); + edit_submenu.innerHTML = menu_options_for(current_env_id, "", "move-or-delete"); + editorLog("just inserted inner menu_options_for(parent_type)", menu_options_for(current_env_id, "", "move-or-delete")); + theChooseCurrent.insertAdjacentElement("beforeend", edit_submenu); + document.getElementById('choose_current').focus(); + editorLog("focus is on", $(":focus")); + } else if (dataAction == "delete") { + current_env = document.getElementById('edit_menu_holder').parentElement; + editorLog("current_env", current_env); + current_env_id = current_env.id; + delete_by_id(current_env_id, "choice") + } else if (["move-local", "move-local-p", "move-local-li"].includes(dataAction)) { + current_env = document.getElementById('edit_menu_holder').parentElement; + current_env_id = current_env.id; + handle_env_id = current_env_id; // we were focused on that p, even though + // we are moving an li. Later refocus on p + if (dataAction == "move-local-li") { + current_env.classList.remove("may_select"); + current_env = current_env.parentElement; + current_env.classList.add("may_select"); + current_env_id = current_env.id; + } + editorLog("current_env", current_env); + move_by_id_local(current_env_id, handle_env_id) + } else if (dataAction == "change-title") { + var this_heading = document.getElementById('edit_menu_holder').parentElement.parentElement; + var this_env_id = this_heading.getAttribute("data-parent_id"); + var new_title_form = standard_title_form(this_env_id); + document.getElementById('edit_menu_holder').parentElement.insertAdjacentHTML("afterend",new_title_form); + document.getElementById('edit_menu_holder').parentElement.remove(); + editorLog("change-title in progress") + document.getElementById('actively_editing').focus(); + } else if (dataAction == "change-caption") { + alert("editing captions not implemented yet"); + return ; + var this_caption = document.getElementById('edit_menu_holder').parentElement; + var this_env_id = this_caption.getAttribute("data-source_id"); + var new_title_form = standard_caption_form(this_env_id); + document.getElementById('edit_menu_holder').parentElement.insertAdjacentHTML("afterend",new_title_form); + document.getElementById('edit_menu_holder').parentElement.remove(); + editorLog("change-caption in progress") + document.getElementById('actively_editing').focus(); + } else { + editorLog("unknown dataAction", dataAction); + alert("I don;t know what to do llllllll dataAction " + dataAction) + } + } // dataAction + else if (dataEnv) { // this has to come after dataAction, because if both occur, + // dataAction says to do something, and dataEnv says what to do + e.preventDefault(); // was this handled earlier? + editorLog("in dataEnv", dataEnv); + editorLog("selected a menu item with no action and no location"); + $("#choose_current").parent().addClass("past"); + editorLog("apparently selected", theChooseCurrent); + theChooseCurrent.removeAttribute("id"); + theChooseCurrent.setAttribute('class', 'chosen'); + + // if (dataEnv in inner_menu_for()) { // object names a collection, so make submenu + if (dataEnv in submenu_options) { // object names a collection, so make submenu + editorLog("making a menu for", dataEnv); + var edit_submenu = document.createElement('ol'); + edit_submenu.innerHTML = menu_options_for("", dataEnv, "inner"); + theChooseCurrent.insertAdjacentElement("beforeend", edit_submenu); + document.getElementById('choose_current').focus(); + + // determine whether both of these next cases can occur + } else if (dataEnv in objectStructure || dataEnvParent in objectStructure) { + // we just selected an action, so do it + // that probably involves adding something before or after a given object + editorLog("making a new", dataEnv, "within", dataEnvParent); + + var before_after = $("#edit_menu_holder > #edit_menu > .chosen").attr("data-location"); + + if (dataEnv == "source") { + alert(" making source"); + show_source(object_of_interest, before_after); + edit_menu_from_current_editing("entering"); + return + // } else if (dataEnv == "save") { + // save_source(); + // edit_menu_from_current_editing("entering"); + } + editorLog("create object to edit",dataEnv, object_of_interest, before_after); + var new_obj = create_object_to_edit(dataEnv, object_of_interest, before_after); + if (!new_obj) { edit_menu_from_current_editing("entering"); return "" } + editorLog("new_obj", new_obj); + edit_in_place(new_obj, "new"); + var new_obj_id = new_obj.id; + editorLog("are we editing id", new_obj_id); + editorLog("are we editing", new_obj); + editorLog(" JJJ current_editing", current_editing["level"], current_editing["location"].length, current_editing["tree"].length, current_editing["tree"][current_editing["level"]]); + editorLog(" current_editing", current_editing); + object_of_interest.classList.remove("may_select"); + object_of_interest.classList.remove("may_enter"); + if (tmp = document.getElementById('edit_menu_holder')) { tmp.remove() } + if (dataEnv.startsWith("sbs")) { + editorLog("added sbs, now add to it", new_obj_id); + editorLog("document.getElementById(new_obj_id)", document.getElementById(new_obj_id)); + var first_panel_id = document.getElementById(new_obj_id).firstElementChild.firstElementChild.id; + editorLog("first_panel_id", first_panel_id, document.getElementById(first_panel_id)); + make_current_editing_tree_from_id(first_panel_id); + edit_menu_from_current_editing("entering"); + } + } else { + editorLog("Error: unknown dataEnv", dataEnv); + editorLog("Or maybe unknown dataEnvParent", dataEnvParent); + editorLog("moving up the menu -- not"); + alert("Sorry, not implemented yet!"); + theChooseCurrent.classList.remove("chosen"); + theChooseCurrent.parentElement.classList.remove("past"); + theChooseCurrent.setAttribute("id", "choose_current"); + } + } + } // // dataEnv + else { + editorLog("key that is not meaningful when navigating a menu:", e.code) + } + } +} // main menu navigator + +editorLog("adding tab listener"); + +document.addEventListener('keydown', logKeyDown); + +function logKeyDown(e) { + if (e.code == "ShiftLeft" || e.code == "ShiftRight" || e.code == "Shift") { return } + prev_prev_char = prev_char; + prev_char = this_char; + this_char = e; + editorLog("logKey",e,"XXX",e.code); + editorLog("are we editing", document.getElementById('actively_editing')); + editorLog("is there already an edit menu?", document.getElementById('edit_menu_holder')); + + var input_region = document.activeElement; + editorLog("input_region", input_region); + // if we are writing something, keystrokes usually are just text input + if (document.getElementById('actively_editing')) { + editorLog(" we are actively editing"); + + if (e.code == "Tab" && !document.getElementById('local_menu_holder')) { + // disabled for now + e.preventDefault(); + return "" + create_local_menu() + } else if (document.getElementById('local_menu_holder')) { + main_menu_navigator(e); + } else { + local_editing_action(e) + } + + } else if (document.getElementById('phantomobject')) { + var the_phantomobject = document.getElementById('phantomobject'); + + if (the_phantomobject.classList.contains('move')) { + move_object(e) + } else { + alert("do not know what to do with that") + } + } else { + main_menu_navigator(e); + } +} + +document.addEventListener('focus', function() { +// editorLog('focused:', document.activeElement) +// editorLog('which has content XX' + document.activeElement.innerHTML + "VV") + prev_prev_focused_element = prev_focused_element; + prev_focused_element = this_focused_element; + this_focused_element = document.activeElement; + $('.in_edit_tree').removeClass('in_edit_tree'); +// $(':focus').parent().addClass('in_edit_tree'); + $('#edit_menu_holder:first-child').parent().addClass('in_edit_tree'); + $('#edit_menu_holder').prev().addClass('in_edit_tree'); +/* + var edit_tree = $(':focus').parents(); + // put little lines on teh right, to show the local heirarchy + for (var i=0; i < edit_tree.length; ++i) { + if (edit_tree[i].getAttribute('id') == "content") { break } + edit_tree[i].classList.add('in_edit_tree') + } +*/ +}, true); + +function initialize_editing(xml_st) { + + createCookie(chosen_edit_option_key,1,0.01); + // console.log("QQQQQQQQ", xml_st, "PPPPPPPPPP"); + // Space Math uses a blank line to indicate mrows + xml_st = xml_st.replace(/<\/mrow>\s*/g, " \n\\cr\n "); + xml_st = xml_st.replace(//g, " "); + xml_st = xml_st.replace(/<\/mrow>/g, " "); + + xmlToObject(xml_st); + record_children(sourceobj); + internalSource = re_transform_source(); + + console.log("mostly done initializing"); + console.log(internalSource); + + current_editing = { + "level": 0, + "location": [0], + // "tree": [ [document.getElementById(top_level_id)] ] + "tree": [ [internalSource.root_data.id] ] + } + console.log("initial current_editing", current_editing); + + e_tree = current_editing["tree"]; + editorLog("e_tree", e_tree); + e_level = current_editing["level"]; + editorLog("e_level", e_level); + e_location = current_editing["location"]; + editorLog("e_location", e_location); + console.log(" making the initial menu for", e_tree[e_level][e_location]); + +// document.getElementById("content").firstElementChild.setAttribute("id", internalSource.root_data.id); + + console.log("replacing by id", internalSource.root_data.id); + replace_by_id(internalSource.root_data.id, "html"); + + edit_menu_for(e_tree[e_level][e_location], "entering") + + console.log("internalSource internalSource internalSource internalSource internalSource internalSource", internalSource); + + // done rebulding HTML, so now process math + // NOT CURRENTLY DOING ANYTHING???? + console.log("ready to edit, so typeset the math"); + document.getElementById("content").classList.add("canedit"); + MathJax.typesetPromise(); +} + +var this_source_txt; +var source_url = window.location.href; +source_url = source_url.replace(/(#|\?).*/, ""); +source_url = source_url.replace(/html$/, "ptx"); +fetch(source_url).then( + function(u){ return u.text();} + ).then( + function(text){ + this_source_txt = text; + // console.log("ppppppppppp this_source_txt",this_source_txt) + if (this_source_txt.includes("404 Not")) { + console.log("Error: source unavailable") + } else if (this_source_txt.includes(" 0) { +// bad code because I dopied and was too lazy to rewrite + parseLog(xml.nodeName, "has attributes", xml.attributes); + for (var j = 0; j < xml.attributes.length; j++) { + var attribute = xml.attributes.item(j); + if (attribute.nodeName == "permid") { this_id = attribute.nodeValue } + // these look backward, but that seems to be how PTX does it currently + if (!this_id && attribute.nodeName == "xml:id") { this_id = attribute.nodeValue } + } + } + if (!this_id) { + this_id = randomstring() + } + return this_id +} + +var sourceobj = {}; +var new_top_id = ""; + +function xmlToObject(xml_st) { + var xml; + if (typeof xml_st == "string") { + // parseLog("xml starts", xml_st.slice(0,50)); + parser = new DOMParser(); + xml = parser.parseFromString(xml_st, "text/xml"); +// xml = $.parseXML(xml_st); + } else { + xml = xml_st + } + + parseLog("xml", xml); + parseLog("xml.nodeName", xml.nodeName, "xml.nodeType", xml.nodeType); +// var obj = {}; + var this_id = ""; + var this_node_content = xml.nodeValue; + + if (xml.nodeType == 9) { // document + xml = xml.documentElement; + } + + parseLog("this_node_content", this_node_content); + + if (xml.nodeType == 1) { // element + this_id = xml_id_of(xml); + if (!new_top_id) { + new_top_id = this_id; + top_level_id = new_top_id; + sourceobj["root_data"] = {"id": new_top_id, "number_base": "X.Y"} + } + parseLog("found this_id", this_id); + var this_entry = {}; + this_entry["xml:id"] = this_id; + if (["ol", "ul", "dl"].includes(xml.nodeName)) { + this_entry["sourcetag"] = "list" +/* + } else if (["me", "men"].includes(xml.nodeName)){ + this_entry["sourcetag"] = "displaymath" +*/ + } else { + this_entry["sourcetag"] = xml.nodeName; + } + + this_node_content = ""; + if (xml.hasChildNodes()) { + for (var i = 0; i < xml.childNodes.length; i++) { + var item = xml.childNodes.item(i); + if (item.nodeType == 8) { + //comment, so skip + } else if (item.nodeType == 3) { // text + this_node_content += item.nodeValue + } else if (item.nodeType == 1) { // element + var sub_node_id = xmlToObject(item); // the contents, in certain cases + if (contained_objects.includes(item.nodeName)) { + this_entry[item.nodeName] = sub_node_id + } else { + this_node_content += "<&>" + sub_node_id + "<;>" + } + } else { + parseLog("what to do with this node?", item) + } + } + } + if (this_node_content) { + this_entry["content"] = this_node_content.trim(); + } + + if (xml.attributes.length > 0) { + // parseLog(xml.nodeName, "has attributes", xml.attributes); + for (var j = 0; j < xml.attributes.length; j++) { + var attribute = xml.attributes.item(j); + if (attribute.nodeName == "source") { this_entry["source"] = attribute.nodeValue } + else if (attribute.nodeName == "ref") { this_entry["ref"] = attribute.nodeValue } + else if (attribute.nodeName == "text") { this_entry["text"] = attribute.nodeValue } + // width for images, widths for sbs + else if (attribute.nodeName == "width") { + var widthvalue = attribute.nodeValue; + if (widthvalue.endsWith("%")) { widthvalue = widthvalue.slice(0,-1); } + this_entry["width"] = widthvalue + } else if (attribute.nodeName == "margins") { + var marginsvalues = attribute.nodeValue.split(' '); + if (marginsvalues.length == 1) { + var margin = marginsvalues[0]; + if (margin.endsWith("%")) { margin = margin.slice(0,-1); } + this_entry["marginleft"] = margin; + this_entry["marginright"] = margin; + } else if (marginsvalues.length == 2) { + var [marginleft, marginright] = marginsvalues; + if (marginleft.endsWith("%")) { marginleft = marginleft.slice(0,-1); } + if (marginright.endsWith("%")) { marginright = marginright.slice(0,-1); } + this_entry["marginleft"] = marginleft; + this_entry["marginright"] = marginright; + } else { + console.log("Error: too many margins in", xml) + } + } + } + } + // The CAT expects a width and margins on an image, but those myght not be + // specified in the source. So add those if not present. + if (xml.nodeName == "image") { + console.log("image attributes", xml.attributes, "ddd", xml.attributes["source"]); + if (!xml.attributes["width"]) { + console.log("no width"); + this_entry["width"] = 100; + } + if (!xml.attributes["margins"]) { + console.log("no margins"); + this_entry["marginleft"] = 0; + this_entry["marginright"] = 0; + } + } + + if (xml.attributes) { + parseLog(xml, "has attributes", xml.attributes) + } +// parseLog("item", item); + if (contained_objects.includes(xml.nodeName)) { + return this_node_content + } else { + sourceobj[this_id] = this_entry + return this_id + } + } else if (xml.nodeType == 8) { + // comment node, so do nothing + this_node_content = "" + } else if (xml.nodeType == 3) { // text + // can this, or the previous case, actually happen? + } else { + console.log("failed to deal with", xml) + } +} + +function record_children(internal_src) { + for (key in internal_src) { + var this_item = internal_src[key]; + if ("content" in this_item) { // skip empty tags + var this_content = this_item["content"]; + parseLog("this_content", this_content); + var child_items = this_content.match(/<&>.*?<;>/g) || ""; + for (var j=0; j < child_items.length; ++j) { + var this_child = child_items[j].slice(3,-3); + parseLog("this_child", this_child, "has a parent", key); + internal_src[this_child]["parent"] = [key, "content"] + } + } + // need to handle content and statement better + if ("statement" in this_item) { // skip empty tags + var this_statement = this_item["statement"]; + // parseLog("this_statement", this_statement); + var child_items = this_statement.match(/<&>.*?<;>/g) || ""; + for (var j=0; j < child_items.length; ++j) { + var this_child = child_items[j].slice(3,-3); + // parseLog("this_child", this_child, "has a parent", key); + internal_src[this_child]["parent"] = [key, "statement"] + } + } + if ("proof" in this_item) { // skip empty tags + var this_proof = this_item["proof"]; + // parseLog("this_statement", this_statement); + var child_items = this_proof.match(/<&>.*?<;>/g) || ""; + for (var j=0; j < child_items.length; ++j) { + var this_child = child_items[j].slice(3,-3); + // parseLog("this_child", this_child, "has a parent", key); + internal_src[this_child]["parent"] = [key, "proof"] + } + } + } + return internal_src +} + +// transofrm again, to un-wrap list in p + +/* rewrite with sourceobj not global */ +function re_transform_source() { + var ids_to_delete = []; + for (var id in sourceobj) { + var this_item = sourceobj[id]; + if (this_item["sourcetag"] == "list") { + // I think this takes the list out of its parent p, + // but I forgot to write this comment when I first wrote the code. + parseLog("found a list", this_item); + var [parent_id, parent_content] = this_item["parent"]; + parseLog("with parent", sourceobj[parent_id]); + if (sourceobj[parent_id]["sourcetag"] == "p") { + var [parent_parent_id, parent_parent_content] = sourceobj[parent_id]["parent"]; + parseLog("with parents parent", sourceobj[parent_parent_id]); + // need to skip the intermediate parent + var old_p_p_content = sourceobj[parent_parent_id][parent_parent_content]; + var new_p_p_content = old_p_p_content.replace("<&>" + parent_id + "<;>", "<&>" + id + "<;>"); + sourceobj[parent_parent_id][parent_parent_content] = new_p_p_content; + sourceobj[id]["parent"] = [parent_parent_id, parent_parent_content]; + // then eliminate the intermediate parent + parseLog("deleting", parent_id); + // delete sourceobj[parent_id]; + ids_to_delete.push(parent_id); + parseLog("now sourceobj[parent_parent_id]", sourceobj[parent_parent_id]) + } + } else if (this_item["sourcetag"] == "image") { + if ("width" in this_item && !("marginleft" in this_item)) { + parseLog("no width in" + this_item["xml:id"]) + var width = parseInt(this_item["width"]); + var margins = (100 - width)*0.5; + this_item["marginleft"] = margins; + this_item["marginright"] = margins; + } + } else if (["md", "mdn", "me", "men"].includes(this_item["sourcetag"])) { + // to handle the case of more than one displaymath in a p, + // we do it in two passes. First we just record the ids of the me/men's. + // Then we simplify the problem by handling the displaymath in the + // order they occur. + parseLog("found displaymath", this_item); + parseLog("with parent", sourceobj[this_item["parent"][0]]); + var displaymath_id= this_item["xml:id"]; + if ("includedmath" in sourceobj[this_item["parent"][0]]) { + sourceobj[this_item["parent"][0]]["includedmath"].push("<&>" + displaymath_id + "<;>") + } else { + sourceobj[this_item["parent"][0]]["includedmath"] = ["<&>" + displaymath_id + "<;>"] + } + } else if (["caption"].includes(this_item["sourcetag"])) { + parseLog("found a caption", this_item); + var [parent_id, parent_content] = this_item["parent"]; + parseLog("with parent", sourceobj[parent_id]); + if (sourceobj[parent_id]["sourcetag"] == "figure") { + var old_p_content = sourceobj[parent_id][parent_content]; + var new_p_content = old_p_content.replace("<&>" + id + "<;>", ""); + sourceobj[parent_id][parent_content] = new_p_content; + sourceobj[parent_id]["captiontext"] = sourceobj[id]["content"]; + // then eliminate the caption object, because now it is an attribute of a figure + // delete sourceobj[id]; + ids_to_delete.push(id); + parseLog("now sourceobj[parent_id]", sourceobj[parent_id]) +alert("testing") + } else { alert("error: caption not in figure") } + } + } + // now go through and fix the "p" containing displaymath + for (var id in sourceobj) { + var this_item = sourceobj[id]; + if (this_item["sourcetag"] == "p") { + if ("includedmath" in this_item) { + console.log("found includedmath", this_item); + var outer_parent = this_item["parent"]; + var context_of_outer_parent = sourceobj[outer_parent[0]][outer_parent[1]]; + console.log("this item id", id, "with parent", outer_parent, "in context", context_of_outer_parent); + var these_includedmath = this_item["includedmath"]; + var this_content = this_item["content"]; + var these_includedmath_index = []; + for (var j=0; j < these_includedmath.length; ++j) { + var this_math_tag = these_includedmath[j]; + these_includedmath_index.push([this_content.indexOf(this_math_tag),this_math_tag]); + console.log("this_math_tag", this_math_tag, "has index", this_content.indexOf(this_math_tag)) + } + these_includedmath_index.sort(); + parseLog("sorted list", these_includedmath_index) + + // var displaymath_parent_original_content = sourceobj[outer_parent[0]]["content"]; + parseLog("ocntent before splitting up", context_of_outer_parent); + this_math_tag = these_includedmath_index[0][1]; + parseLog("this_math_tag", this_math_tag); + var this_math_id = this_math_tag.slice(3,-3); + parseLog("this_math_id", this_math_id); + parseLog("with source", sourceobj[this_math_id]); + // move punctuation inside the display math + // (for easier editing. move it back out later) + var find_char_after = new RegExp('^(.*' + this_math_tag + ")(.)\s*(.*)$", "s"); + var char_after = this_content.replace(find_char_after, "$2"); + if ([".", ",", ";", ":"].includes(char_after)) { + console.log("found punctuation", char_after); + this_content = this_content.replace(find_char_after, "$1$3"); // go back an omit white space + sourceobj[this_math_id]["content"] += char_after + } + // the original p ends at the first displaymath + var displaymath_id_and_before = new RegExp('^.*' + this_math_tag, "s"); // s = dotAll + var displaymath_id_and_after = new RegExp(this_math_tag + '.*$', "s"); + sourceobj[id]["content"] = + this_content.replace(displaymath_id_and_after, ""); + sourceobj[id]["sourcetag"] = "ip" + this_content = this_content.replace(displaymath_id_and_before, ""); + parseLog("updated this_content", this_content); + // the first displaymath now has a different parent + sourceobj[this_math_id]["parent"] = outer_parent; + // that parent needs to know where to put the first displaymath + context_of_outer_parent = context_of_outer_parent.replace("<&>" + id + "<;>", "<&>" + id + "<;>" + "\n" + this_math_tag); + parseLog("updated context_of_outer_parent", context_of_outer_parent); + sourceobj[outer_parent[0]][outer_parent[1]] = context_of_outer_parent; + // here need to check if this_content is nonempty (after removing trailing white space + var new_id = "XXXX" + randomstring(); + sourceobj[new_id] = {"xml:id":new_id, "sourcetag": "mp"}; + sourceobj[new_id]["content"] = this_content; + sourceobj[new_id]["parent"] = outer_parent; + context_of_outer_parent = context_of_outer_parent.replace(this_math_tag, this_math_tag + "\n" + "<&>" + new_id + "<;>"); + sourceobj[outer_parent[0]][outer_parent[1]] = context_of_outer_parent; + for (var j=1; j < these_includedmath_index.length; ++j) { + this_math_tag = these_includedmath_index[j][1]; + this_math_id = this_math_tag.slice(3,-3); + + // move punctuation inside the display math + // (for easier editing. move it back out later) + var find_char_after = new RegExp('^(.*' + this_math_tag + ")(.)\s*(.*)$", "s"); + var char_after = this_content.replace(find_char_after, "$2"); + if ([".", ",", ";", ":"].includes(char_after)) { + console.log("found punctuation", char_after); + this_content = this_content.replace(find_char_after, "$1$3"); // go back an omit white space + sourceobj[this_math_id]["content"] += char_after + } + + displaymath_id_and_before = new RegExp('^.*' + this_math_tag, "s"); // s = dotAll + displaymath_id_and_after = new RegExp(this_math_tag + '.*$', "s"); + sourceobj[new_id]["content"] = this_content.replace(displaymath_id_and_after, ""); + this_content = this_content.replace(displaymath_id_and_before, ""); + // this displaymath now has a different parent + sourceobj[this_math_id]["parent"] = outer_parent; + // that parent needs to know where to put the first displaymath + context_of_outer_parent = context_of_outer_parent.replace("<&>" + new_id + "<;>", "<&>" + new_id + "<;>" + "\n" + this_math_tag); + sourceobj[outer_parent[0]][outer_parent[1]] = context_of_outer_parent; + // omit next if this_content is only white space + new_id = "XXXX" + randomstring(); + sourceobj[new_id] = {"xml:id":new_id, "sourcetag": "mp"}; + sourceobj[new_id]["content"] = this_content; + sourceobj[new_id]["parent"] = outer_parent; + context_of_outer_parent = context_of_outer_parent.replace(this_math_tag, this_math_tag + "\n" + "<&>" + new_id + "<;>"); + sourceobj[outer_parent[0]][outer_parent[1]] = context_of_outer_parent; + } + sourceobj[new_id]["sourcetag"] = "fp" + } + } + } + // trim leading white space in paragraph content + for (var id in sourceobj) { + var this_item = sourceobj[id]; + if (["p", "ip", "mp", "fp"].includes(this_item["sourcetag"])) { + var this_content = sourceobj[id]["content"]; + this_content = this_content.replace(/\n +/g, "\n"); + sourceobj[id]["content"] = this_content; + } else if (["me", "men", "md", "mdn"].includes(this_item["sourcetag"])) { + var this_content = sourceobj[id]["content"]; + this_content = this_content.replace(/\n +/g, "\n "); + sourceobj[id]["content"] = this_content; + } + // next also needs table (or whatever it is that has a caption) + if (["figure"].includes(this_item["sourcetag"])) { + editorLog("adjusting a figure", this_item); + // ??? next line should involve captiontext instead of caption ? + var this_caption = sourceobj[id]["caption"]; + this_caption = this_caption.replace(/\n +/g, "\n"); + sourceobj[id]["captiontext"] = this_caption; + } + } + + for (var j=1; j < ids_to_delete.length; ++j) { + // delete sourceobj[ids_to_delete[j]] + } + + return sourceobj; +} + diff --git a/slides/_static/pretext/js/highlight.js b/slides/_static/pretext/js/highlight.js new file mode 100755 index 0000000..98f906a --- /dev/null +++ b/slides/_static/pretext/js/highlight.js @@ -0,0 +1,480 @@ + +/* +highlight_css = document.createElement('style'); +highlight_css.type = "text/css"; +highlight_css.id = "highlight_css"; +document.head.appendChild(highlight_css); +var css_for_hl = 'span.hl { background: yellow; }\n'; +css_for_hl += '#hlmenu { position: absolute; top: 300px; left: 200px;}\n'; +css_for_hl += '#hlmenu { padding: 8px; background: #FFF; }\n'; +css_for_hl += '#hlmenu { box-shadow: 8px 10px 5px #888; border: 1px solid #aaa;}\n'; +css_for_hl += '#hlmenu .hldelete { background: #fdd; }'; +css_for_hl += '#hlmenu .hldelete:hover { background: #fbb; }'; +css_for_hl += '#hlmenu .hlcopy { background: #ddf; }'; +css_for_hl += '#hlmenu .hlcopy:hover { background: #bbf; }'; +css_for_hl += '#hlmenu .dismiss:hover { background: #ff9; }'; +css_for_hl += '#hlmenu > div { padding: 4px; font-size: 90%}'; +highlight_css.innerHTML = css_for_hl; +*/ + +hlmenu = document.createElement('div'); +hlmenu.id = "hlmenu"; +hlmenu.style.display = "none"; +hlmenu_contents = '
    copy to clipboard
    \n'; +hlmenu_contents += '
    delete highlight
    \n'; +hlmenu_contents += '
    dismiss menu
    '; +hlmenu.innerHTML = hlmenu_contents; +document.body.appendChild(hlmenu); + +var all_highlights = localStorage.getObject("all_highlights"); +if (!all_highlights) { + console.log("no highlights on this page",all_highlights); + all_highlights= {}; +} else { + console.log("highlights already",all_highlights); +} + +console.log("all_highlights.keys()", Object.keys(all_highlights)); +console.log("waiting for MathJax"); +MathJax.Hub.Register.StartupHook("End",function () { + // need to wait for MathJax, because MathJax changes the number of nodes in a paragraph + console.log("MathJax is done"); + display_all_highlights(all_highlights, Object.keys(all_highlights), 0); +}); + + +/* +for (var key in all_highlights) { + var this_key = key; + var these_highlights = all_highlights[key]; + console.log("adding highlights to", key); +// await display_highlights_on(key, all_highlights[key], 0) + for (var i=0; i< these_highlights.length; ++i) { + hl = these_highlights[i]; + console.log("inserting highlight",i,"which is",hl, "on", key); + // display_one_highlight(key, hl); +//fails because + setTimeout(function() { display_one_highlight(this_key, hl)}, i*2000); + + } +} +*/ + +function index_of_child(child) { + var i = 0; + while( (child = child.previousSibling) != null ) + {i++ } + return i +} + +function increment_id(list_of_things_with_ids) { // assume ids are of the form xxxxNN with N a digit and x a non-digit + console.log("incrementing id on", list_of_things_with_ids); + if (list_of_things_with_ids.length == 0) { + return 0 + } + + id_start = list_of_things_with_ids[0]["id"].replace(/^(.*?)[0-9]+$/, "$1"); + current_endings = []; + for (var i=0; i < list_of_things_with_ids.length; ++i) { + current_endings.push(list_of_things_with_ids[i]["id"].replace(/^.*?([0-9]+)$/, "$1")); + } + console.log("existing id endings", current_endings); + current_max = Math.max(...current_endings); + return id_start + (parseInt(current_max) + 1) +} + +function enclosing_p_or_li(obj) { + console.log("obj.tagName", obj.tagName, "ggg",obj); + if (!obj) { + console.log("problem with previous object"); + return null + } + else if (obj.tagName == 'P' || obj.tagName == 'LI') { + return obj + } + return enclosing_p_or_li(obj.parentNode) +} + +// If you just loop ofer the highlights to add them, +// you end up with a race condition because each highlight changes +// the structure of the paragraph. +async function display_one_highlight(parent_id, hl) { + var the_parent = document.getElementById(parent_id); + if (!the_parent) { + console.log("this id not on this page:", parent_id); + return + } + console.log("setting", hl, "on", parent_id); + var st_node_ind = hl['start_nn']; + var st_offset = hl['start_offset']; + var end_node_ind = hl['end_nn']; + var end_offset = hl['end_offset']; + console.log("st_node_ind", st_node_ind, "st_offset", st_offset, "end_node_ind", end_node_ind, "end_offset", end_offset); + // other error checks: same parent + if (st_offset < 0 || st_node_ind > the_parent.childNodes.length || end_node_ind > the_parent.childNodes.length || st_offset > the_parent.childNodes[st_node_ind].textContent.length || end_offset < 0 || end_offset > the_parent.childNodes[end_node_ind].textContent.length) { + console.log("highlight data inconsistent with paragraph structure that contains", the_parent.childNodes.length, "nodes"); + return + } + + let this_range = document.createRange(); + console.log("setting this_range.setStart", the_parent.childNodes[st_node_ind], "with offset", st_offset, "out of", the_parent.childNodes[st_node_ind].textContent.length); + this_range.setStart(the_parent.childNodes[st_node_ind], st_offset); + this_range.setEnd(the_parent.childNodes[end_node_ind], end_offset); + var inside_part = document.createElement("span") + inside_part.classList.add("hl"); + inside_part.id = hl['id']; + console.log("inside_part", inside_part, "going inside this_range",this_range); + console.log("in the_parent",the_parent); + this_range.surroundContents(inside_part); + return; +} + +//var display_highlights_on = function(parent_id, highlights_on, ind) { +async function display_highlights_on(parent_id, highlights_on, ind) { + console.log("display_highlights_on", parent_id, "xxxx", ind, "out of", highlights_on.length); + if (ind < highlights_on.length) { + console.log("about to display one highlight, number", ind, "on", parent_id); + await display_one_highlight(parent_id, highlights_on[ind]); + ++ind; + await display_highlights_on(parent_id, highlights_on, ind) + } + return; +} + +async function display_all_highlights(every_highlight, hl_p_keys, i) { + console.log("making hl on", hl_p_keys[i]); + if (i < hl_p_keys.length) { + await display_highlights_on(hl_p_keys[i], every_highlight[hl_p_keys[i]], 0); + ++i; + await display_all_highlights(every_highlight, hl_p_keys, i) + } + return; +} + +var DELAY = 500, clicks = 0, timer = null; +$("p[id], li[id]").on("click", function(e) { + clicks++; //count clicks + + if(clicks === 1) { + + if (e.target.classList.contains("hl")) { + modifyhighlight(e); + clicks = 0; + } else { + timer = setTimeout(function() { + newhighlight() + clicks = 0; //after action performed, reset counter + }, DELAY); + + } + + + } else if (clicks === 2) { + timer2 = setTimeout(function() { + clearTimeout(timer); + // alert("Double Click"); + clicks = 0; + }, DELAY/2); + } else { + clearTimeout(timer); //prevent single-click action + clearTimeout(timer2); //prevent double-click action + // alert("Triple Click"); //perform triple-click action + clicks = 0; //after action performed, reset counter + } +}); + +function save_highlights() { + hl_data = {"action": "save", "user": uname, "pw": emanu, "bookID": bodyID, "type": "highlights", "hl": JSON.stringify(all_highlights)} + $.ajax({ + url: "https://aimath.org/cgi-bin/u/highlights.py", + type: "post", + data: JSON.stringify(hl_data), + dataType: "json", + success: function(data) { + console.log("something", data, "back from highlight"); + // alert(data); + }, + error: function(errMsg) { + console.log("seems to be an error?",errMsg); + // alert("Error\n" + errMsg); + } + }); + + console.log("just ajax sent", JSON.stringify(reading_questions_object)); +} + +function newhighlight() { + var this_selection = window.getSelection(); + console.log("UUUUUUUUUUUUUUUUUUUUUUUUUUU"); + console.log("selection", this_selection, "as string", this_selection.toString(), "length", this_selection.toString().length); + if(this_selection.toString().length >= 3) { + console.log("this_selection", this_selection); + console.log("this_selection parentNode", this_selection.parentNode); + + num_selected_ranges = this_selection.rangeCount; + console.log("this_selection.rangeCount", this_selection.rangeCount); + console.log("this_selection.getRangeAt(0)", this_selection.getRangeAt(0)); + console.log("this_selection.getRangeAt(num_selected_ranges)", this_selection.getRangeAt(num_selected_ranges-1)); + console.log("this_selection.getRangeAt(0).startContainer", this_selection.getRangeAt(0).startContainer); + console.log("this_selection.getRangeAt(0).endContainer", this_selection.getRangeAt(0).endContainer); + console.log("this_selection.getRangeAt(-1).startContainer", this_selection.getRangeAt(num_selected_ranges-1).startContainer); + console.log("this_selection.getRangeAt(-1).endContainer", this_selection.getRangeAt(num_selected_ranges-1).endContainer); + console.log("start equals end", this_selection.getRangeAt(0).startContainer == this_selection.getRangeAt(0).endContainer); + console.log("this_selection.getRangeAt(0).startContainer.parent", this_selection.getRangeAt(0).startContainer.parentNode); + console.log("this_selection.getRangeAt(0).endContainer.parent", this_selection.getRangeAt(0).endContainer.parentNode); + starting_parent = enclosing_p_or_li(this_selection.getRangeAt(0).startContainer); + starting_parent_id = starting_parent.id; + ending_parent_id = enclosing_p_or_li(this_selection.getRangeAt(num_selected_ranges-1).endContainer).id; + console.log("starting parent", enclosing_p_or_li(this_selection.getRangeAt(0).startContainer)); + console.log("starting_parent.childNodes", starting_parent.childNodes); + console.log("starting_parent.childNodes.length", starting_parent.childNodes.length); + console.log("ending parent", enclosing_p_or_li(this_selection.getRangeAt(0).endContainer)); + console.log("index of starting node", index_of_child(this_selection.getRangeAt(0).startContainer)); + console.log("which is", starting_parent.childNodes[index_of_child(this_selection.getRangeAt(0).startContainer)]); + console.log("index of ending node", index_of_child(this_selection.getRangeAt(num_selected_ranges-1).endContainer)); + console.log("which is", starting_parent.childNodes[index_of_child(this_selection.getRangeAt(num_selected_ranges-1).endContainer)]); + console.log("XXXXXXXXXXXXXXXXXXXXXXX"); + starting_node = this_selection.getRangeAt(0); + starting_node_container = starting_node.startContainer; + starting_node_number = index_of_child(starting_node_container); + starting_node_offset = starting_node.startOffset; + console.log("starting_node", starting_node, "starting_node_container", starting_node_container); + console.log("starting_parent.childNodes[0]", starting_parent.childNodes[0]); + ending_node = this_selection.getRangeAt(num_selected_ranges-1); + ending_node_container = ending_node.endContainer; + console.log("ending_node", ending_node, "ending_node_container", ending_node_container); + ending_node_number = index_of_child(ending_node_container); + ending_node_offset = ending_node.endOffset; + console.log("selection starts at character number", starting_node.startOffset, "in node number", index_of_child(starting_node), "of node", starting_node.startContainer, "within", starting_node.startContainer.parentNode,"which is", starting_node); + console.log("selection ends at character number", ending_node.endOffset, "in node number", index_of_child(ending_node_container), "of node", ending_node.endContainer, "within", ending_node.endContainer.parentNode, "which is", ending_node); + +// let this_range = document.createRange(); +// this_range.setStart(starting_parent.childNodes[starting_node_number], starting_node_offset); +// this_range.setEnd(document.getElementById(starting_parent_id).childNodes[ending_node_number], ending_node_offset); +// test_inside_part=document.createElement("span") +// test_inside_part.classList.add("gggg"); +// this_range.surroundContents(test_inside_part); + console.log("num_selected_ranges", num_selected_ranges, "starting_parent_id", starting_parent_id, "ending_parent_id", ending_parent_id); + this_selection.empty(); + console.log("starting_parent_id", starting_parent_id, "ending_parent_id", ending_parent_id); + if (starting_parent_id != ending_parent_id) { + alert("Highlights must be within\none paragraph or list item."); + return "" + } + if (starting_parent != starting_node_container.parentNode) { + starting_node_number = index_of_child(starting_node_container.parentNode) - 1; + console.log("new starting node",starting_node_number, " which is", starting_parent.childNodes[starting_node_number]); + starting_node_offset = starting_parent.childNodes[starting_node_number].length; + } + if (starting_parent != ending_node_container.parentNode) { + ending_node_number = index_of_child(ending_node_container.parentNode) + 1; + console.log("new edngin node",ending_node_number, " which is", starting_parent.childNodes[ending_node_number]); + ending_node_offset = 0; + } + console.log("starting_parent eq st cont parent", starting_parent == starting_node_container.parentNode, "starting_parent eq end cont parent", starting_parent == ending_node_container.parentNode); +// display_one_highlight(starting_parent_id, starting_node_number, starting_node_offset, ending_node_number, ending_node_offset); +// console.log("this_range", this_range); + this_highlight = {"start_nn": starting_node_number, + "start_offset": starting_node_offset, + "end_nn": ending_node_number, + "end_offset": ending_node_offset}; + if (starting_parent_id in all_highlights) { + console.log("the starting_parent_id", starting_parent_id, "is already in all_highlights", all_highlights); + new_id = increment_id(all_highlights[starting_parent_id]); + this_highlight['id'] = new_id; + all_highlights[starting_parent_id].push(this_highlight) + } else { + this_highlight['id'] = starting_parent_id + "-" + "hl" + 1; + all_highlights[starting_parent_id] = [this_highlight] + } + display_one_highlight(starting_parent_id, this_highlight); + localStorage.setObject("all_highlights", all_highlights); + + if (uname != "guest" && role=="student") { + console.log("saving highlights"); + save_highlights(); + } + console.log("all_highlights", all_highlights); + return ""; + } +} + +function modifyhighlight(e) { + this_hl = e.target; + console.log("clicked hl", this_hl.id); + + var x = e.clientX, y = e.clientY; + console.log("x", x, "y", y); + document.getElementById("hlmenu").style.top = (y - 10 + $(window).scrollTop()) + 'px'; + document.getElementById("hlmenu").style.left = (x + 20) + 'px'; + document.getElementById("hlmenu").style.display = 'block'; + document.getElementsByClassName("hlcopy")[0].setAttribute("data-hlid", this_hl.id); + document.getElementsByClassName("hldelete")[0].setAttribute("data-hlid", this_hl.id); +// tooltipSpan.style.left = (x + 20) + 'px'; + + var parent_id = this_hl.id.replace(/^(.*)-[^\-]*$/, "$1"); + var number_of_this_highlight = this_hl.id.slice(-1); + var these_highlights = all_highlights[parent_id]; + var this_highlight = these_highlights[number_of_this_highlight-1]; + console.log("parent id", parent_id, "nunber",number_of_this_highlight); + var num_child_nodes = this_hl.childNodes.length; + console.log("which has", this_hl.childNodes.length, "child nodes"); + for (var i=0; i < num_child_nodes; ++i) { + console.log(i, "node is", this_hl.childNodes[i]) + } + console.log("highlights on this item",all_highlights[parent_id], "of which we clicked", these_highlights[number_of_this_highlight - 1], +"number", number_of_this_highlight, "out of", these_highlights.length ); + for (var i=0; i 0) { + all_highlights[parent_id] = new_highlights + } else { + delete all_highlights[parent_id] + } + + localStorage.setObject("all_highlights", all_highlights); + + + // hide the current highlight + $(hl_to_delete).replaceWith(hl_to_delete.innerHTML); + + document.getElementById(parent_id).normalize(); // because a previous step creates adjacent text nodes + + document.getElementById("hlmenu").style.display = 'none'; +}); + +$('body').on('click','.dismiss', function(e){ + console.log(".dismiss of",this); + the_parent = this.closest("[id]"); + the_parent.style.display = 'none'; +}); diff --git a/slides/_static/pretext/js/instructor.js b/slides/_static/pretext/js/instructor.js new file mode 100755 index 0000000..df5a43c --- /dev/null +++ b/slides/_static/pretext/js/instructor.js @@ -0,0 +1,131 @@ + +var icon = { + "warning": "⚠️ ", + "commentary": "☕", + "media": "▶", + "tip": "📣", + "worksheet": "📄", + "assesment": "A+", + "slides": "📻", + "outcomes": "✓" +}; + +var html_words_of = { + "warning": "Common pitfall", + "commentary": "Alert", + "media": "An amusing demo", + "tip": "Tip: discussion point", + "worksheet": "Worksheet:", + "assesment": "Sample exam:", + "slides": "Slides:", + "outcomes": "Learning outcomes" +} + +//
       Word PTX PDF
    ", + +var type_name = { + "warning": "warning", + "commentary": "commentary", + "media": "media", + "tip": "tip", + "worksheet": "worksheet", + "assesment": "assesment", + "slides": "slides", + "outcomes": "outcomes" +}; + +console.log("in instructor.js", role, "role", logged_in, "logged_in"); + +if (role=="instructor") { + console.log(" loading instructor resources"); + + var instructor_resources = document.querySelectorAll(".instructor"); + + console.log('instructor_resources.length', instructor_resources.length); + + var icons_on_this_page = []; + + for (var j=0; j < instructor_resources.length; ++j) { + var instructor_resource = instructor_resources[j]; + var instructor_resource_parent_id = instructor_resource.parentNode.id; + console.log(" XXXXXXXXXXXX instructor_resource.parentNode", instructor_resource.parentNode); + console.log("instructor_resource_parent_id", instructor_resource_parent_id); + if(instructor_resource_parent_id) { + } else { + instructor_resource_parent_id = instructor_resource.parentNode.parentNode.id; + } + + this_type = instructor_resource.getAttribute("data-resource"); + this_icon = icon[this_type]; + var id_of_this_icon = "resourceid" + j; + icons_on_this_page.push([type_name[this_type], this_icon, id_of_this_icon]); + + console.log("this_type", this_type, "this_icon", this_icon, "html_words_of[this_type]", html_words_of[this_type]); + this_item_with_resource = document.getElementById(instructor_resource_parent_id); + console.log("instructor_resource_parent_id", instructor_resource_parent_id, "this_item_with_resource", this_item_with_resource); + + var this_margin_resource = document.createElement('div'); + this_margin_resource.setAttribute('class', 'marginresource'); + this_margin_resource.setAttribute('id', id_of_this_icon); + this_margin_resource.innerHTML = "" + this_icon + ""; + if(!(this_title = instructor_resource.getAttribute("title"))) { + this_title = html_words_of[this_type] + } + if(instructor_resource.hasAttribute("data-content")) { + this_content = instructor_resource.getAttribute("data-content"); + this_title = '' + this_title + ''; + } + var links_html = "" + if(instructor_resource.hasAttribute("data-links")) { + var this_links = instructor_resource.getAttribute("data-links"); + these_links = this_links.split(";"); + links_html = '' + console.log(" OOOOOOOOOOOOOO these_links", these_links, "these_links.length", these_links.length); + if(these_links.length > 0) { + console.log("these_links[0]", these_links[0]); + } + tmpJ = these_links.length; + console.log("tmpJ", tmpJ); + for(var jj=0; jj < these_links.length; ++jj) { + console.log( " UUUUUUUUUUUUUu these_links[jj]", these_links[jj]); + this_type_and_link = these_links[jj].split(","); + if(jj>0) { links_html += ", " } + links_html += '' + this_type_and_link[0] + ''; + } + links_html += 'XX'; + console.log("done adding links to title"); + } + this_title = '' + this_title + if(links_html) { + this_title += links_html + } + this_title += ''; + this_margin_resource.innerHTML += this_title; +// this_item_with_resource.insertBefore(this_margin_resource); + this_item_with_resource.insertBefore(this_margin_resource, this_item_with_resource.firstChild); + console.log("appended to ",this_item_with_resource); + } + + icons_on_this_page.sort(); + + var icon_legend_for_this_page = document.createElement('div'); + var prev_icon = ""; + var icon_list = '' + "" + icons_on_this_page[0][0] + ": "; + for (var j=0; j < icons_on_this_page.length; ++j) { + next_icon = icons_on_this_page[j][1]; + if(j > 0 && next_icon !== prev_icon) { + icon_list += '' + "
    "; + icon_list += '' + "" + icons_on_this_page[j][0] + ": "; + icon_list += '' + next_icon + ''; + } else { + icon_list += '' + next_icon + ''; + } + prev_icon = next_icon; + } + icon_list += ''; + + icon_legend_for_this_page.setAttribute('class', 'iconlegend'); + icon_legend_for_this_page.innerHTML = icon_list + document.body.appendChild(icon_legend_for_this_page); + +} // if logged in as instructor diff --git a/slides/_static/pretext/js/lib/jquery.espy.min.js b/slides/_static/pretext/js/lib/jquery.espy.min.js new file mode 100755 index 0000000..31cffd1 --- /dev/null +++ b/slides/_static/pretext/js/lib/jquery.espy.min.js @@ -0,0 +1,195 @@ + +(function(b, u, v) { + function x(b, f, e) { + b = (b + "").match(/^(-?[0-9]+)(%)?$/); + if (!b) return !1; + var c = parseInt(b[1], 10); + b[2] && (c *= f / 100); + return 0 > c ? f + c + (e || 0) : c + } + + function y(k, f) { + function e() { + function b() { + g = +new Date; + f.apply(e, t); + c && (c = clearTimeout(c)) + } + var e = this, + q = +new Date - g, + t = arguments; + c && (c = clearTimeout(c)); + q > k ? b() : c = setTimeout(b, k - q) + } + var c, g = 0; + b.guid && (e.guid = f.guid = f.guid || b.guid++); + return e + } + b.Espy = function(k, f, e) { + function c(a, d) { + b.isPlainObject(a) && (d = a, a = null); + b.extend(t.prototype, d); + null !== a && (w = a) + } + + function g(a) { + if (a = + q(a)) { + var d = a.$el.offset()[a.settings.horizontal ? "left" : "top"] - p.offset[a.settings.horizontal ? "left" : "top"], + h = a.$el[a.settings.horizontal ? "outerWidth" : "outerHeight"](); + b.extend(a, { + start: d, + elSize: h, + end: d + h + }) + } + } + + function r(a) { + // console.log("r(a) with a = ", a); + if (a === v) b.each(m, r); + else if (a = q(a)) { + var d = p[a.settings.horizontal ? "width" : "height"], + h = x(a.settings.size, d), + d = p[a.settings.horizontal ? "left" : "top"] + x(a.settings.offset, d, -h), + c = d + h, + h = a.settings.contain ? d <= a.start && c >= a.end ? "inside" : d + h / 2 > a.start + a.elSize / 2 ? a.settings.horizontal ? "left" : + "up" : a.settings.horizontal ? "right" : "down" : d > a.start && d < a.end || c > a.start && c < a.end || d <= a.start && c >= a.start || d <= a.end && c >= a.end ? "inside" : d > a.end ? a.settings.horizontal ? "left" : "up" : a.settings.horizontal ? "right" : "down"; + a.state !== h && (a.state = h, "function" === typeof w && w.call(a.el, "inside" === h, h), "function" === typeof a.callback && a.callback.call(a.el, "inside" === h, h)) + } + } + + function s(a) { + if (m.hasOwnProperty(a)) return a; + if (b.isPlainObject(a) && m.hasOwnProperty(a.id)) return a.id; + a = b(a)[0]; + var d = !1; + b.each(m, function(b, + c) { + c.el === a && (d = b) + }); + return d + } + + // console.log("nothing yet"); + function q(a) { + return (a = s(a)) ? m[a] : !1 + } + "function" !== typeof f && (e = f, f = 0); + // console.log("k was", k); + var t = function(a) { + b.extend(this, a) + }, + u = function(a, d, c, e) { + this.id = a; + this.el = d; + this.$el = b(d); + this.callback = c; + this.settings = new t(e); + this.configure = function(a, d) { + b.isPlainObject(a) && (d = a, a = null); + b.extend(this.settings, d); + null !== a && (this.callback = a) + } + }, + n = this, + l = b(k); + // console.log("l", l, "l==window", l[0]==window); + k = b.fn.espy.defaults; + // console.log("k is", k); + var w, m = {}, + z = 0; + // because window.offset() is not defined in jQuery 3 (why?!?!?!), + // we have to treat that as a special case (DF 1/28/19) + if (l[0]==window) { offSET = { top: 0, left: 0 } } + else { offSET = l.offset() } + var p = { + top: l.scrollTop(), + left: l.scrollLeft(), + width: l.innerWidth(), + height: l.innerHeight(), + offset: offSET +/* + offset: l.offset() || { + top: 0, + left: 0 + } +*/ + }; + // console.log("p", p); + c(f, b.extend({}, k, e)); + n.add = function(a, d, c) { + b.isPlainObject(d) && (c = d, d = 0); + b(a).each(function(a, b) { + var e = s(b) || "s" + z++; + m[e] = new u(e, b, d, c); + g(e); + r(e) + }) + }; + n.configure = function(a, d, e) { + "function" === typeof a ? (d = a, a = null, b.isPlainObject(d) && (e = d, d = null)) : b.isPlainObject(a) ? (e = a, d = a = null) : b.isPlainObject(d) && (e = d, d = null); + null === a ? (c(d, e), b.each(m, function(a, b) { + g(b) + })) : b(a).each(function(a, b) { + var c = q(b); + c && (c.configure(d, e), g(b)) + }) + }; + n.reload = function(a) { + a === v ? b.each(m, function() { + g(this.id) + }) : + b(a).each(function(a, b) { + var c = s(b); + c && (g(c), r(c)) + }) + }; + n.remove = function(a) { + b(a).each(function(a, b) { + var c = s(b); + c && delete m[c] + }) + }; + n.destroy = function() { + l.off(".espy"); + m = {}; + n = v + }; + n.resize = function() { + b.each(m, function() { + this.reloadOnResize && g(this) + }); + p.width = l.innerWidth(); + p.height = l.innerHeight(); + r() + }; + l.on("scroll.espy", y(k.delay, function() { + p.top = l.scrollTop(); + p.left = l.scrollLeft(); + r() + })); + l.on("resize.espy", y(k.delay, function() { + n.resize() + })) + }; + b.fn.espy = function(k, f) { + var e, c; + e = f && f.context || u; + var g = b.data(e, + "espy") || b.data(e, "espy", new b.Espy(e)); + "string" !== typeof k ? g.add(this, k, f) : (e = k, c = Array.prototype.slice.call(arguments), c[0] = this, "function" === typeof g[e] && g[e].apply(g, c)); + return this + }; + b.fn.espy.defaults = { + delay: 100, + context: window, + horizontal: 0, + offset: 0, + size: "100%", + contain: 0, + reloadOnResize: !0 + } +})(jQuery, window); + + diff --git a/slides/_static/pretext/js/lib/jquery.min.js b/slides/_static/pretext/js/lib/jquery.min.js new file mode 100755 index 0000000..4d9b3a2 --- /dev/null +++ b/slides/_static/pretext/js/lib/jquery.min.js @@ -0,0 +1,2 @@ +/*! jQuery v3.3.1 | (c) JS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(e,t){"use strict";var n=[],r=e.document,i=Object.getPrototypeOf,o=n.slice,a=n.concat,s=n.push,u=n.indexOf,l={},c=l.toString,f=l.hasOwnProperty,p=f.toString,d=p.call(Object),h={},g=function e(t){return"function"==typeof t&&"number"!=typeof t.nodeType},y=function e(t){return null!=t&&t===t.window},v={type:!0,src:!0,noModule:!0};function m(e,t,n){var i,o=(t=t||r).createElement("script");if(o.text=e,n)for(i in v)n[i]&&(o[i]=n[i]);t.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?l[c.call(e)]||"object":typeof e}var b="3.3.1",w=function(e,t){return new w.fn.init(e,t)},T=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;w.fn=w.prototype={jquery:"3.3.1",constructor:w,length:0,toArray:function(){return o.call(this)},get:function(e){return null==e?o.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=w.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return w.each(this,e)},map:function(e){return this.pushStack(w.map(this,function(t,n){return e.call(t,n,t)}))},slice:function(){return this.pushStack(o.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(n>=0&&n0&&t-1 in e)}var E=function(e){var t,n,r,i,o,a,s,u,l,c,f,p,d,h,g,y,v,m,x,b="sizzle"+1*new Date,w=e.document,T=0,C=0,E=ae(),k=ae(),S=ae(),D=function(e,t){return e===t&&(f=!0),0},N={}.hasOwnProperty,A=[],j=A.pop,q=A.push,L=A.push,H=A.slice,O=function(e,t){for(var n=0,r=e.length;n+~]|"+M+")"+M+"*"),z=new RegExp("="+M+"*([^\\]'\"]*?)"+M+"*\\]","g"),X=new RegExp(W),U=new RegExp("^"+R+"$"),V={ID:new RegExp("^#("+R+")"),CLASS:new RegExp("^\\.("+R+")"),TAG:new RegExp("^("+R+"|[*])"),ATTR:new RegExp("^"+I),PSEUDO:new RegExp("^"+W),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+P+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},G=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Q=/^[^{]+\{\s*\[native \w/,J=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,K=/[+~]/,Z=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),ee=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},te=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ne=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},re=function(){p()},ie=me(function(e){return!0===e.disabled&&("form"in e||"label"in e)},{dir:"parentNode",next:"legend"});try{L.apply(A=H.call(w.childNodes),w.childNodes),A[w.childNodes.length].nodeType}catch(e){L={apply:A.length?function(e,t){q.apply(e,H.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function oe(e,t,r,i){var o,s,l,c,f,h,v,m=t&&t.ownerDocument,T=t?t.nodeType:9;if(r=r||[],"string"!=typeof e||!e||1!==T&&9!==T&&11!==T)return r;if(!i&&((t?t.ownerDocument||t:w)!==d&&p(t),t=t||d,g)){if(11!==T&&(f=J.exec(e)))if(o=f[1]){if(9===T){if(!(l=t.getElementById(o)))return r;if(l.id===o)return r.push(l),r}else if(m&&(l=m.getElementById(o))&&x(t,l)&&l.id===o)return r.push(l),r}else{if(f[2])return L.apply(r,t.getElementsByTagName(e)),r;if((o=f[3])&&n.getElementsByClassName&&t.getElementsByClassName)return L.apply(r,t.getElementsByClassName(o)),r}if(n.qsa&&!S[e+" "]&&(!y||!y.test(e))){if(1!==T)m=t,v=e;else if("object"!==t.nodeName.toLowerCase()){(c=t.getAttribute("id"))?c=c.replace(te,ne):t.setAttribute("id",c=b),s=(h=a(e)).length;while(s--)h[s]="#"+c+" "+ve(h[s]);v=h.join(","),m=K.test(e)&&ge(t.parentNode)||t}if(v)try{return L.apply(r,m.querySelectorAll(v)),r}catch(e){}finally{c===b&&t.removeAttribute("id")}}}return u(e.replace(B,"$1"),t,r,i)}function ae(){var e=[];function t(n,i){return e.push(n+" ")>r.cacheLength&&delete t[e.shift()],t[n+" "]=i}return t}function se(e){return e[b]=!0,e}function ue(e){var t=d.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function le(e,t){var n=e.split("|"),i=n.length;while(i--)r.attrHandle[n[i]]=t}function ce(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function fe(e){return function(t){return"input"===t.nodeName.toLowerCase()&&t.type===e}}function pe(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function de(e){return function(t){return"form"in t?t.parentNode&&!1===t.disabled?"label"in t?"label"in t.parentNode?t.parentNode.disabled===e:t.disabled===e:t.isDisabled===e||t.isDisabled!==!e&&ie(t)===e:t.disabled===e:"label"in t&&t.disabled===e}}function he(e){return se(function(t){return t=+t,se(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}function ge(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}n=oe.support={},o=oe.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return!!t&&"HTML"!==t.nodeName},p=oe.setDocument=function(e){var t,i,a=e?e.ownerDocument||e:w;return a!==d&&9===a.nodeType&&a.documentElement?(d=a,h=d.documentElement,g=!o(d),w!==d&&(i=d.defaultView)&&i.top!==i&&(i.addEventListener?i.addEventListener("unload",re,!1):i.attachEvent&&i.attachEvent("onunload",re)),n.attributes=ue(function(e){return e.className="i",!e.getAttribute("className")}),n.getElementsByTagName=ue(function(e){return e.appendChild(d.createComment("")),!e.getElementsByTagName("*").length}),n.getElementsByClassName=Q.test(d.getElementsByClassName),n.getById=ue(function(e){return h.appendChild(e).id=b,!d.getElementsByName||!d.getElementsByName(b).length}),n.getById?(r.filter.ID=function(e){var t=e.replace(Z,ee);return function(e){return e.getAttribute("id")===t}},r.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&g){var n=t.getElementById(e);return n?[n]:[]}}):(r.filter.ID=function(e){var t=e.replace(Z,ee);return function(e){var n="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return n&&n.value===t}},r.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&g){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),r.find.TAG=n.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):n.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},r.find.CLASS=n.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&g)return t.getElementsByClassName(e)},v=[],y=[],(n.qsa=Q.test(d.querySelectorAll))&&(ue(function(e){h.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&y.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||y.push("\\["+M+"*(?:value|"+P+")"),e.querySelectorAll("[id~="+b+"-]").length||y.push("~="),e.querySelectorAll(":checked").length||y.push(":checked"),e.querySelectorAll("a#"+b+"+*").length||y.push(".#.+[+~]")}),ue(function(e){e.innerHTML="";var t=d.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&y.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&y.push(":enabled",":disabled"),h.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&y.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),y.push(",.*:")})),(n.matchesSelector=Q.test(m=h.matches||h.webkitMatchesSelector||h.mozMatchesSelector||h.oMatchesSelector||h.msMatchesSelector))&&ue(function(e){n.disconnectedMatch=m.call(e,"*"),m.call(e,"[s!='']:x"),v.push("!=",W)}),y=y.length&&new RegExp(y.join("|")),v=v.length&&new RegExp(v.join("|")),t=Q.test(h.compareDocumentPosition),x=t||Q.test(h.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return f=!0,0;var r=!e.compareDocumentPosition-!t.compareDocumentPosition;return r||(1&(r=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!n.sortDetached&&t.compareDocumentPosition(e)===r?e===d||e.ownerDocument===w&&x(w,e)?-1:t===d||t.ownerDocument===w&&x(w,t)?1:c?O(c,e)-O(c,t):0:4&r?-1:1)}:function(e,t){if(e===t)return f=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===d?-1:t===d?1:i?-1:o?1:c?O(c,e)-O(c,t):0;if(i===o)return ce(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?ce(a[r],s[r]):a[r]===w?-1:s[r]===w?1:0},d):d},oe.matches=function(e,t){return oe(e,null,null,t)},oe.matchesSelector=function(e,t){if((e.ownerDocument||e)!==d&&p(e),t=t.replace(z,"='$1']"),n.matchesSelector&&g&&!S[t+" "]&&(!v||!v.test(t))&&(!y||!y.test(t)))try{var r=m.call(e,t);if(r||n.disconnectedMatch||e.document&&11!==e.document.nodeType)return r}catch(e){}return oe(t,d,null,[e]).length>0},oe.contains=function(e,t){return(e.ownerDocument||e)!==d&&p(e),x(e,t)},oe.attr=function(e,t){(e.ownerDocument||e)!==d&&p(e);var i=r.attrHandle[t.toLowerCase()],o=i&&N.call(r.attrHandle,t.toLowerCase())?i(e,t,!g):void 0;return void 0!==o?o:n.attributes||!g?e.getAttribute(t):(o=e.getAttributeNode(t))&&o.specified?o.value:null},oe.escape=function(e){return(e+"").replace(te,ne)},oe.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},oe.uniqueSort=function(e){var t,r=[],i=0,o=0;if(f=!n.detectDuplicates,c=!n.sortStable&&e.slice(0),e.sort(D),f){while(t=e[o++])t===e[o]&&(i=r.push(o));while(i--)e.splice(r[i],1)}return c=null,e},i=oe.getText=function(e){var t,n="",r=0,o=e.nodeType;if(o){if(1===o||9===o||11===o){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=i(e)}else if(3===o||4===o)return e.nodeValue}else while(t=e[r++])n+=i(t);return n},(r=oe.selectors={cacheLength:50,createPseudo:se,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(Z,ee),e[3]=(e[3]||e[4]||e[5]||"").replace(Z,ee),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||oe.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&oe.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return V.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=a(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(Z,ee).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=E[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&E(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=oe.attr(r,e);return null==i?"!="===t:!t||(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i.replace($," ")+" ").indexOf(n)>-1:"|="===t&&(i===n||i.slice(0,n.length+1)===n+"-"))}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,f,p,d,h,g=o!==a?"nextSibling":"previousSibling",y=t.parentNode,v=s&&t.nodeName.toLowerCase(),m=!u&&!s,x=!1;if(y){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===v:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?y.firstChild:y.lastChild],a&&m){x=(d=(l=(c=(f=(p=y)[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]||[])[0]===T&&l[1])&&l[2],p=d&&y.childNodes[d];while(p=++d&&p&&p[g]||(x=d=0)||h.pop())if(1===p.nodeType&&++x&&p===t){c[e]=[T,d,x];break}}else if(m&&(x=d=(l=(c=(f=(p=t)[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]||[])[0]===T&&l[1]),!1===x)while(p=++d&&p&&p[g]||(x=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===v:1===p.nodeType)&&++x&&(m&&((c=(f=p[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]=[T,x]),p===t))break;return(x-=i)===r||x%r==0&&x/r>=0}}},PSEUDO:function(e,t){var n,i=r.pseudos[e]||r.setFilters[e.toLowerCase()]||oe.error("unsupported pseudo: "+e);return i[b]?i(t):i.length>1?(n=[e,e,"",t],r.setFilters.hasOwnProperty(e.toLowerCase())?se(function(e,n){var r,o=i(e,t),a=o.length;while(a--)e[r=O(e,o[a])]=!(n[r]=o[a])}):function(e){return i(e,0,n)}):i}},pseudos:{not:se(function(e){var t=[],n=[],r=s(e.replace(B,"$1"));return r[b]?se(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),t[0]=null,!n.pop()}}),has:se(function(e){return function(t){return oe(e,t).length>0}}),contains:se(function(e){return e=e.replace(Z,ee),function(t){return(t.textContent||t.innerText||i(t)).indexOf(e)>-1}}),lang:se(function(e){return U.test(e||"")||oe.error("unsupported lang: "+e),e=e.replace(Z,ee).toLowerCase(),function(t){var n;do{if(n=g?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return(n=n.toLowerCase())===e||0===n.indexOf(e+"-")}while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===h},focus:function(e){return e===d.activeElement&&(!d.hasFocus||d.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:de(!1),disabled:de(!0),checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!r.pseudos.empty(e)},header:function(e){return Y.test(e.nodeName)},input:function(e){return G.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:he(function(){return[0]}),last:he(function(e,t){return[t-1]}),eq:he(function(e,t,n){return[n<0?n+t:n]}),even:he(function(e,t){for(var n=0;n=0;)e.push(r);return e}),gt:he(function(e,t,n){for(var r=n<0?n+t:n;++r1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function be(e,t,n){for(var r=0,i=t.length;r-1&&(o[l]=!(a[l]=f))}}else v=we(v===a?v.splice(h,v.length):v),i?i(null,a,v,u):L.apply(a,v)})}function Ce(e){for(var t,n,i,o=e.length,a=r.relative[e[0].type],s=a||r.relative[" "],u=a?1:0,c=me(function(e){return e===t},s,!0),f=me(function(e){return O(t,e)>-1},s,!0),p=[function(e,n,r){var i=!a&&(r||n!==l)||((t=n).nodeType?c(e,n,r):f(e,n,r));return t=null,i}];u1&&xe(p),u>1&&ve(e.slice(0,u-1).concat({value:" "===e[u-2].type?"*":""})).replace(B,"$1"),n,u0,i=e.length>0,o=function(o,a,s,u,c){var f,h,y,v=0,m="0",x=o&&[],b=[],w=l,C=o||i&&r.find.TAG("*",c),E=T+=null==w?1:Math.random()||.1,k=C.length;for(c&&(l=a===d||a||c);m!==k&&null!=(f=C[m]);m++){if(i&&f){h=0,a||f.ownerDocument===d||(p(f),s=!g);while(y=e[h++])if(y(f,a||d,s)){u.push(f);break}c&&(T=E)}n&&((f=!y&&f)&&v--,o&&x.push(f))}if(v+=m,n&&m!==v){h=0;while(y=t[h++])y(x,b,a,s);if(o){if(v>0)while(m--)x[m]||b[m]||(b[m]=j.call(u));b=we(b)}L.apply(u,b),c&&!o&&b.length>0&&v+t.length>1&&oe.uniqueSort(u)}return c&&(T=E,l=w),x};return n?se(o):o}return s=oe.compile=function(e,t){var n,r=[],i=[],o=S[e+" "];if(!o){t||(t=a(e)),n=t.length;while(n--)(o=Ce(t[n]))[b]?r.push(o):i.push(o);(o=S(e,Ee(i,r))).selector=e}return o},u=oe.select=function(e,t,n,i){var o,u,l,c,f,p="function"==typeof e&&e,d=!i&&a(e=p.selector||e);if(n=n||[],1===d.length){if((u=d[0]=d[0].slice(0)).length>2&&"ID"===(l=u[0]).type&&9===t.nodeType&&g&&r.relative[u[1].type]){if(!(t=(r.find.ID(l.matches[0].replace(Z,ee),t)||[])[0]))return n;p&&(t=t.parentNode),e=e.slice(u.shift().value.length)}o=V.needsContext.test(e)?0:u.length;while(o--){if(l=u[o],r.relative[c=l.type])break;if((f=r.find[c])&&(i=f(l.matches[0].replace(Z,ee),K.test(u[0].type)&&ge(t.parentNode)||t))){if(u.splice(o,1),!(e=i.length&&ve(u)))return L.apply(n,i),n;break}}}return(p||s(e,d))(i,t,!g,n,!t||K.test(e)&&ge(t.parentNode)||t),n},n.sortStable=b.split("").sort(D).join("")===b,n.detectDuplicates=!!f,p(),n.sortDetached=ue(function(e){return 1&e.compareDocumentPosition(d.createElement("fieldset"))}),ue(function(e){return e.innerHTML="","#"===e.firstChild.getAttribute("href")})||le("type|href|height|width",function(e,t,n){if(!n)return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),n.attributes&&ue(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||le("value",function(e,t,n){if(!n&&"input"===e.nodeName.toLowerCase())return e.defaultValue}),ue(function(e){return null==e.getAttribute("disabled")})||le(P,function(e,t,n){var r;if(!n)return!0===e[t]?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),oe}(e);w.find=E,w.expr=E.selectors,w.expr[":"]=w.expr.pseudos,w.uniqueSort=w.unique=E.uniqueSort,w.text=E.getText,w.isXMLDoc=E.isXML,w.contains=E.contains,w.escapeSelector=E.escape;var k=function(e,t,n){var r=[],i=void 0!==n;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&w(e).is(n))break;r.push(e)}return r},S=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},D=w.expr.match.needsContext;function N(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}var A=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,t,n){return g(t)?w.grep(e,function(e,r){return!!t.call(e,r,e)!==n}):t.nodeType?w.grep(e,function(e){return e===t!==n}):"string"!=typeof t?w.grep(e,function(e){return u.call(t,e)>-1!==n}):w.filter(t,e,n)}w.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?w.find.matchesSelector(r,e)?[r]:[]:w.find.matches(e,w.grep(t,function(e){return 1===e.nodeType}))},w.fn.extend({find:function(e){var t,n,r=this.length,i=this;if("string"!=typeof e)return this.pushStack(w(e).filter(function(){for(t=0;t1?w.uniqueSort(n):n},filter:function(e){return this.pushStack(j(this,e||[],!1))},not:function(e){return this.pushStack(j(this,e||[],!0))},is:function(e){return!!j(this,"string"==typeof e&&D.test(e)?w(e):e||[],!1).length}});var q,L=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(w.fn.init=function(e,t,n){var i,o;if(!e)return this;if(n=n||q,"string"==typeof e){if(!(i="<"===e[0]&&">"===e[e.length-1]&&e.length>=3?[null,e,null]:L.exec(e))||!i[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(i[1]){if(t=t instanceof w?t[0]:t,w.merge(this,w.parseHTML(i[1],t&&t.nodeType?t.ownerDocument||t:r,!0)),A.test(i[1])&&w.isPlainObject(t))for(i in t)g(this[i])?this[i](t[i]):this.attr(i,t[i]);return this}return(o=r.getElementById(i[2]))&&(this[0]=o,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):g(e)?void 0!==n.ready?n.ready(e):e(w):w.makeArray(e,this)}).prototype=w.fn,q=w(r);var H=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};w.fn.extend({has:function(e){var t=w(e,this),n=t.length;return this.filter(function(){for(var e=0;e-1:1===n.nodeType&&w.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(o.length>1?w.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?u.call(w(e),this[0]):u.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(w.uniqueSort(w.merge(this.get(),w(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}w.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return k(e,"parentNode")},parentsUntil:function(e,t,n){return k(e,"parentNode",n)},next:function(e){return P(e,"nextSibling")},prev:function(e){return P(e,"previousSibling")},nextAll:function(e){return k(e,"nextSibling")},prevAll:function(e){return k(e,"previousSibling")},nextUntil:function(e,t,n){return k(e,"nextSibling",n)},prevUntil:function(e,t,n){return k(e,"previousSibling",n)},siblings:function(e){return S((e.parentNode||{}).firstChild,e)},children:function(e){return S(e.firstChild)},contents:function(e){return N(e,"iframe")?e.contentDocument:(N(e,"template")&&(e=e.content||e),w.merge([],e.childNodes))}},function(e,t){w.fn[e]=function(n,r){var i=w.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=w.filter(r,i)),this.length>1&&(O[e]||w.uniqueSort(i),H.test(e)&&i.reverse()),this.pushStack(i)}});var M=/[^\x20\t\r\n\f]+/g;function R(e){var t={};return w.each(e.match(M)||[],function(e,n){t[n]=!0}),t}w.Callbacks=function(e){e="string"==typeof e?R(e):w.extend({},e);var t,n,r,i,o=[],a=[],s=-1,u=function(){for(i=i||e.once,r=t=!0;a.length;s=-1){n=a.shift();while(++s-1)o.splice(n,1),n<=s&&s--}),this},has:function(e){return e?w.inArray(e,o)>-1:o.length>0},empty:function(){return o&&(o=[]),this},disable:function(){return i=a=[],o=n="",this},disabled:function(){return!o},lock:function(){return i=a=[],n||t||(o=n=""),this},locked:function(){return!!i},fireWith:function(e,n){return i||(n=[e,(n=n||[]).slice?n.slice():n],a.push(n),t||u()),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!r}};return l};function I(e){return e}function W(e){throw e}function $(e,t,n,r){var i;try{e&&g(i=e.promise)?i.call(e).done(t).fail(n):e&&g(i=e.then)?i.call(e,t,n):t.apply(void 0,[e].slice(r))}catch(e){n.apply(void 0,[e])}}w.extend({Deferred:function(t){var n=[["notify","progress",w.Callbacks("memory"),w.Callbacks("memory"),2],["resolve","done",w.Callbacks("once memory"),w.Callbacks("once memory"),0,"resolved"],["reject","fail",w.Callbacks("once memory"),w.Callbacks("once memory"),1,"rejected"]],r="pending",i={state:function(){return r},always:function(){return o.done(arguments).fail(arguments),this},"catch":function(e){return i.then(null,e)},pipe:function(){var e=arguments;return w.Deferred(function(t){w.each(n,function(n,r){var i=g(e[r[4]])&&e[r[4]];o[r[1]](function(){var e=i&&i.apply(this,arguments);e&&g(e.promise)?e.promise().progress(t.notify).done(t.resolve).fail(t.reject):t[r[0]+"With"](this,i?[e]:arguments)})}),e=null}).promise()},then:function(t,r,i){var o=0;function a(t,n,r,i){return function(){var s=this,u=arguments,l=function(){var e,l;if(!(t=o&&(r!==W&&(s=void 0,u=[e]),n.rejectWith(s,u))}};t?c():(w.Deferred.getStackHook&&(c.stackTrace=w.Deferred.getStackHook()),e.setTimeout(c))}}return w.Deferred(function(e){n[0][3].add(a(0,e,g(i)?i:I,e.notifyWith)),n[1][3].add(a(0,e,g(t)?t:I)),n[2][3].add(a(0,e,g(r)?r:W))}).promise()},promise:function(e){return null!=e?w.extend(e,i):i}},o={};return w.each(n,function(e,t){var a=t[2],s=t[5];i[t[1]]=a.add,s&&a.add(function(){r=s},n[3-e][2].disable,n[3-e][3].disable,n[0][2].lock,n[0][3].lock),a.add(t[3].fire),o[t[0]]=function(){return o[t[0]+"With"](this===o?void 0:this,arguments),this},o[t[0]+"With"]=a.fireWith}),i.promise(o),t&&t.call(o,o),o},when:function(e){var t=arguments.length,n=t,r=Array(n),i=o.call(arguments),a=w.Deferred(),s=function(e){return function(n){r[e]=this,i[e]=arguments.length>1?o.call(arguments):n,--t||a.resolveWith(r,i)}};if(t<=1&&($(e,a.done(s(n)).resolve,a.reject,!t),"pending"===a.state()||g(i[n]&&i[n].then)))return a.then();while(n--)$(i[n],s(n),a.reject);return a.promise()}});var B=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;w.Deferred.exceptionHook=function(t,n){e.console&&e.console.warn&&t&&B.test(t.name)&&e.console.warn("jQuery.Deferred exception: "+t.message,t.stack,n)},w.readyException=function(t){e.setTimeout(function(){throw t})};var F=w.Deferred();w.fn.ready=function(e){return F.then(e)["catch"](function(e){w.readyException(e)}),this},w.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--w.readyWait:w.isReady)||(w.isReady=!0,!0!==e&&--w.readyWait>0||F.resolveWith(r,[w]))}}),w.ready.then=F.then;function _(){r.removeEventListener("DOMContentLoaded",_),e.removeEventListener("load",_),w.ready()}"complete"===r.readyState||"loading"!==r.readyState&&!r.documentElement.doScroll?e.setTimeout(w.ready):(r.addEventListener("DOMContentLoaded",_),e.addEventListener("load",_));var z=function(e,t,n,r,i,o,a){var s=0,u=e.length,l=null==n;if("object"===x(n)){i=!0;for(s in n)z(e,t,s,n[s],!0,o,a)}else if(void 0!==r&&(i=!0,g(r)||(a=!0),l&&(a?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(w(e),n)})),t))for(;s1,null,!0)},removeData:function(e){return this.each(function(){K.remove(this,e)})}}),w.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=J.get(e,t),n&&(!r||Array.isArray(n)?r=J.access(e,t,w.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=w.queue(e,t),r=n.length,i=n.shift(),o=w._queueHooks(e,t),a=function(){w.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return J.get(e,n)||J.access(e,n,{empty:w.Callbacks("once memory").add(function(){J.remove(e,[t+"queue",n])})})}}),w.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),arguments.length\x20\t\r\n\f]+)/i,he=/^$|^module$|\/(?:java|ecma)script/i,ge={option:[1,""],thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};ge.optgroup=ge.option,ge.tbody=ge.tfoot=ge.colgroup=ge.caption=ge.thead,ge.th=ge.td;function ye(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&N(e,t)?w.merge([e],n):n}function ve(e,t){for(var n=0,r=e.length;n-1)i&&i.push(o);else if(l=w.contains(o.ownerDocument,o),a=ye(f.appendChild(o),"script"),l&&ve(a),n){c=0;while(o=a[c++])he.test(o.type||"")&&n.push(o)}return f}!function(){var e=r.createDocumentFragment().appendChild(r.createElement("div")),t=r.createElement("input");t.setAttribute("type","radio"),t.setAttribute("checked","checked"),t.setAttribute("name","t"),e.appendChild(t),h.checkClone=e.cloneNode(!0).cloneNode(!0).lastChild.checked,e.innerHTML="",h.noCloneChecked=!!e.cloneNode(!0).lastChild.defaultValue}();var be=r.documentElement,we=/^key/,Te=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ce=/^([^.]*)(?:\.(.+)|)/;function Ee(){return!0}function ke(){return!1}function Se(){try{return r.activeElement}catch(e){}}function De(e,t,n,r,i,o){var a,s;if("object"==typeof t){"string"!=typeof n&&(r=r||n,n=void 0);for(s in t)De(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=ke;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return w().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=w.guid++)),e.each(function(){w.event.add(this,t,i,r,n)})}w.event={global:{},add:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,y=J.get(e);if(y){n.handler&&(n=(o=n).handler,i=o.selector),i&&w.find.matchesSelector(be,i),n.guid||(n.guid=w.guid++),(u=y.events)||(u=y.events={}),(a=y.handle)||(a=y.handle=function(t){return"undefined"!=typeof w&&w.event.triggered!==t.type?w.event.dispatch.apply(e,arguments):void 0}),l=(t=(t||"").match(M)||[""]).length;while(l--)d=g=(s=Ce.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=w.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=w.event.special[d]||{},c=w.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&w.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(e,r,h,a)||e.addEventListener&&e.addEventListener(d,a)),f.add&&(f.add.call(e,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),w.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,y=J.hasData(e)&&J.get(e);if(y&&(u=y.events)){l=(t=(t||"").match(M)||[""]).length;while(l--)if(s=Ce.exec(t[l])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){f=w.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,y.handle)||w.removeEvent(e,d,y.handle),delete u[d])}else for(d in u)w.event.remove(e,d+t[l],n,r,!0);w.isEmptyObject(u)&&J.remove(e,"handle events")}},dispatch:function(e){var t=w.event.fix(e),n,r,i,o,a,s,u=new Array(arguments.length),l=(J.get(this,"events")||{})[t.type]||[],c=w.event.special[t.type]||{};for(u[0]=t,n=1;n=1))for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&("click"!==e.type||!0!==l.disabled)){for(o=[],a={},n=0;n-1:w.find(i,this,null,[l]).length),a[i]&&o.push(r);o.length&&s.push({elem:l,handlers:o})}return l=this,u\x20\t\r\n\f]*)[^>]*)\/>/gi,Ae=/\s*$/g;function Le(e,t){return N(e,"table")&&N(11!==t.nodeType?t:t.firstChild,"tr")?w(e).children("tbody")[0]||e:e}function He(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Oe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Pe(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(J.hasData(e)&&(o=J.access(e),a=J.set(t,o),l=o.events)){delete a.handle,a.events={};for(i in l)for(n=0,r=l[i].length;n1&&"string"==typeof y&&!h.checkClone&&je.test(y))return e.each(function(i){var o=e.eq(i);v&&(t[0]=y.call(this,i,o.html())),Re(o,t,n,r)});if(p&&(i=xe(t,e[0].ownerDocument,!1,e,r),o=i.firstChild,1===i.childNodes.length&&(i=o),o||r)){for(u=(s=w.map(ye(i,"script"),He)).length;f")},clone:function(e,t,n){var r,i,o,a,s=e.cloneNode(!0),u=w.contains(e.ownerDocument,e);if(!(h.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||w.isXMLDoc(e)))for(a=ye(s),r=0,i=(o=ye(e)).length;r0&&ve(a,!u&&ye(e,"script")),s},cleanData:function(e){for(var t,n,r,i=w.event.special,o=0;void 0!==(n=e[o]);o++)if(Y(n)){if(t=n[J.expando]){if(t.events)for(r in t.events)i[r]?w.event.remove(n,r):w.removeEvent(n,r,t.handle);n[J.expando]=void 0}n[K.expando]&&(n[K.expando]=void 0)}}}),w.fn.extend({detach:function(e){return Ie(this,e,!0)},remove:function(e){return Ie(this,e)},text:function(e){return z(this,function(e){return void 0===e?w.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return Re(this,arguments,function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||Le(this,e).appendChild(e)})},prepend:function(){return Re(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Le(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return Re(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return Re(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(w.cleanData(ye(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return w.clone(this,e,t)})},html:function(e){return z(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!Ae.test(e)&&!ge[(de.exec(e)||["",""])[1].toLowerCase()]){e=w.htmlPrefilter(e);try{for(;n=0&&(u+=Math.max(0,Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-o-u-s-.5))),u}function et(e,t,n){var r=$e(e),i=Fe(e,t,r),o="border-box"===w.css(e,"boxSizing",!1,r),a=o;if(We.test(i)){if(!n)return i;i="auto"}return a=a&&(h.boxSizingReliable()||i===e.style[t]),("auto"===i||!parseFloat(i)&&"inline"===w.css(e,"display",!1,r))&&(i=e["offset"+t[0].toUpperCase()+t.slice(1)],a=!0),(i=parseFloat(i)||0)+Ze(e,t,n||(o?"border":"content"),a,r,i)+"px"}w.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Fe(e,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,s=G(t),u=Xe.test(t),l=e.style;if(u||(t=Je(s)),a=w.cssHooks[t]||w.cssHooks[s],void 0===n)return a&&"get"in a&&void 0!==(i=a.get(e,!1,r))?i:l[t];"string"==(o=typeof n)&&(i=ie.exec(n))&&i[1]&&(n=ue(e,t,i),o="number"),null!=n&&n===n&&("number"===o&&(n+=i&&i[3]||(w.cssNumber[s]?"":"px")),h.clearCloneStyle||""!==n||0!==t.indexOf("background")||(l[t]="inherit"),a&&"set"in a&&void 0===(n=a.set(e,n,r))||(u?l.setProperty(t,n):l[t]=n))}},css:function(e,t,n,r){var i,o,a,s=G(t);return Xe.test(t)||(t=Je(s)),(a=w.cssHooks[t]||w.cssHooks[s])&&"get"in a&&(i=a.get(e,!0,n)),void 0===i&&(i=Fe(e,t,r)),"normal"===i&&t in Ve&&(i=Ve[t]),""===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),w.each(["height","width"],function(e,t){w.cssHooks[t]={get:function(e,n,r){if(n)return!ze.test(w.css(e,"display"))||e.getClientRects().length&&e.getBoundingClientRect().width?et(e,t,r):se(e,Ue,function(){return et(e,t,r)})},set:function(e,n,r){var i,o=$e(e),a="border-box"===w.css(e,"boxSizing",!1,o),s=r&&Ze(e,t,r,a,o);return a&&h.scrollboxSize()===o.position&&(s-=Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-parseFloat(o[t])-Ze(e,t,"border",!1,o)-.5)),s&&(i=ie.exec(n))&&"px"!==(i[3]||"px")&&(e.style[t]=n,n=w.css(e,t)),Ke(e,n,s)}}}),w.cssHooks.marginLeft=_e(h.reliableMarginLeft,function(e,t){if(t)return(parseFloat(Fe(e,"marginLeft"))||e.getBoundingClientRect().left-se(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}))+"px"}),w.each({margin:"",padding:"",border:"Width"},function(e,t){w.cssHooks[e+t]={expand:function(n){for(var r=0,i={},o="string"==typeof n?n.split(" "):[n];r<4;r++)i[e+oe[r]+t]=o[r]||o[r-2]||o[0];return i}},"margin"!==e&&(w.cssHooks[e+t].set=Ke)}),w.fn.extend({css:function(e,t){return z(this,function(e,t,n){var r,i,o={},a=0;if(Array.isArray(t)){for(r=$e(e),i=t.length;a1)}});function tt(e,t,n,r,i){return new tt.prototype.init(e,t,n,r,i)}w.Tween=tt,tt.prototype={constructor:tt,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||w.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(w.cssNumber[n]?"":"px")},cur:function(){var e=tt.propHooks[this.prop];return e&&e.get?e.get(this):tt.propHooks._default.get(this)},run:function(e){var t,n=tt.propHooks[this.prop];return this.options.duration?this.pos=t=w.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):tt.propHooks._default.set(this),this}},tt.prototype.init.prototype=tt.prototype,tt.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=w.css(e.elem,e.prop,""))&&"auto"!==t?t:0},set:function(e){w.fx.step[e.prop]?w.fx.step[e.prop](e):1!==e.elem.nodeType||null==e.elem.style[w.cssProps[e.prop]]&&!w.cssHooks[e.prop]?e.elem[e.prop]=e.now:w.style(e.elem,e.prop,e.now+e.unit)}}},tt.propHooks.scrollTop=tt.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},w.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},w.fx=tt.prototype.init,w.fx.step={};var nt,rt,it=/^(?:toggle|show|hide)$/,ot=/queueHooks$/;function at(){rt&&(!1===r.hidden&&e.requestAnimationFrame?e.requestAnimationFrame(at):e.setTimeout(at,w.fx.interval),w.fx.tick())}function st(){return e.setTimeout(function(){nt=void 0}),nt=Date.now()}function ut(e,t){var n,r=0,i={height:e};for(t=t?1:0;r<4;r+=2-t)i["margin"+(n=oe[r])]=i["padding"+n]=e;return t&&(i.opacity=i.width=e),i}function lt(e,t,n){for(var r,i=(pt.tweeners[t]||[]).concat(pt.tweeners["*"]),o=0,a=i.length;o1)},removeAttr:function(e){return this.each(function(){w.removeAttr(this,e)})}}),w.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return"undefined"==typeof e.getAttribute?w.prop(e,t,n):(1===o&&w.isXMLDoc(e)||(i=w.attrHooks[t.toLowerCase()]||(w.expr.match.bool.test(t)?dt:void 0)),void 0!==n?null===n?void w.removeAttr(e,t):i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+""),n):i&&"get"in i&&null!==(r=i.get(e,t))?r:null==(r=w.find.attr(e,t))?void 0:r)},attrHooks:{type:{set:function(e,t){if(!h.radioValue&&"radio"===t&&N(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,i=t&&t.match(M);if(i&&1===e.nodeType)while(n=i[r++])e.removeAttribute(n)}}),dt={set:function(e,t,n){return!1===t?w.removeAttr(e,n):e.setAttribute(n,n),n}},w.each(w.expr.match.bool.source.match(/\w+/g),function(e,t){var n=ht[t]||w.find.attr;ht[t]=function(e,t,r){var i,o,a=t.toLowerCase();return r||(o=ht[a],ht[a]=i,i=null!=n(e,t,r)?a:null,ht[a]=o),i}});var gt=/^(?:input|select|textarea|button)$/i,yt=/^(?:a|area)$/i;w.fn.extend({prop:function(e,t){return z(this,w.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[w.propFix[e]||e]})}}),w.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&w.isXMLDoc(e)||(t=w.propFix[t]||t,i=w.propHooks[t]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=w.find.attr(e,"tabindex");return t?parseInt(t,10):gt.test(e.nodeName)||yt.test(e.nodeName)&&e.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),h.optSelected||(w.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),w.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){w.propFix[this.toLowerCase()]=this});function vt(e){return(e.match(M)||[]).join(" ")}function mt(e){return e.getAttribute&&e.getAttribute("class")||""}function xt(e){return Array.isArray(e)?e:"string"==typeof e?e.match(M)||[]:[]}w.fn.extend({addClass:function(e){var t,n,r,i,o,a,s,u=0;if(g(e))return this.each(function(t){w(this).addClass(e.call(this,t,mt(this)))});if((t=xt(e)).length)while(n=this[u++])if(i=mt(n),r=1===n.nodeType&&" "+vt(i)+" "){a=0;while(o=t[a++])r.indexOf(" "+o+" ")<0&&(r+=o+" ");i!==(s=vt(r))&&n.setAttribute("class",s)}return this},removeClass:function(e){var t,n,r,i,o,a,s,u=0;if(g(e))return this.each(function(t){w(this).removeClass(e.call(this,t,mt(this)))});if(!arguments.length)return this.attr("class","");if((t=xt(e)).length)while(n=this[u++])if(i=mt(n),r=1===n.nodeType&&" "+vt(i)+" "){a=0;while(o=t[a++])while(r.indexOf(" "+o+" ")>-1)r=r.replace(" "+o+" "," ");i!==(s=vt(r))&&n.setAttribute("class",s)}return this},toggleClass:function(e,t){var n=typeof e,r="string"===n||Array.isArray(e);return"boolean"==typeof t&&r?t?this.addClass(e):this.removeClass(e):g(e)?this.each(function(n){w(this).toggleClass(e.call(this,n,mt(this),t),t)}):this.each(function(){var t,i,o,a;if(r){i=0,o=w(this),a=xt(e);while(t=a[i++])o.hasClass(t)?o.removeClass(t):o.addClass(t)}else void 0!==e&&"boolean"!==n||((t=mt(this))&&J.set(this,"__className__",t),this.setAttribute&&this.setAttribute("class",t||!1===e?"":J.get(this,"__className__")||""))})},hasClass:function(e){var t,n,r=0;t=" "+e+" ";while(n=this[r++])if(1===n.nodeType&&(" "+vt(mt(n))+" ").indexOf(t)>-1)return!0;return!1}});var bt=/\r/g;w.fn.extend({val:function(e){var t,n,r,i=this[0];{if(arguments.length)return r=g(e),this.each(function(n){var i;1===this.nodeType&&(null==(i=r?e.call(this,n,w(this).val()):e)?i="":"number"==typeof i?i+="":Array.isArray(i)&&(i=w.map(i,function(e){return null==e?"":e+""})),(t=w.valHooks[this.type]||w.valHooks[this.nodeName.toLowerCase()])&&"set"in t&&void 0!==t.set(this,i,"value")||(this.value=i))});if(i)return(t=w.valHooks[i.type]||w.valHooks[i.nodeName.toLowerCase()])&&"get"in t&&void 0!==(n=t.get(i,"value"))?n:"string"==typeof(n=i.value)?n.replace(bt,""):null==n?"":n}}}),w.extend({valHooks:{option:{get:function(e){var t=w.find.attr(e,"value");return null!=t?t:vt(w.text(e))}},select:{get:function(e){var t,n,r,i=e.options,o=e.selectedIndex,a="select-one"===e.type,s=a?null:[],u=a?o+1:i.length;for(r=o<0?u:a?o:0;r-1)&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),w.each(["radio","checkbox"],function(){w.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=w.inArray(w(e).val(),t)>-1}},h.checkOn||(w.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})}),h.focusin="onfocusin"in e;var wt=/^(?:focusinfocus|focusoutblur)$/,Tt=function(e){e.stopPropagation()};w.extend(w.event,{trigger:function(t,n,i,o){var a,s,u,l,c,p,d,h,v=[i||r],m=f.call(t,"type")?t.type:t,x=f.call(t,"namespace")?t.namespace.split("."):[];if(s=h=u=i=i||r,3!==i.nodeType&&8!==i.nodeType&&!wt.test(m+w.event.triggered)&&(m.indexOf(".")>-1&&(m=(x=m.split(".")).shift(),x.sort()),c=m.indexOf(":")<0&&"on"+m,t=t[w.expando]?t:new w.Event(m,"object"==typeof t&&t),t.isTrigger=o?2:3,t.namespace=x.join("."),t.rnamespace=t.namespace?new RegExp("(^|\\.)"+x.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=void 0,t.target||(t.target=i),n=null==n?[t]:w.makeArray(n,[t]),d=w.event.special[m]||{},o||!d.trigger||!1!==d.trigger.apply(i,n))){if(!o&&!d.noBubble&&!y(i)){for(l=d.delegateType||m,wt.test(l+m)||(s=s.parentNode);s;s=s.parentNode)v.push(s),u=s;u===(i.ownerDocument||r)&&v.push(u.defaultView||u.parentWindow||e)}a=0;while((s=v[a++])&&!t.isPropagationStopped())h=s,t.type=a>1?l:d.bindType||m,(p=(J.get(s,"events")||{})[t.type]&&J.get(s,"handle"))&&p.apply(s,n),(p=c&&s[c])&&p.apply&&Y(s)&&(t.result=p.apply(s,n),!1===t.result&&t.preventDefault());return t.type=m,o||t.isDefaultPrevented()||d._default&&!1!==d._default.apply(v.pop(),n)||!Y(i)||c&&g(i[m])&&!y(i)&&((u=i[c])&&(i[c]=null),w.event.triggered=m,t.isPropagationStopped()&&h.addEventListener(m,Tt),i[m](),t.isPropagationStopped()&&h.removeEventListener(m,Tt),w.event.triggered=void 0,u&&(i[c]=u)),t.result}},simulate:function(e,t,n){var r=w.extend(new w.Event,n,{type:e,isSimulated:!0});w.event.trigger(r,null,t)}}),w.fn.extend({trigger:function(e,t){return this.each(function(){w.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return w.event.trigger(e,t,n,!0)}}),h.focusin||w.each({focus:"focusin",blur:"focusout"},function(e,t){var n=function(e){w.event.simulate(t,e.target,w.event.fix(e))};w.event.special[t]={setup:function(){var r=this.ownerDocument||this,i=J.access(r,t);i||r.addEventListener(e,n,!0),J.access(r,t,(i||0)+1)},teardown:function(){var r=this.ownerDocument||this,i=J.access(r,t)-1;i?J.access(r,t,i):(r.removeEventListener(e,n,!0),J.remove(r,t))}}});var Ct=e.location,Et=Date.now(),kt=/\?/;w.parseXML=function(t){var n;if(!t||"string"!=typeof t)return null;try{n=(new e.DOMParser).parseFromString(t,"text/xml")}catch(e){n=void 0}return n&&!n.getElementsByTagName("parsererror").length||w.error("Invalid XML: "+t),n};var St=/\[\]$/,Dt=/\r?\n/g,Nt=/^(?:submit|button|image|reset|file)$/i,At=/^(?:input|select|textarea|keygen)/i;function jt(e,t,n,r){var i;if(Array.isArray(t))w.each(t,function(t,i){n||St.test(e)?r(e,i):jt(e+"["+("object"==typeof i&&null!=i?t:"")+"]",i,n,r)});else if(n||"object"!==x(t))r(e,t);else for(i in t)jt(e+"["+i+"]",t[i],n,r)}w.param=function(e,t){var n,r=[],i=function(e,t){var n=g(t)?t():t;r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)};if(Array.isArray(e)||e.jquery&&!w.isPlainObject(e))w.each(e,function(){i(this.name,this.value)});else for(n in e)jt(n,e[n],t,i);return r.join("&")},w.fn.extend({serialize:function(){return w.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=w.prop(this,"elements");return e?w.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!w(this).is(":disabled")&&At.test(this.nodeName)&&!Nt.test(e)&&(this.checked||!pe.test(e))}).map(function(e,t){var n=w(this).val();return null==n?null:Array.isArray(n)?w.map(n,function(e){return{name:t.name,value:e.replace(Dt,"\r\n")}}):{name:t.name,value:n.replace(Dt,"\r\n")}}).get()}});var qt=/%20/g,Lt=/#.*$/,Ht=/([?&])_=[^&]*/,Ot=/^(.*?):[ \t]*([^\r\n]*)$/gm,Pt=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Mt=/^(?:GET|HEAD)$/,Rt=/^\/\//,It={},Wt={},$t="*/".concat("*"),Bt=r.createElement("a");Bt.href=Ct.href;function Ft(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(M)||[];if(g(n))while(r=o[i++])"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function _t(e,t,n,r){var i={},o=e===Wt;function a(s){var u;return i[s]=!0,w.each(e[s]||[],function(e,s){var l=s(t,n,r);return"string"!=typeof l||o||i[l]?o?!(u=l):void 0:(t.dataTypes.unshift(l),a(l),!1)}),u}return a(t.dataTypes[0])||!i["*"]&&a("*")}function zt(e,t){var n,r,i=w.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&w.extend(!0,e,r),e}function Xt(e,t,n){var r,i,o,a,s=e.contents,u=e.dataTypes;while("*"===u[0])u.shift(),void 0===r&&(r=e.mimeType||t.getResponseHeader("Content-Type"));if(r)for(i in s)if(s[i]&&s[i].test(r)){u.unshift(i);break}if(u[0]in n)o=u[0];else{for(i in n){if(!u[0]||e.converters[i+" "+u[0]]){o=i;break}a||(a=i)}o=o||a}if(o)return o!==u[0]&&u.unshift(o),n[o]}function Ut(e,t,n,r){var i,o,a,s,u,l={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)l[a.toLowerCase()]=e.converters[a];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!u&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u=o,o=c.shift())if("*"===o)o=u;else if("*"!==u&&u!==o){if(!(a=l[u+" "+o]||l["* "+o]))for(i in l)if((s=i.split(" "))[1]===o&&(a=l[u+" "+s[0]]||l["* "+s[0]])){!0===a?a=l[i]:!0!==l[i]&&(o=s[0],c.unshift(s[1]));break}if(!0!==a)if(a&&e["throws"])t=a(t);else try{t=a(t)}catch(e){return{state:"parsererror",error:a?e:"No conversion from "+u+" to "+o}}}return{state:"success",data:t}}w.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Ct.href,type:"GET",isLocal:Pt.test(Ct.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":$t,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":w.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?zt(zt(e,w.ajaxSettings),t):zt(w.ajaxSettings,e)},ajaxPrefilter:Ft(It),ajaxTransport:Ft(Wt),ajax:function(t,n){"object"==typeof t&&(n=t,t=void 0),n=n||{};var i,o,a,s,u,l,c,f,p,d,h=w.ajaxSetup({},n),g=h.context||h,y=h.context&&(g.nodeType||g.jquery)?w(g):w.event,v=w.Deferred(),m=w.Callbacks("once memory"),x=h.statusCode||{},b={},T={},C="canceled",E={readyState:0,getResponseHeader:function(e){var t;if(c){if(!s){s={};while(t=Ot.exec(a))s[t[1].toLowerCase()]=t[2]}t=s[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return c?a:null},setRequestHeader:function(e,t){return null==c&&(e=T[e.toLowerCase()]=T[e.toLowerCase()]||e,b[e]=t),this},overrideMimeType:function(e){return null==c&&(h.mimeType=e),this},statusCode:function(e){var t;if(e)if(c)E.always(e[E.status]);else for(t in e)x[t]=[x[t],e[t]];return this},abort:function(e){var t=e||C;return i&&i.abort(t),k(0,t),this}};if(v.promise(E),h.url=((t||h.url||Ct.href)+"").replace(Rt,Ct.protocol+"//"),h.type=n.method||n.type||h.method||h.type,h.dataTypes=(h.dataType||"*").toLowerCase().match(M)||[""],null==h.crossDomain){l=r.createElement("a");try{l.href=h.url,l.href=l.href,h.crossDomain=Bt.protocol+"//"+Bt.host!=l.protocol+"//"+l.host}catch(e){h.crossDomain=!0}}if(h.data&&h.processData&&"string"!=typeof h.data&&(h.data=w.param(h.data,h.traditional)),_t(It,h,n,E),c)return E;(f=w.event&&h.global)&&0==w.active++&&w.event.trigger("ajaxStart"),h.type=h.type.toUpperCase(),h.hasContent=!Mt.test(h.type),o=h.url.replace(Lt,""),h.hasContent?h.data&&h.processData&&0===(h.contentType||"").indexOf("application/x-www-form-urlencoded")&&(h.data=h.data.replace(qt,"+")):(d=h.url.slice(o.length),h.data&&(h.processData||"string"==typeof h.data)&&(o+=(kt.test(o)?"&":"?")+h.data,delete h.data),!1===h.cache&&(o=o.replace(Ht,"$1"),d=(kt.test(o)?"&":"?")+"_="+Et+++d),h.url=o+d),h.ifModified&&(w.lastModified[o]&&E.setRequestHeader("If-Modified-Since",w.lastModified[o]),w.etag[o]&&E.setRequestHeader("If-None-Match",w.etag[o])),(h.data&&h.hasContent&&!1!==h.contentType||n.contentType)&&E.setRequestHeader("Content-Type",h.contentType),E.setRequestHeader("Accept",h.dataTypes[0]&&h.accepts[h.dataTypes[0]]?h.accepts[h.dataTypes[0]]+("*"!==h.dataTypes[0]?", "+$t+"; q=0.01":""):h.accepts["*"]);for(p in h.headers)E.setRequestHeader(p,h.headers[p]);if(h.beforeSend&&(!1===h.beforeSend.call(g,E,h)||c))return E.abort();if(C="abort",m.add(h.complete),E.done(h.success),E.fail(h.error),i=_t(Wt,h,n,E)){if(E.readyState=1,f&&y.trigger("ajaxSend",[E,h]),c)return E;h.async&&h.timeout>0&&(u=e.setTimeout(function(){E.abort("timeout")},h.timeout));try{c=!1,i.send(b,k)}catch(e){if(c)throw e;k(-1,e)}}else k(-1,"No Transport");function k(t,n,r,s){var l,p,d,b,T,C=n;c||(c=!0,u&&e.clearTimeout(u),i=void 0,a=s||"",E.readyState=t>0?4:0,l=t>=200&&t<300||304===t,r&&(b=Xt(h,E,r)),b=Ut(h,b,E,l),l?(h.ifModified&&((T=E.getResponseHeader("Last-Modified"))&&(w.lastModified[o]=T),(T=E.getResponseHeader("etag"))&&(w.etag[o]=T)),204===t||"HEAD"===h.type?C="nocontent":304===t?C="notmodified":(C=b.state,p=b.data,l=!(d=b.error))):(d=C,!t&&C||(C="error",t<0&&(t=0))),E.status=t,E.statusText=(n||C)+"",l?v.resolveWith(g,[p,C,E]):v.rejectWith(g,[E,C,d]),E.statusCode(x),x=void 0,f&&y.trigger(l?"ajaxSuccess":"ajaxError",[E,h,l?p:d]),m.fireWith(g,[E,C]),f&&(y.trigger("ajaxComplete",[E,h]),--w.active||w.event.trigger("ajaxStop")))}return E},getJSON:function(e,t,n){return w.get(e,t,n,"json")},getScript:function(e,t){return w.get(e,void 0,t,"script")}}),w.each(["get","post"],function(e,t){w[t]=function(e,n,r,i){return g(n)&&(i=i||r,r=n,n=void 0),w.ajax(w.extend({url:e,type:t,dataType:i,data:n,success:r},w.isPlainObject(e)&&e))}}),w._evalUrl=function(e){return w.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},w.fn.extend({wrapAll:function(e){var t;return this[0]&&(g(e)&&(e=e.call(this[0])),t=w(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(e){return g(e)?this.each(function(t){w(this).wrapInner(e.call(this,t))}):this.each(function(){var t=w(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=g(e);return this.each(function(n){w(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(e){return this.parent(e).not("body").each(function(){w(this).replaceWith(this.childNodes)}),this}}),w.expr.pseudos.hidden=function(e){return!w.expr.pseudos.visible(e)},w.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},w.ajaxSettings.xhr=function(){try{return new e.XMLHttpRequest}catch(e){}};var Vt={0:200,1223:204},Gt=w.ajaxSettings.xhr();h.cors=!!Gt&&"withCredentials"in Gt,h.ajax=Gt=!!Gt,w.ajaxTransport(function(t){var n,r;if(h.cors||Gt&&!t.crossDomain)return{send:function(i,o){var a,s=t.xhr();if(s.open(t.type,t.url,t.async,t.username,t.password),t.xhrFields)for(a in t.xhrFields)s[a]=t.xhrFields[a];t.mimeType&&s.overrideMimeType&&s.overrideMimeType(t.mimeType),t.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest");for(a in i)s.setRequestHeader(a,i[a]);n=function(e){return function(){n&&(n=r=s.onload=s.onerror=s.onabort=s.ontimeout=s.onreadystatechange=null,"abort"===e?s.abort():"error"===e?"number"!=typeof s.status?o(0,"error"):o(s.status,s.statusText):o(Vt[s.status]||s.status,s.statusText,"text"!==(s.responseType||"text")||"string"!=typeof s.responseText?{binary:s.response}:{text:s.responseText},s.getAllResponseHeaders()))}},s.onload=n(),r=s.onerror=s.ontimeout=n("error"),void 0!==s.onabort?s.onabort=r:s.onreadystatechange=function(){4===s.readyState&&e.setTimeout(function(){n&&r()})},n=n("abort");try{s.send(t.hasContent&&t.data||null)}catch(e){if(n)throw e}},abort:function(){n&&n()}}}),w.ajaxPrefilter(function(e){e.crossDomain&&(e.contents.script=!1)}),w.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return w.globalEval(e),e}}}),w.ajaxPrefilter("script",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET")}),w.ajaxTransport("script",function(e){if(e.crossDomain){var t,n;return{send:function(i,o){t=w("' + + `` + + '' + + '' + + '' + + '' + + ''; + + // Determine javascript and css dependencies + const extra_css_files = []; + const extra_js_files = []; + //if (data.rh_result.flags.extra_js_files) { + // for (const jsFile of data.rh_result.flags.extra_js_files) { + // if (jsFile.file == "js/apps/GraphTool/graphtool.min.js" || jsFile.file == "js/apps/Scaffold/scaffold.js") { + // extra_css_files.push({ file: 'js/vendor/bootstrap/css/bootstrap.css', external: '0' }); + // extra_css_files.push({ file: 'themes/math4/math4.css', external: '0' }); + // extra_js_files.push({ file: 'js/vendor/bootstrap/js/bootstrap.js', external: '0' }); + // } + // } + //} + + if (data.rh_result.flags.extra_css_files) data.rh_result.flags.extra_css_files.unshift(...extra_css_files); + else data.rh_result.flags.extra_css_files = extra_css_files; + for (const cssFile of data.rh_result.flags.extra_css_files) { + iframeContents += ''; + } + + if (data.rh_result.flags.extra_js_files) data.rh_result.flags.extra_js_files.unshift(...extra_js_files); + else data.rh_result.flags.extra_js_files = extra_js_files; + for (const jsFile of data.rh_result.flags.extra_js_files) { + iframeContents += ''; + } + + iframeContents += + '' + + '' + + '' + + `` + + '
    ' + form.outerHTML + '
    ' + + ''; + + let iframe; + // If there is no action this is the initialization call. + if (!action) { + // Create the iframe. + iframe = document.createElement('iframe'); + iframe.style.width = '1px'; + iframe.style.minWidth = '100%'; + iframe.classList.add('problem-iframe'); + + // Hide the static problem + ww_container.querySelector('.problem-contents').classList.add('hidden-content'); + + if (activate_button != null) { + // Make sure the iframe follows the activate button in the DOM + activate_button.after(iframe); + } else { + ww_container.prepend(iframe); + } + + iFrameResize({ checkOrigin: false, scrolling: 'omit', heightCalculationMethod: 'min' }, iframe); + + iframe.addEventListener('load', () => { + // Set up form submission from inside the iframe. + const iframeForm = iframe.contentDocument.getElementById(ww_id + '-form'); + iframeForm.addEventListener('submit', (e) => { + handleWW(ww_id, "check"); + e.preventDefault(); + }); + + iframe.contentDocument.querySelectorAll('.collapse.in').forEach(collapse => collapse.classList.add('expanded')); + iframe.contentWindow.jQuery('.collapse').on('shown', function(e) { if (e.target != this) return; this.classList.add('expanded'); }); + iframe.contentWindow.jQuery('.collapse').on('hide', function(e) { if (e.target != this) return; this.classList.remove('expanded'); }); + + iframe.contentDocument.querySelectorAll("button.ww-feedback[data-content]").forEach(button => { + iframe.contentWindow.jQuery(button).popover("show"); + const content = iframe.contentDocument.getElementById(button.id.replace("-feedback-button", "-content")); + const popover = content.parentNode.parentNode; + popover.id = button.id.replace("-feedback-button", "-feedback") + button.setAttribute('aria-describedby', popover.id); + if (button.previousElementSibling) + button.previousElementSibling.setAttribute('aria-describedby', popover.id); + popover.querySelector('.arrow').remove(); + const title = popover.querySelector('.popover-title'); + if (button.dataset.emptyContent) { + title.style.borderBottomWidth = 0; + content.parentNode.remove(); + } + if (title.textContent == localize_correct + '!') title.classList.add('correct'); + }); + iframe.contentWindow.MathJax.startup.promise.then(() => iframe.contentWindow.MathJax.typesetPromise(['.popover', '.popover-content'])); + }); + } else { + iframe = ww_container.querySelector('.problem-iframe'); + } + + iframe.srcdoc = iframeContents; + + iframe.addEventListener('load', () => { + // Remove the loader overlay + loader.remove(); + }, { once: true }) + + // Place focus on the problem. + ww_container.focus() + }); +} + +function WWshowCorrect(ww_id, answers) { + const ww_container = document.getElementById(ww_id); + const iframe = ww_container.querySelector('.problem-iframe'); + + const body = iframe.contentDocument.getElementById(ww_id + '-body') + $("body").trigger("runestone_show_correct", { + "ww_id": ww_id, + "answers": answers + }); + + let inputs = body.querySelectorAll("input:not([type=hidden])"); + for (const input of inputs) { + const name = input.name; + const span_id = `${ww_id}-${name}-correct`; + + if (input.type == 'text' && answers[name] && !(iframe.contentDocument.getElementById(span_id))) { + const feedbackButton = iframe.contentDocument.getElementById(`${ww_id}-${name}-feedback-button`); + if (feedbackButton) { + feedbackButton.remove(); + iframe.contentWindow.jQuery(feedbackButton).popover("hide"); + } + label = iframe.contentDocument.getElementById(`${span_id}-label`); + if (label) { + label.parentElement.insertBefore(input, label); + label.remove(); + } + input.type = "hidden"; + // we need to convert things like < in answers to < + const correct_ans_text = iframe.contentDocument.createElement('div'); + correct_ans_text.innerHTML = answers[name].correct_ans; + input.value = correct_ans_text.textContent; + const show_span = iframe.contentDocument.createElement('span'); + show_span.id = span_id; + show_span.appendChild(answers[name].correct_ans_latex_string + ? iframe.contentDocument.createTextNode('\\(' + answers[name].correct_ans_latex_string + '\\)') + : iframe.contentDocument.createTextNode(answers[name].correct_ans)); + input.parentElement.insertBefore(show_span, input); + } + + if (input.type == 'radio' && answers[name]) { + const feedbackButton = iframe.contentDocument.getElementById(`${ww_id}-${name}-feedback-button`); + if (feedbackButton) { + feedbackButton.remove(); + iframe.contentWindow.jQuery(feedbackButton).popover("hide"); + } + correct_value = answers[name].correct_choice; + if (input.value == correct_value) input.checked = true; + } + } + + const hiddenInputs = body.querySelectorAll("input[type=hidden]"); + for (const input of hiddenInputs) { + const name = input.name; + if (!input.nextElementSibling) continue; + const graphtoolContainer = input.nextElementSibling.nextElementSibling; + if (graphtoolContainer && answers[name] && graphtoolContainer.classList.contains('graphtool-container')) { + const feedbackButton = iframe.contentDocument.getElementById(`${ww_id}-${name}-feedback-button`); + if (feedbackButton) { + feedbackButton.remove(); + iframe.contentWindow.jQuery(feedbackButton).popover("hide"); + } + const correct_ans_div = iframe.contentDocument.createElement('div'); + input.parentElement.insertBefore(correct_ans_div, graphtoolContainer); + graphtoolContainer.style.display = 'none'; + input.value = answers[name].correct_ans; + iframe.contentWindow.jQuery(correct_ans_div).html(answers[name].correct_ans_latex_string); + const script = iframe.contentDocument.createElement('script'); + script.textContent = correct_ans_div.querySelector('script').textContent + .replace('\nwindow.addEventListener("DOMContentLoaded",', '(') + .replace(/;\n$/, '();'); + iframe.contentDocument.body.appendChild(script); + } + } + + let selects = body.querySelectorAll("select:not([type=hidden])"); + for (const select of selects) { + const name = select.name; + const span_id = `${ww_id}-${name}-correct`; + if (answers[name] && !iframe.contentDocument.getElementById(span_id)) { + const feedbackButton = iframe.contentDocument.getElementById(`${ww_id}-${name}-feedback-button`); + if (feedbackButton) { + feedbackButton.remove(); + iframe.contentWindow.jQuery(feedbackButton).popover("hide"); + } + select.style.display = "none"; + select.value = answers[name].correct_ans; + const show_span = iframe.contentDocument.createElement('span'); + show_span.id = span_id; + show_span.appendChild(answers[name].correct_ans_latex_string + ? iframe.contentDocument.createTextNode('\\(' + answers[name].correct_ans_latex_string + '\\)') + : iframe.contentDocument.createTextNode(answers[name].correct_ans)); + select.parentElement.insertBefore(show_span, select); + } + } + + // run MathJax on our new rendering + // FIXME: We only need to typeset the added elements, not the entire body. + const mathjaxTypesetScript = iframe.contentDocument.createElement('script'); + mathjaxTypesetScript.textContent = 'MathJax.startup.promise.then(() => MathJax.typesetPromise([document.body]));'; + iframe.contentDocument.body.appendChild(mathjaxTypesetScript); +} + +function resetWW(ww_id) { + const ww_container = document.getElementById(ww_id); + const activate_button = document.getElementById(ww_id + '-button'); + + ww_container.dataset.current_seed = ww_container.dataset.seed; + + iframe = ww_container.querySelector('.problem-iframe'); + iframe.remove(); + + ww_container.querySelector('.problem-contents').classList.remove('hidden-content'); + + ww_container.querySelector('.problem-buttons.webwork').remove(); + ww_container.querySelector('.problem-buttons').classList.remove('hidden-content'); + // if the newer activate button is there (but hidden) bring it back too + if (activate_button != null) {activate_button.classList.remove('hidden-content');}; +} + +function adjustSrcHrefs(container,ww_domain) { + container.querySelectorAll('[href]').forEach((node) => { + const href = node.attributes.href.value; + if (href !== '#' && !href.match(/^[a-z]+:\/\//i)) node.href = ww_domain + '/' + href; + }); + container.querySelectorAll('[src]').forEach((node) => { + node.src = ww_domain + '/' + node.attributes.src.value; + }); +} + +function translateHintSol(ww_id, body_div, ww_domain, b_ptx_has_hint, b_ptx_has_solution, hint_label_text, solution_label_text) { + // the problem text may come with "hint"s and "solution"s + // each one is an "a" with content "Hint" or "Solution", and an attribute with base64-encoded HTML content + // the WeBWorK knowl js would normally handle this, but we want PreTeXt knowl js to handle it + // so we replace the "a" with the content that should be there for PTX knowl js + // also if hint/sol were missing from the static version, we want these removed here + + const ww_container = document.getElementById(ww_id); + + const hintsolnodes = document.evaluate("//p[a/b]", body_div, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null); + if (hintsolnodes) { + let solutionlikewrapper; + for (let i = 0; i < hintsolnodes.snapshotLength; i++) { + const hintsolp = hintsolnodes.snapshotItem(i); + if (!hintsolp) continue; + const hintSolType = hintsolp.textContent.trim().toLowerCase().replace(':', ''); + + if (hintsolp.previousElementSibling.textContent.trim() != 'Hint:' && hintsolp.previousElementSibling.textContent.trim() != 'Solution:') { + solutionlikewrapper = document.createElement('div'); + solutionlikewrapper.classList.add('webwork', 'solutions'); + hintsolp.parentNode.insertBefore(solutionlikewrapper, hintsolp); + } + + if ((hintSolType == 'solution' && !b_ptx_has_solution) || (hintSolType == 'hint' && !b_ptx_has_hint)) continue; + + const knowlDetails = document.createElement('details'); + knowlDetails.classList.add(hintSolType); + knowlDetails.classList.add('solution-like'); + knowlDetails.classList.add('born-hidden-knowl'); + + const knowlSummary = document.createElement('summary'); + const summaryLabel = document.createElement('span'); + summaryLabel.classList.add('type'); + summaryLabel.innerHTML = hintSolType == 'hint' ? hint_label_text : solution_label_text; + knowlSummary.appendChild(summaryLabel); + knowlDetails.appendChild(knowlSummary); + + const knowlContents = document.createElement('div'); + knowlContents.classList.add(hintSolType); + knowlContents.classList.add('solution-like'); + knowlContents.innerHTML = hintsolp.firstElementChild.dataset.knowlContents; + knowlDetails.appendChild(knowlContents); + + adjustSrcHrefs(knowlContents, ww_domain) + solutionlikewrapper.appendChild(knowlDetails); + } + } + for (let i = 0; i < hintsolnodes.snapshotLength; i++) { + hintsolnodes.snapshotItem(i).remove(); + } +} + +function cloneAttributes(target, source) { + [...source.attributes].forEach( attr => { target.setAttribute(attr.nodeName ,attr.nodeValue) }); +} + +function createFeedbackButton(id, title, content) { + const feedbackButton = document.createElement('button'); + feedbackButton.dataset.title = title; + feedbackButton.dataset.content = `
    ${content || ''}
    `; + if (!content) feedbackButton.dataset.emptyContent = '1'; + const contentSpan = document.createElement('span'); + contentSpan.style.fontWeight = 1000; + contentSpan.textContent = '\uD83D\uDDE9' + feedbackButton.appendChild(contentSpan); + feedbackButton.type = 'button'; + feedbackButton.classList.add('ww-feedback'); + feedbackButton.style.borderRadius = 0; + + feedbackButton.id = `${id}-feedback-button`; + feedbackButton.dataset.html = true; + feedbackButton.dataset.placement = 'bottom'; + feedbackButton.dataset.trigger = 'click'; + + return feedbackButton; +} diff --git a/slides/_static/pretext/js/pretext-webwork/2.17/pretext-webwork.js b/slides/_static/pretext/js/pretext-webwork/2.17/pretext-webwork.js new file mode 100755 index 0000000..9b809e6 --- /dev/null +++ b/slides/_static/pretext/js/pretext-webwork/2.17/pretext-webwork.js @@ -0,0 +1,1058 @@ +//Critical: +// TODO: Think about what should be done for an essay question. + +//Accessibility: + +//Enhancement: +// TODO: In a scaffold problem, adjust the green bar for when one part is correct. +// TODO: Have randomize check that new seed is actually producing new HTML. +// TODO: Don't offer randomize or button unless we know a new version can be produced after trying a few seeds. +// TODO: In a staged problem, Make Interactive button is adjacent to solution knowls; move it. +// TODO: MathQuill +// TODO: Image pop up. +// TODO: Deal with singleResult MultiAnswer problems. + +//Styling: +// TODO: Review all styling in all scenarios (staged/not, correct/partly-correct/incorrect/blank, single/multiple) +// TODO: Sean: I think I'd like to see a slightly larger font size on the buttons + +function handleWW(ww_id, action) { + const ww_container = document.getElementById(ww_id); + const ww_domain = ww_container.dataset.domain; + const ww_problemSource = ww_container.dataset.problemsource; + const ww_sourceFilePath = ww_container.dataset.sourcefilepath; + const ww_course_id = ww_container.dataset.courseid; + const ww_user_id = ww_container.dataset.userid; + const ww_course_password = ww_container.dataset.coursepassword || ww_container.dataset.passwd; + const localize_correct = ww_container.dataset.localizeCorrect || "Correct"; + const localize_incorrect = ww_container.dataset.localizeIncorrect || "Incorrect"; + const localize_blank = ww_container.dataset.localizeBlank || "Blank"; + const localize_submit = ww_container.dataset.localizeSubmit || "Submit"; + const localize_check_responses = ww_container.dataset.localizeCheckResponses || "Check Responses"; + const localize_reveal = ww_container.dataset.localizeReveal || "Reveal"; + const localize_randomize = ww_container.dataset.localizeRandomize || "Randomize"; + const localize_reset = ww_container.dataset.localizeReset || "Reset"; + const runestone_logged_in = (typeof eBookConfig !== 'undefined' && eBookConfig.username !== ''); + // will be null on pages generated prior to late December 2022 + const activate_button = document.getElementById(ww_id + '-button') + + // Set the current seed + if (!action) { + ww_container.dataset.current_seed = ww_container.dataset.seed; + if (runestone_logged_in) { + ww_container.dataset.current_seed = webworkSeedHash(eBookConfig.username + ww_container.dataset.current_seed); + } + } + else if (action == 'randomize') ww_container.dataset.current_seed = Number(ww_container.dataset.current_seed) + 100; + + let loader = document.createElement('div'); + loader.style.position = 'absolute'; + loader.style.left = 0; + loader.style.top = 0; + loader.style.backgroundColor = 'rgba(0.2, 0.2, 0.2, 0.4)'; + loader.style.color = 'white'; + loader.style.width = '100%'; + loader.style.height = '100%'; + loader.style.display = 'flex'; + loader.style.alignItems = 'center'; + loader.style.justifyContent = 'center'; + loader.style.marginTop = '0'; + loader.tabIndex = -1; + const loaderText = document.createElement('span'); + loaderText.textContent = 'Loading'; + loaderText.style.fontSize = '2rem'; + loader.appendChild(loaderText); + ww_container.appendChild(loader); + loader.focus(); + + if (!action) { + // Determine if static version shows hints, solutions, or answers and save that information in the container dataset for later runs. + ww_container.dataset.hasHint = ww_container.getElementsByClassName('hint').length > 0; + ww_container.dataset.hasSolution = ww_container.getElementsByClassName('solution').length > 0; + ww_container.dataset.hasAnswer = ww_container.getElementsByClassName('answer').length > 0; + // Get (possibly localized) label text for hints and solutions. + ww_container.dataset.hintLabelText = ww_container.dataset.hasHint == 'true' + ? ww_container.querySelectorAll('.hint-knowl span.type, details.hint span.type')[0].textContent : 'Hint'; + ww_container.dataset.solutionLabelText = ww_container.dataset.hasSolution == 'true' + ? ww_container.querySelectorAll('.solution-knowl span.type, details.solution span.type')[0].textContent : 'Solution'; + + ww_container.tabIndex = -1; + } + + let url; + + if (action == 'check') { + const iframe = ww_container.querySelector('.problem-iframe'); + const formData = new FormData(iframe.contentDocument.getElementById(ww_id + "-form")); + const params = new URLSearchParams(formData); + url = new URL(ww_domain + '/webwork2/html2xml?' + params.toString()) + url.searchParams.append("answersSubmitted", '1'); + url.searchParams.append('WWsubmit', "1"); + } else { + url = new URL(ww_domain + '/webwork2/html2xml'); + url.searchParams.append("problemSeed", ww_container.dataset.current_seed); + if (ww_problemSource) url.searchParams.append("problemSource", ww_problemSource); + else if (ww_sourceFilePath) url.searchParams.append("sourceFilePath", ww_sourceFilePath); + url.searchParams.append("answersSubmitted", '0'); + url.searchParams.append("displayMode", "MathJax"); + url.searchParams.append("courseID", ww_course_id); + url.searchParams.append("userID", ww_user_id); + url.searchParams.append("course_password", ww_course_password); + url.searchParams.append("outputformat", "raw"); + // note ww_container.dataset.hasSolution is a string, possibly 'false' which is true + url.searchParams.append("showSolutions", ww_container.dataset.hasSolution == 'true' ? '1' : '0'); + url.searchParams.append("showHints", ww_container.dataset.hasHint == 'true' ? '1' : '0'); + url.searchParams.append("problemUUID",ww_id); + } + + // get the json and do stuff with what we get + $.getJSON(url.toString(), (data) => { + // Create the form that will contain the text and input fields of the interactive problem. + const form = document.createElement("form"); + form.id = ww_id + "-form"; + + // Create a div for the problem text. + const body_div = document.createElement("div"); + body_div.id = ww_id + "-body"; + body_div.classList.add("exercise", "exercise-like"); + body_div.lang = data.lang; + body_div.dir = data.dir; + + // Dump the problem text, answer blanks, etc. + body_div.innerHTML = data.rh_result.text; + + // Replace all hn headings with h6 headings. + for (const tag_name of ['h6', 'h5', 'h4', 'h3', 'h2', 'h1']) { + const headings = body_div.getElementsByTagName(tag_name); + for (heading of headings) { + const new_heading = document.createElement("h6"); + new_heading.innerHTML = heading.innerHTML; + cloneAttributes(new_heading, heading); + new_heading.classList.add('webwork-part'); + heading.replaceWith(new_heading); + } + } + + adjustSrcHrefs(body_div, ww_domain); + + translateHintSol(ww_id, body_div, ww_domain, + ww_container.dataset.hasHint == 'true', ww_container.dataset.hasSolution == 'true', + ww_container.dataset.hintLabelText, ww_container.dataset.solutionLabelText) + + // insert previous answers + if (runestone_logged_in) { + const answersObject = (wwList[ww_id.replace(/-ww-rs$/,'')].answers ? wwList[ww_id.replace(/-ww-rs$/,'')].answers : {'answers' : [], 'mqAnswers' : []}); + const mqAnswers = answersObject.mqAnswers; + for (const mqAnswer in mqAnswers) { + const mqInput = body_div.querySelector('input[id=' + mqAnswer + ']'); + if (mqInput && mqInput.value == '') { + mqInput.setAttribute('value', mqAnswers[mqAnswer]); + } + } + const answers = answersObject.answers; + for (const answer in answers) { + const input = body_div.querySelector('input[id=' + answer + ']'); + if (input && input.value == '') { + input.setAttribute('value', answers[answer]); + } + if (input && input.type.toUpperCase() == 'RADIO') { + const buttons = body_div.querySelectorAll('input[name=' + answer + ']'); + for (const button of buttons) { + if (button.value == answers[answer]) { + button.setAttribute('checked', 'checked'); + } + } + } + if (input && input.type.toUpperCase() == 'CHECKBOX') { + const checkboxes = body_div.querySelectorAll('input[name=' + answer + ']'); + for (const checkbox of checkboxes) { + // This is not a bulletproof approach if the problem used input values that are weird + // For example, with commas in them + // However, we are stuck with WW providing answers[answer] as a string like `[value0, value1]` + // and note that it is not `["value0", "value1"]`, so we cannot cleanly parse it into an array + let checkbox_regex = new RegExp('(\\[|, )' + checkbox.value + '(, |\\])'); + if (answers[answer].match(checkbox_regex)) { + checkbox.setAttribute('checked', 'checked'); + } + } + } + var select = body_div.querySelector('select[id=' + answer + ']'); + if (select && answers[answer]) { + // answers[answer] may be wrapped in \text{...} that we want to remove, since value does not have this. + let this_answer = answers[answer]; + if (/^\\text\{.*\}$/.test(this_answer)) {this_answer = this_answer.match(/^\\text\{(.*)\}$/)[1]}; + let quote_escaped_answer = this_answer.replace(/"/g, '\\"'); + const option = body_div.querySelector(`select[id="${answer}"] option[value="${quote_escaped_answer}"]`); + if (option) {option.setAttribute('selected', 'selected')}; + } + } + } + + // insert our cleaned up problem text + form.appendChild(body_div); + + // Set up hidden input fields that the form uses + const wwInputs = { + problemSeed: data.inputs_ref.problemSeed, + problemUUID: data.inputs_ref.problemUUID, + psvn: data.inputs_ref.psvn, + courseName: ww_course_id, + courseID: ww_course_id, + userID: ww_user_id, + course_password: ww_course_password, + displayMode: "MathJax", + session_key: data.rh_result.session_key, + outputformat: "raw", + language: data.formLanguage, + showSummary: data.showSummary, + // note ww_container.dataset.hasSolution is a string, possibly 'false' which is true + showSolutions: ww_container.dataset.hasSolution == 'true' ? '1' : '0', + showHints: ww_container.dataset.hasHint == 'true' ? '1' : '0', + forcePortNumber: data.forcePortNumber + }; + + if (ww_sourceFilePath) wwInputs.sourceFilePath = ww_sourceFilePath; + else if (ww_problemSource) wwInputs.problemSource = ww_problemSource; + + for (const wwInputName of Object.keys(wwInputs)) { + const input = document.createElement('input'); + input.type = 'hidden'; + input.name = wwInputName; + input.value = wwInputs[wwInputName]; + form.appendChild(input); + } + + // Prepare answers object + const answers = {}; + // id the answers even if we won't populate them + Object.keys(data.rh_result.answers).forEach(function(id) { + answers[id] = {}; + }, data.rh_result.answers); + if (ww_container.dataset.hasAnswer == 'true') { + // Update answer data + Object.keys(data.rh_result.answers).forEach(function(id) { + answers[id] = { + correct_ans: this[id].correct_ans, + correct_ans_latex_string: this[id].correct_ans_latex_string, + correct_choice: this[id].correct_choice, + correct_choices: this[id].correct_choices, + }; + }, data.rh_result.answers); + } + + let buttonContainer = ww_container.querySelector('.problem-buttons.webwork'); + // Create the submission buttons if they have not yet been created. + if (!buttonContainer) { + // Hide the original div that contains the old make active button. + ww_container.querySelector('.problem-buttons').classList.add('hidden-content'); + // And the newer activate button if it is there + if (activate_button != null) {activate_button.classList.add('hidden-content');}; + + // Create a new div for the webwork buttons. + buttonContainer = document.createElement('div'); + buttonContainer.classList.add('problem-buttons', 'webwork'); + if (activate_button != null) { + // Make sure the button container follows the activate button in the DOM + activate_button.after(buttonContainer); + } else { + ww_container.prepend(buttonContainer); + } + + // Check button + const check = document.createElement("button"); + check.type = "button"; + check.id = ww_id + '-check'; + check.style.marginRight = "0.25rem"; + check.classList.add('webwork-button'); + + // Adjust if more than one answer to check + const answerCount = body_div.querySelectorAll("input:not([type=hidden])").length + + body_div.querySelectorAll("select:not([type=hidden])").length; + + check.textContent = runestone_logged_in ? localize_submit : localize_check_responses; + check.addEventListener('click', () => handleWW(ww_id, "check")); + + buttonContainer.appendChild(check); + + // Show correct answers button if original PTX has answer knowl. + if (ww_container.dataset.hasAnswer == 'true') { + const correct = document.createElement("button"); + correct.classList.add("show-correct", 'webwork-button'); + correct.type = "button"; + correct.style.marginRight = "0.25rem"; + correct.textContent = localize_reveal; + correct.addEventListener('click', () => WWshowCorrect(ww_id, answers)); + buttonContainer.appendChild(correct); + } + + // randomize button + const randomize = document.createElement("button") + randomize.type = "button"; + randomize.classList.add('webwork-button'); + randomize.style.marginRight = "0.25rem"; + randomize.textContent = localize_randomize; + randomize.addEventListener('click', () => handleWW(ww_id, 'randomize')); + buttonContainer.appendChild(randomize) + + // reset button + const reset = document.createElement("button") + reset.type = "button" + reset.classList.add('webwork-button'); + reset.textContent = localize_reset; + reset.addEventListener('click', () => resetWW(ww_id)); + buttonContainer.appendChild(reset) + } else { + // Update the click handler for the show correct button. + if (ww_container.dataset.hasAnswer == 'true') { + const correct = buttonContainer.querySelector('.show-correct'); + const correctNew = correct.cloneNode(true); + correctNew.addEventListener('click', () => WWshowCorrect(ww_id, answers)); + correct.replaceWith(correctNew); + } + } + + if (action == 'check') { + // Runestone trigger + // TODO: condition on platform=Runestone + $("body").trigger('runestone_ww_check', data) + + const inputs = body_div.querySelectorAll("input:not([type=hidden])"); + for (const input of inputs) { + const name = input.name; + if (input.type == 'text' && answers[name]) { + const score = data.rh_result.answers[name].score; + let headline = ''; + let correctClass = ''; + if (score >= 1) { + headline = localize_correct + '!'; + correctClass = 'correct'; + } else if (score > 0 && score < 1) { + headline = `${Math.round(score * 100)}% ${localize_correct}.`; + correctClass = 'partly-correct'; + } else if (data.rh_result.answers[name].student_ans == '') { + headline = localize_blank + '.'; + correctClass = 'blank'; + } else if (score <= 0) { + headline = localize_incorrect + '.'; + correctClass = 'incorrect'; + } + let title = `${headline}`; + let message = (data.rh_result.answers[name].ans_message ? data.rh_result.answers[name].ans_message : ''); + input.classList.add(correctClass); + // make a span that contains feedback + feedbackSpan = document.createElement('span'); + feedbackSpan.id = `${ww_id}-${name}-feedback`; + feedbackHeadline = document.createElement('span'); + feedbackHeadline.id = `${ww_id}-${name}-headline`; + feedbackHeadline.textContent = headline; + feedbackSpan.appendChild(feedbackHeadline); + feedbackMessage = document.createElement('span'); + feedbackMessage.id = `${ww_id}-${name}-message`; + feedbackMessage.textContent = message; + feedbackSpan.appendChild(feedbackMessage); + // make a non breaking span to surround input, sr span, and feedback button + let nbSpan = document.createElement('span'); + nbSpan.classList.add("nobreak"); + input.parentNode.insertBefore(nbSpan, input); + nbSpan.appendChild(input); + // put a visibly hidden (screen-reader only) span with feedback + let srSpan = document.createElement('span'); + srSpan.classList.add("visually-hidden"); + srSpan.appendChild(feedbackSpan); + nbSpan.appendChild(srSpan); + // give the feedback to sr users immediately + input.setAttribute('aria-describedby',`${ww_id}-${name}-feedback`); + // make the feedback button + srSpan.after(createFeedbackButton(`${ww_id}-${name}`, title, message)); + + } + + if (input.type.toUpperCase() == 'RADIO' && answers[name]) { + const score = data.rh_result.answers[name].score; + const student_ans = data.rh_result.answers[name].student_value || data.rh_result.answers[name].student_ans; + const correct_ans = data.rh_result.answers[name].correct_choice || data.rh_result.answers[name].correct_ans; + if (input.value == student_ans) { + if (score == 1) { + input.parentNode.classList.add('correct'); + } else { + input.parentNode.classList.add('incorrect'); + } + const feedbackButton = createFeedbackButton(`${ww_id}-${name}`, + (student_ans == correct_ans) + ? `${localize_correct}` : `${localize_incorrect}.`) + feedbackButton.style.marginRight = '0.25rem'; + input.after(feedbackButton); + } + } + + if (input.type.toUpperCase() == 'CHECKBOX' && answers[name]) { + const score = data.rh_result.answers[name].score; + const student_ans = data.rh_result.answers[name].student_ans; + const correct_ans = data.rh_result.answers[name].correct_ans; + if (input.value == data.rh_result.answers[name].firstElement) { + const checkbox_div = input.parentNode.parentNode; + if (score == 1) { + checkbox_div.classList.add('correct'); + } else { + checkbox_div.classList.add('incorrect'); + } + const feedbackButton = createFeedbackButton(`${ww_id}-${name}`, + (student_ans == correct_ans) + ? `${localize_correct}` : `${localize_incorrect}.`) + checkbox_div.insertBefore(feedbackButton, checkbox_div.firstChild); + } + } + } + + const hiddenInputs = body_div.querySelectorAll("input[type=hidden]"); + for (const input of hiddenInputs) { + const name = input.name; + if (!input.nextElementSibling) continue; + const graphtoolContainer = input.nextElementSibling.nextElementSibling; + if (graphtoolContainer && answers[name] && graphtoolContainer.classList.contains('graphtool-container')) { + graphtoolContainer.style.position = 'relative'; + const score = data.rh_result.answers[name].score; + let title = ''; + if (score == 1) { + title = `${localize_correct}`; + } else if (score > 0 && score < 1) { + title = `${Math.round(score * 100)}% ${localize_correct}.`; + } else if (data.rh_result.answers[name].student_ans == '') { + // do nothing if the submitted answer is blank and the problem has not already been scored as correct + continue; + } else if (score == 0) { + title = `${localize_incorrect}.`; + } + + const feedbackButton = createFeedbackButton(`${ww_id}-${name}`, title, data.rh_result.answers[name].ans_message); + feedbackButton.style.position = 'absolute'; + feedbackButton.style.left = '100%'; + feedbackButton.style.top = 0; + feedbackButton.style.marginLeft = '0.5rem'; + feedbackButton.dataset.bsContainer = 'body'; + graphtoolContainer.appendChild(feedbackButton); + if (score == 1) { + graphtoolContainer.classList.add('correct'); + } else { + graphtoolContainer.classList.add('incorrect'); + } + + } + } + + const selects = body_div.querySelectorAll("select:not([type=hidden])"); + for (const select of selects) { + const name = select.name; + const score = data.rh_result.answers[name].score; + let title = ''; + if (score == 1) { + headline = localize_correct + '!'; + correctClass = 'correct'; + } else { + headline = localize_incorrect + '.'; + correctClass = 'incorrect'; + } + select.classList.add(correctClass); + title = `${headline}`; + // make a span that contains feedback + feedbackSpan = document.createElement('span'); + feedbackSpan.id = `${ww_id}-${name}-feedback`; + feedbackHeadline = document.createElement('span'); + feedbackHeadline.id = `${ww_id}-${name}-headline`; + feedbackHeadline.textContent = headline; + feedbackSpan.appendChild(feedbackHeadline); + // make a non breaking span to surround select, sr span, and feedback button + let nbSpan = document.createElement('span'); + nbSpan.classList.add("nobreak"); + select.parentNode.insertBefore(nbSpan, select); + nbSpan.appendChild(select); + // put a span around select since select does not support ::after pseudo elements + let psSpan = document.createElement('span'); + psSpan.classList.add("select-wrapper", correctClass); + select.parentNode.insertBefore(psSpan, select); + psSpan.appendChild(select); + // put a visibly hidden (screen-reader only) span with feedback + let srSpan = document.createElement('span'); + srSpan.classList.add("visually-hidden"); + srSpan.appendChild(feedbackSpan); + nbSpan.appendChild(srSpan); + // give the feedback to sr users immediately + select.setAttribute('aria-describedby',`${ww_id}-${name}-feedback`); + // make the feedback button + srSpan.after(createFeedbackButton(`${ww_id}-${name}`, title)); + } + } + + let iframeContents = '' + + '' + + `` + + '' + + '' + + '' + + ''; + + // Determine javascript and css dependencies + const extra_css_files = []; + const extra_js_files = []; + //if (data.rh_result.flags.extra_js_files) { + // for (const jsFile of data.rh_result.flags.extra_js_files) { + // if (jsFile.file == "js/apps/GraphTool/graphtool.min.js" || jsFile.file == "js/apps/Scaffold/scaffold.js") { + // extra_css_files.push({ file: 'js/vendor/bootstrap/css/bootstrap.css', external: '0' }); + // extra_css_files.push({ file: 'themes/math4/math4.css', external: '0' }); + // extra_js_files.push({ file: 'js/vendor/bootstrap/js/bootstrap.js', external: '0' }); + // } + // } + //} + + if (data.extra_css_files) data.extra_css_files.unshift(...extra_css_files); + else data.extra_css_files = extra_css_files; + for (const cssFile of data.extra_css_files) { + if (/knowl/.test(cssFile.file)) continue; + iframeContents += ''; + } + + if (data.extra_js_files) data.extra_js_files.unshift(...extra_js_files); + else data.extra_js_files = extra_js_files; + for (const jsFile of data.extra_js_files) { + if (/knowl/.test(jsFile.file)) continue; + iframeContents += ''; + } + + iframeContents += + '' + + '' + + '' + + `` + + '
    ' + form.outerHTML + '
    ' + + ''; + + let iframe; + // If there is no action this is the initialization call. + if (!action) { + // Create the iframe. + iframe = document.createElement('iframe'); + iframe.style.width = '1px'; + iframe.style.minWidth = '100%'; + iframe.classList.add('problem-iframe'); + + // Hide the static problem + ww_container.querySelector('.problem-contents').classList.add('hidden-content'); + + if (activate_button != null) { + // Make sure the iframe follows the activate button in the DOM + activate_button.after(iframe); + } else { + ww_container.prepend(iframe); + } + + iFrameResize({ checkOrigin: false, scrolling: 'omit', heightCalculationMethod: 'min' }, iframe); + + iframe.addEventListener('load', () => { + // Set up form submission from inside the iframe. + const iframeForm = iframe.contentDocument.getElementById(ww_id + '-form'); + iframeForm.addEventListener('submit', (e) => { + handleWW(ww_id, "check"); + e.preventDefault(); + }); + + iframe.contentDocument.querySelectorAll('.collapse.in').forEach(collapse => collapse.classList.add('expanded')); + iframe.contentWindow.jQuery('.collapse').on('shown', function(e) { if (e.target != this) return; this.classList.add('expanded'); }); + iframe.contentWindow.jQuery('.collapse').on('hide', function(e) { if (e.target != this) return; this.classList.remove('expanded'); }); + + iframe.contentDocument.querySelectorAll("button.ww-feedback[data-bs-content]").forEach(button => { + const bsPopover = new iframe.contentWindow.bootstrap.Popover(button, { + html: true, + placement: 'bottom', + fallbackPlacements: [], + trigger: 'click', + customClass: 'result-popover' + (button.dataset.emptyContent ? ' nocontent' : '') + }); + bsPopover.show(); + const content = iframe.contentDocument.getElementById(button.id.replace("-feedback-button", "-content")); + const popover = content.parentNode.parentNode; + bsPopover.hide(); + + /*button.addEventListener('shown.bs.popover', () => { + // Note that the button itself has the aria-describedby attribute set by bootstrap automatically. + button.previousElementSibling?.setAttribute('aria-describedby', popover.id); + });*/ + + button.addEventListener('click', () => bsPopover.show(), { once: true }); + + popover.querySelector('.popover-arrow').remove(); + const title = popover.querySelector('.popover-header'); + if (button.dataset.emptyContent) content.parentNode.remove(); + if (title.textContent == localize_correct + '!') title.classList.add('correct'); + }); + iframe.contentWindow.MathJax.startup.promise.then(() => iframe.contentWindow.MathJax.typesetPromise(['.popover', '.popover-content'])); + }); + } else { + iframe = ww_container.querySelector('.problem-iframe'); + } + + iframe.srcdoc = iframeContents; + + iframe.addEventListener('load', () => { + // Remove the loader overlay + loader.remove(); + }, { once: true }) + + // Place focus on the problem. + ww_container.focus() + }); +} + +function WWshowCorrect(ww_id, answers) { + const ww_container = document.getElementById(ww_id); + const iframe = ww_container.querySelector('.problem-iframe'); + + const body = iframe.contentDocument.getElementById(ww_id + '-body') + $("body").trigger("runestone_show_correct", { + "ww_id": ww_id, + "answers": answers + }); + + let inputs = body.querySelectorAll("input:not([type=hidden])"); + for (const input of inputs) { + const name = input.name; + const span_id = `${ww_id}-${name}-correct`; + + if (input.type == 'text' && answers[name] && !(iframe.contentDocument.getElementById(span_id))) { + const input_id = input.id; + const mq_span = iframe.contentDocument.getElementById(`mq-answer-${input_id}`); + if (mq_span) { + mq_span.style.display = 'none'; + } + const feedbackButton = iframe.contentDocument.getElementById(`${ww_id}-${name}-feedback-button`); + if (feedbackButton) { + iframe.contentWindow.bootstrap.Popover.getInstance(feedbackButton)?.hide(); + feedbackButton.remove(); + } + label = iframe.contentDocument.getElementById(`${span_id}-label`); + if (label) { + label.parentElement.insertBefore(input, label); + label.remove(); + } + input.type = "hidden"; + // we need to convert things like < in answers to < + const correct_ans_text = iframe.contentDocument.createElement('div'); + correct_ans_text.innerHTML = answers[name].correct_ans; + input.value = correct_ans_text.textContent; + const show_span = iframe.contentDocument.createElement('span'); + show_span.id = span_id; + show_span.appendChild(answers[name].correct_ans_latex_string + ? iframe.contentDocument.createTextNode('\\(' + answers[name].correct_ans_latex_string + '\\)') + : iframe.contentDocument.createTextNode(answers[name].correct_ans)); + input.parentElement.insertBefore(show_span, input); + } + + if (input.type.toUpperCase() == 'RADIO' && answers[name]) { + const feedbackButton = iframe.contentDocument.getElementById(`${ww_id}-${name}-feedback-button`); + if (feedbackButton) { + iframe.contentWindow.bootstrap.Popover.getInstance(feedbackButton)?.hide(); + feedbackButton.remove(); + } + const correct_ans = answers[name].correct_choice || answers[name].correct_ans; + if (input.value == correct_ans) { + input.checked = true; + //input.setAttribute('checked', 'checked'); + } else { + input.checked = false; + } + } + + if (input.type.toUpperCase() == 'CHECKBOX' && answers[name]) { + const correct_choices = answers[name].correct_choices; + if (correct_choices.includes(input.value)) { + input.checked = true; + // input.setAttribute('checked', 'checked'); + } else { + input.checked = false; + // input.setAttribute('checked', false); + } + } + } + + const hiddenInputs = body.querySelectorAll("input[type=hidden]"); + for (const input of hiddenInputs) { + const name = input.name; + if (!input.nextElementSibling) continue; + const graphtoolContainer = input.nextElementSibling.nextElementSibling; + if (graphtoolContainer && answers[name] && graphtoolContainer.classList.contains('graphtool-container')) { + const feedbackButton = iframe.contentDocument.getElementById(`${ww_id}-${name}-feedback-button`); + if (feedbackButton) { + iframe.contentWindow.bootstrap.Popover.getInstance(feedbackButton)?.hide(); + feedbackButton.remove(); + } + const correct_ans_div = iframe.contentDocument.createElement('div'); + input.parentElement.insertBefore(correct_ans_div, graphtoolContainer); + graphtoolContainer.style.display = 'none'; + input.value = answers[name].correct_ans; + iframe.contentWindow.jQuery(correct_ans_div).html(answers[name].correct_ans_latex_string); + const script = iframe.contentDocument.createElement('script'); + script.textContent = correct_ans_div.querySelector('script').textContent + .replace('\nwindow.addEventListener("DOMContentLoaded",', '(') + .replace(/;\n$/, '();'); + iframe.contentDocument.body.appendChild(script); + } + } + + let selects = body.querySelectorAll("select:not([type=hidden])"); + for (const select of selects) { + const name = select.name; + const span_id = `${ww_id}-${name}-correct`; + if (answers[name] && !iframe.contentDocument.getElementById(span_id)) { + const feedbackButton = iframe.contentDocument.getElementById(`${ww_id}-${name}-feedback-button`); + if (feedbackButton) { + iframe.contentWindow.bootstrap.Popover.getInstance(feedbackButton)?.hide(); + feedbackButton.remove(); + } + select.style.display = "none"; + select.value = answers[name].correct_ans; + const show_span = iframe.contentDocument.createElement('span'); + show_span.id = span_id; + show_span.appendChild(answers[name].correct_ans_latex_string + ? iframe.contentDocument.createTextNode('\\(' + answers[name].correct_ans_latex_string + '\\)') + : iframe.contentDocument.createTextNode(answers[name].correct_ans)); + select.parentElement.insertBefore(show_span, select); + } + } + + // run MathJax on our new rendering + // FIXME: We only need to typeset the added elements, not the entire body. + const mathjaxTypesetScript = iframe.contentDocument.createElement('script'); + mathjaxTypesetScript.textContent = 'MathJax.startup.promise.then(() => MathJax.typesetPromise([document.body]));'; + iframe.contentDocument.body.appendChild(mathjaxTypesetScript); +} + +function resetWW(ww_id) { + const ww_container = document.getElementById(ww_id); + const activate_button = document.getElementById(ww_id + '-button'); + + ww_container.dataset.current_seed = ww_container.dataset.seed; + + iframe = ww_container.querySelector('.problem-iframe'); + iframe.remove(); + + ww_container.querySelector('.problem-contents').classList.remove('hidden-content'); + + ww_container.querySelector('.problem-buttons.webwork').remove(); + ww_container.querySelector('.problem-buttons').classList.remove('hidden-content'); + // if the newer activate button is there (but hidden) bring it back too + if (activate_button != null) {activate_button.classList.remove('hidden-content');}; +} + +function adjustSrcHrefs(container,ww_domain) { + container.querySelectorAll('[href]').forEach((node) => { + const href = node.attributes.href.value; + if (href !== '#' && !href.match(/^[a-z]+:\/\//i)) node.href = ww_domain + '/' + href; + }); + container.querySelectorAll('[src]').forEach((node) => { + node.src = new URL(node.attributes.src.value, ww_domain).href; + }); +} + +function translateHintSol(ww_id, body_div, ww_domain, b_ptx_has_hint, b_ptx_has_solution, hint_label_text, solution_label_text) { + // the problem text may come with "hint"s and "solution"s + // Each one is an "a" with the class ".knowl" and the data-type attribute of either "hint" or "solution". + // the WeBWorK knowl js would normally handle this, but we want PreTeXt knowl js to handle it + // so we replace the "a" with the content that should be there for PTX knowl js + // also if hint/sol were missing from the static version, we want these removed here + + const hintSols = body_div.querySelectorAll('.knowl[data-type="hint"],.knowl[data-type="solution"]'); + let solutionlikewrapper; + for (const hintSol of hintSols) { + const hintsolp = hintSol.parentNode; + if (!hintsolp) continue; + const hintsolpp = hintsolp.parentNode; //the div that contains each p with a knowl anchor + const hintSolType = hintSol.dataset.type; + + if (hintsolpp.querySelectorAll(".webwork.solutions").length == 0) + { + solutionlikewrapper = document.createElement('div'); + solutionlikewrapper.classList.add('webwork', 'solutions'); + hintsolpp.insertBefore(solutionlikewrapper, hintsolp); + } + + if ((hintSolType == 'solution' && !b_ptx_has_solution) || + (hintSolType == 'hint' && !b_ptx_has_hint)) + continue; + + const knowlDetails = document.createElement('details'); + knowlDetails.classList.add(hintSolType); + knowlDetails.classList.add('solution-like'); + knowlDetails.classList.add('born-hidden-knowl'); + + const knowlSummary = document.createElement('summary'); + const summaryLabel = document.createElement('span'); + summaryLabel.classList.add('type'); + summaryLabel.innerHTML = hintSolType == 'hint' ? hint_label_text : solution_label_text; + knowlSummary.appendChild(summaryLabel); + knowlDetails.appendChild(knowlSummary); + + const knowlContents = document.createElement('div'); + knowlContents.classList.add(hintSolType); + knowlContents.classList.add('solution-like'); + knowlContents.innerHTML = hintsolp.firstElementChild.dataset.knowlContents; + knowlDetails.appendChild(knowlContents); + + adjustSrcHrefs(knowlContents, ww_domain) + solutionlikewrapper.appendChild(knowlDetails); + } + for (const hintSol of hintSols) { + hintSol.parentNode?.remove(); + } +} + +function cloneAttributes(target, source) { + [...source.attributes].forEach( attr => { target.setAttribute(attr.nodeName ,attr.nodeValue) }); +} + +function createFeedbackButton(id, title, content) { + const feedbackButton = document.createElement('button'); + feedbackButton.dataset.bsTitle = title; + feedbackButton.dataset.bsContent = `
    ${content || ''}
    `; + if (!content) feedbackButton.dataset.emptyContent = '1'; + const contentSpan = document.createElement('span'); + contentSpan.innerHTML = ``; + feedbackButton.appendChild(contentSpan); + feedbackButton.type = 'button'; + feedbackButton.classList.add('ww-feedback'); + + feedbackButton.id = `${id}-feedback-button`; + feedbackButton.dataset.bsToggle = 'popover'; + + return feedbackButton; +} + +function webworkSeedHash(string) { + var hash = 0, i, chr; + if (string.length === 0) return hash; + for (i = 0; i < string.length; i++) { + chr = string.charCodeAt(i); + hash = ((hash << 5) - hash) + chr; + hash |= 0; //Convert to 32bit integer + } + return Math.abs(hash); +}; diff --git a/slides/_static/pretext/js/pretext-webwork/2.18/pretext-webwork.js b/slides/_static/pretext/js/pretext-webwork/2.18/pretext-webwork.js new file mode 100755 index 0000000..c9b6b7c --- /dev/null +++ b/slides/_static/pretext/js/pretext-webwork/2.18/pretext-webwork.js @@ -0,0 +1,1058 @@ +//Critical: +// TODO: Think about what should be done for an essay question. + +//Accessibility: + +//Enhancement: +// TODO: In a scaffold problem, adjust the green bar for when one part is correct. +// TODO: Have randomize check that new seed is actually producing new HTML. +// TODO: Don't offer randomize or button unless we know a new version can be produced after trying a few seeds. +// TODO: In a staged problem, Make Interactive button is adjacent to solution knowls; move it. +// TODO: MathQuill +// TODO: Image pop up. +// TODO: Deal with singleResult MultiAnswer problems. + +//Styling: +// TODO: Review all styling in all scenarios (staged/not, correct/partly-correct/incorrect/blank, single/multiple) +// TODO: Sean: I think I'd like to see a slightly larger font size on the buttons + +function handleWW(ww_id, action) { + const ww_container = document.getElementById(ww_id); + const ww_domain = ww_container.dataset.domain; + const ww_problemSource = ww_container.dataset.problemsource; + const ww_sourceFilePath = ww_container.dataset.sourcefilepath; + const ww_course_id = ww_container.dataset.courseid; + const ww_user_id = ww_container.dataset.userid; + const ww_course_password = ww_container.dataset.coursepassword; + const localize_correct = ww_container.dataset.localizeCorrect || "Correct"; + const localize_incorrect = ww_container.dataset.localizeIncorrect || "Incorrect"; + const localize_blank = ww_container.dataset.localizeBlank || "Blank"; + const localize_submit = ww_container.dataset.localizeSubmit || "Submit"; + const localize_check_responses = ww_container.dataset.localizeCheckResponses || "Check Responses"; + const localize_reveal = ww_container.dataset.localizeReveal || "Reveal"; + const localize_randomize = ww_container.dataset.localizeRandomize || "Randomize"; + const localize_reset = ww_container.dataset.localizeReset || "Reset"; + const runestone_logged_in = (typeof eBookConfig !== 'undefined' && eBookConfig.username !== ''); + // will be null on pages generated prior to late December 2022 + const activate_button = document.getElementById(ww_id + '-button') + + // Set the current seed + if (!action) { + ww_container.dataset.current_seed = ww_container.dataset.seed; + if (runestone_logged_in) { + ww_container.dataset.current_seed = webworkSeedHash(eBookConfig.username + ww_container.dataset.current_seed); + } + } + else if (action == 'randomize') ww_container.dataset.current_seed = Number(ww_container.dataset.current_seed) + 100; + + let loader = document.createElement('div'); + loader.style.position = 'absolute'; + loader.style.left = 0; + loader.style.top = 0; + loader.style.backgroundColor = 'rgba(0.2, 0.2, 0.2, 0.4)'; + loader.style.color = 'white'; + loader.style.width = '100%'; + loader.style.height = '100%'; + loader.style.display = 'flex'; + loader.style.alignItems = 'center'; + loader.style.justifyContent = 'center'; + loader.style.marginTop = '0'; + loader.tabIndex = -1; + const loaderText = document.createElement('span'); + loaderText.textContent = 'Loading'; + loaderText.style.fontSize = '2rem'; + loader.appendChild(loaderText); + ww_container.appendChild(loader); + loader.focus(); + + if (!action) { + // Determine if static version shows hints, solutions, or answers and save that information in the container dataset for later runs. + ww_container.dataset.hasHint = ww_container.getElementsByClassName('hint').length > 0; + ww_container.dataset.hasSolution = ww_container.getElementsByClassName('solution').length > 0; + ww_container.dataset.hasAnswer = ww_container.getElementsByClassName('answer').length > 0; + // Get (possibly localized) label text for hints and solutions. + ww_container.dataset.hintLabelText = ww_container.dataset.hasHint == 'true' + ? ww_container.querySelectorAll('.hint-knowl span.type, details.hint span.type')[0].textContent : 'Hint'; + ww_container.dataset.solutionLabelText = ww_container.dataset.hasSolution == 'true' + ? ww_container.querySelectorAll('.solution-knowl span.type, details.solution span.type')[0].textContent : 'Solution'; + + ww_container.tabIndex = -1; + } + + let url; + + if (action == 'check') { + const iframe = ww_container.querySelector('.problem-iframe'); + const formData = new FormData(iframe.contentDocument.getElementById(ww_id + "-form")); + const params = new URLSearchParams(formData); + url = new URL(ww_domain + '/webwork2/html2xml?' + params.toString()) + url.searchParams.append("answersSubmitted", '1'); + url.searchParams.append('WWsubmit', "1"); + } else { + url = new URL(ww_domain + '/webwork2/html2xml'); + url.searchParams.append("problemSeed", ww_container.dataset.current_seed); + if (ww_problemSource) url.searchParams.append("problemSource", ww_problemSource); + else if (ww_sourceFilePath) url.searchParams.append("sourceFilePath", ww_sourceFilePath); + url.searchParams.append("answersSubmitted", '0'); + url.searchParams.append("displayMode", "MathJax"); + url.searchParams.append("courseID", ww_course_id); + url.searchParams.append("userID", ww_user_id); + url.searchParams.append("course_password", ww_course_password); + url.searchParams.append("outputformat", "raw"); + // note ww_container.dataset.hasSolution is a string, possibly 'false' which is true + url.searchParams.append("showSolutions", ww_container.dataset.hasSolution == 'true' ? '1' : '0'); + url.searchParams.append("showHints", ww_container.dataset.hasHint == 'true' ? '1' : '0'); + url.searchParams.append("problemUUID",ww_id); + } + + // get the json and do stuff with what we get + $.getJSON(url.toString(), (data) => { + // Create the form that will contain the text and input fields of the interactive problem. + const form = document.createElement("form"); + form.id = ww_id + "-form"; + + // Create a div for the problem text. + const body_div = document.createElement("div"); + body_div.id = ww_id + "-body"; + body_div.classList.add("exercise", "exercise-like"); + body_div.lang = data.lang; + body_div.dir = data.dir; + + // Dump the problem text, answer blanks, etc. + body_div.innerHTML = data.rh_result.text; + + // Replace all hn headings with h6 headings. + for (const tag_name of ['h6', 'h5', 'h4', 'h3', 'h2', 'h1']) { + const headings = body_div.getElementsByTagName(tag_name); + for (heading of headings) { + const new_heading = document.createElement("h6"); + new_heading.innerHTML = heading.innerHTML; + cloneAttributes(new_heading, heading); + new_heading.classList.add('webwork-part'); + heading.replaceWith(new_heading); + } + } + + adjustSrcHrefs(body_div, ww_domain); + + translateHintSol(ww_id, body_div, ww_domain, + ww_container.dataset.hasHint == 'true', ww_container.dataset.hasSolution == 'true', + ww_container.dataset.hintLabelText, ww_container.dataset.solutionLabelText) + + // insert previous answers + if (runestone_logged_in) { + const answersObject = (wwList[ww_id.replace(/-ww-rs$/,'')].answers ? wwList[ww_id.replace(/-ww-rs$/,'')].answers : {'answers' : [], 'mqAnswers' : []}); + const mqAnswers = answersObject.mqAnswers; + for (const mqAnswer in mqAnswers) { + const mqInput = body_div.querySelector('input[id=' + mqAnswer + ']'); + if (mqInput && mqInput.value == '') { + mqInput.setAttribute('value', mqAnswers[mqAnswer]); + } + } + const answers = answersObject.answers; + for (const answer in answers) { + const input = body_div.querySelector('input[id=' + answer + ']'); + if (input && input.value == '') { + input.setAttribute('value', answers[answer]); + } + if (input && input.type.toUpperCase() == 'RADIO') { + const buttons = body_div.querySelectorAll('input[name=' + answer + ']'); + for (const button of buttons) { + if (button.value == answers[answer]) { + button.setAttribute('checked', 'checked'); + } + } + } + if (input && input.type.toUpperCase() == 'CHECKBOX') { + const checkboxes = body_div.querySelectorAll('input[name=' + answer + ']'); + for (const checkbox of checkboxes) { + // This is not a bulletproof approach if the problem used input values that are weird + // For example, with commas in them + // However, we are stuck with WW providing answers[answer] as a string like `[value0, value1]` + // and note that it is not `["value0", "value1"]`, so we cannot cleanly parse it into an array + let checkbox_regex = new RegExp('(\\[|, )' + checkbox.value + '(, |\\])'); + if (answers[answer].match(checkbox_regex)) { + checkbox.setAttribute('checked', 'checked'); + } + } + } + var select = body_div.querySelector('select[id=' + answer + ']'); + if (select && answers[answer]) { + // answers[answer] may be wrapped in \text{...} that we want to remove, since value does not have this. + let this_answer = answers[answer]; + if (/^\\text\{.*\}$/.test(this_answer)) {this_answer = this_answer.match(/^\\text\{(.*)\}$/)[1]}; + let quote_escaped_answer = this_answer.replace(/"/g, '\\"'); + const option = body_div.querySelector(`select[id="${answer}"] option[value="${quote_escaped_answer}"]`); + if (option) {option.setAttribute('selected', 'selected')}; + } + } + } + + // insert our cleaned up problem text + form.appendChild(body_div); + + // Set up hidden input fields that the form uses + const wwInputs = { + problemSeed: data.inputs_ref.problemSeed, + problemUUID: data.inputs_ref.problemUUID, + psvn: data.inputs_ref.psvn, + courseName: ww_course_id, + courseID: ww_course_id, + userID: ww_user_id, + course_password: ww_course_password, + displayMode: "MathJax", + session_key: data.rh_result.session_key, + outputformat: "raw", + language: data.formLanguage, + showSummary: data.showSummary, + // note ww_container.dataset.hasSolution is a string, possibly 'false' which is true + showSolutions: ww_container.dataset.hasSolution == 'true' ? '1' : '0', + showHints: ww_container.dataset.hasHint == 'true' ? '1' : '0', + forcePortNumber: data.forcePortNumber + }; + + if (ww_sourceFilePath) wwInputs.sourceFilePath = ww_sourceFilePath; + else if (ww_problemSource) wwInputs.problemSource = ww_problemSource; + + for (const wwInputName of Object.keys(wwInputs)) { + const input = document.createElement('input'); + input.type = 'hidden'; + input.name = wwInputName; + input.value = wwInputs[wwInputName]; + form.appendChild(input); + } + + // Prepare answers object + const answers = {}; + // id the answers even if we won't populate them + Object.keys(data.rh_result.answers).forEach(function(id) { + answers[id] = {}; + }, data.rh_result.answers); + if (ww_container.dataset.hasAnswer == 'true') { + // Update answer data + Object.keys(data.rh_result.answers).forEach(function(id) { + answers[id] = { + correct_ans: this[id].correct_ans, + correct_ans_latex_string: this[id].correct_ans_latex_string, + correct_choice: this[id].correct_choice, + correct_choices: this[id].correct_choices, + }; + }, data.rh_result.answers); + } + + let buttonContainer = ww_container.querySelector('.problem-buttons.webwork'); + // Create the submission buttons if they have not yet been created. + if (!buttonContainer) { + // Hide the original div that contains the old make active button. + ww_container.querySelector('.problem-buttons').classList.add('hidden-content'); + // And the newer activate button if it is there + if (activate_button != null) {activate_button.classList.add('hidden-content');}; + + // Create a new div for the webwork buttons. + buttonContainer = document.createElement('div'); + buttonContainer.classList.add('problem-buttons', 'webwork'); + if (activate_button != null) { + // Make sure the button container follows the activate button in the DOM + activate_button.after(buttonContainer); + } else { + ww_container.prepend(buttonContainer); + } + + // Check button + const check = document.createElement("button"); + check.type = "button"; + check.id = ww_id + '-check'; + check.style.marginRight = "0.25rem"; + check.classList.add('webwork-button'); + + // Adjust if more than one answer to check + const answerCount = body_div.querySelectorAll("input:not([type=hidden])").length + + body_div.querySelectorAll("select:not([type=hidden])").length; + + check.textContent = runestone_logged_in ? localize_submit : localize_check_responses; + check.addEventListener('click', () => handleWW(ww_id, "check")); + + buttonContainer.appendChild(check); + + // Show correct answers button if original PTX has answer knowl. + if (ww_container.dataset.hasAnswer == 'true') { + const correct = document.createElement("button"); + correct.classList.add("show-correct", 'webwork-button'); + correct.type = "button"; + correct.style.marginRight = "0.25rem"; + correct.textContent = localize_reveal; + correct.addEventListener('click', () => WWshowCorrect(ww_id, answers)); + buttonContainer.appendChild(correct); + } + + // randomize button + const randomize = document.createElement("button") + randomize.type = "button"; + randomize.classList.add('webwork-button'); + randomize.style.marginRight = "0.25rem"; + randomize.textContent = localize_randomize; + randomize.addEventListener('click', () => handleWW(ww_id, 'randomize')); + buttonContainer.appendChild(randomize) + + // reset button + const reset = document.createElement("button") + reset.type = "button" + reset.classList.add('webwork-button'); + reset.textContent = localize_reset; + reset.addEventListener('click', () => resetWW(ww_id)); + buttonContainer.appendChild(reset) + } else { + // Update the click handler for the show correct button. + if (ww_container.dataset.hasAnswer == 'true') { + const correct = buttonContainer.querySelector('.show-correct'); + const correctNew = correct.cloneNode(true); + correctNew.addEventListener('click', () => WWshowCorrect(ww_id, answers)); + correct.replaceWith(correctNew); + } + } + + if (action == 'check') { + // Runestone trigger + // TODO: condition on platform=Runestone + $("body").trigger('runestone_ww_check', data) + + const inputs = body_div.querySelectorAll("input:not([type=hidden])"); + for (const input of inputs) { + const name = input.name; + if (input.type == 'text' && answers[name]) { + const score = data.rh_result.answers[name].score; + let headline = ''; + let correctClass = ''; + if (score >= 1) { + headline = localize_correct + '!'; + correctClass = 'correct'; + } else if (score > 0 && score < 1) { + headline = `${Math.round(score * 100)}% ${localize_correct}.`; + correctClass = 'partly-correct'; + } else if (data.rh_result.answers[name].student_ans == '') { + headline = localize_blank + '.'; + correctClass = 'blank'; + } else if (score <= 0) { + headline = localize_incorrect + '.'; + correctClass = 'incorrect'; + } + let title = `${headline}`; + let message = (data.rh_result.answers[name].ans_message ? data.rh_result.answers[name].ans_message : ''); + input.classList.add(correctClass); + // make a span that contains feedback + feedbackSpan = document.createElement('span'); + feedbackSpan.id = `${ww_id}-${name}-feedback`; + feedbackHeadline = document.createElement('span'); + feedbackHeadline.id = `${ww_id}-${name}-headline`; + feedbackHeadline.textContent = headline; + feedbackSpan.appendChild(feedbackHeadline); + feedbackMessage = document.createElement('span'); + feedbackMessage.id = `${ww_id}-${name}-message`; + feedbackMessage.textContent = message; + feedbackSpan.appendChild(feedbackMessage); + // make a non breaking span to surround input, sr span, and feedback button + let nbSpan = document.createElement('span'); + nbSpan.classList.add("nobreak"); + input.parentNode.insertBefore(nbSpan, input); + nbSpan.appendChild(input); + // put a visibly hidden (screen-reader only) span with feedback + let srSpan = document.createElement('span'); + srSpan.classList.add("visually-hidden"); + srSpan.appendChild(feedbackSpan); + nbSpan.appendChild(srSpan); + // give the feedback to sr users immediately + input.setAttribute('aria-describedby',`${ww_id}-${name}-feedback`); + // make the feedback button + srSpan.after(createFeedbackButton(`${ww_id}-${name}`, title, message)); + + } + + if (input.type.toUpperCase() == 'RADIO' && answers[name]) { + const score = data.rh_result.answers[name].score; + const student_ans = data.rh_result.answers[name].student_value || data.rh_result.answers[name].student_ans; + const correct_ans = data.rh_result.answers[name].correct_choice || data.rh_result.answers[name].correct_ans; + if (input.value == student_ans) { + if (score == 1) { + input.parentNode.classList.add('correct'); + } else { + input.parentNode.classList.add('incorrect'); + } + const feedbackButton = createFeedbackButton(`${ww_id}-${name}`, + (student_ans == correct_ans) + ? `${localize_correct}` : `${localize_incorrect}.`) + feedbackButton.style.marginRight = '0.25rem'; + input.after(feedbackButton); + } + } + + if (input.type.toUpperCase() == 'CHECKBOX' && answers[name]) { + const score = data.rh_result.answers[name].score; + const student_ans = data.rh_result.answers[name].student_ans; + const correct_ans = data.rh_result.answers[name].correct_ans; + if (input.value == data.rh_result.answers[name].firstElement) { + const checkbox_div = input.parentNode.parentNode; + if (score == 1) { + checkbox_div.classList.add('correct'); + } else { + checkbox_div.classList.add('incorrect'); + } + const feedbackButton = createFeedbackButton(`${ww_id}-${name}`, + (student_ans == correct_ans) + ? `${localize_correct}` : `${localize_incorrect}.`) + checkbox_div.insertBefore(feedbackButton, checkbox_div.firstChild); + } + } + } + + const hiddenInputs = body_div.querySelectorAll("input[type=hidden]"); + for (const input of hiddenInputs) { + const name = input.name; + if (!input.nextElementSibling) continue; + const graphtoolContainer = input.nextElementSibling.nextElementSibling; + if (graphtoolContainer && answers[name] && graphtoolContainer.classList.contains('graphtool-container')) { + graphtoolContainer.style.position = 'relative'; + const score = data.rh_result.answers[name].score; + let title = ''; + if (score == 1) { + title = `${localize_correct}`; + } else if (score > 0 && score < 1) { + title = `${Math.round(score * 100)}% ${localize_correct}.`; + } else if (data.rh_result.answers[name].student_ans == '') { + // do nothing if the submitted answer is blank and the problem has not already been scored as correct + continue; + } else if (score == 0) { + title = `${localize_incorrect}.`; + } + + const feedbackButton = createFeedbackButton(`${ww_id}-${name}`, title, data.rh_result.answers[name].ans_message); + feedbackButton.style.position = 'absolute'; + feedbackButton.style.left = '100%'; + feedbackButton.style.top = 0; + feedbackButton.style.marginLeft = '0.5rem'; + feedbackButton.dataset.bsContainer = 'body'; + graphtoolContainer.appendChild(feedbackButton); + if (score == 1) { + graphtoolContainer.classList.add('correct'); + } else { + graphtoolContainer.classList.add('incorrect'); + } + + } + } + + const selects = body_div.querySelectorAll("select:not([type=hidden])"); + for (const select of selects) { + const name = select.name; + const score = data.rh_result.answers[name].score; + let title = ''; + if (score == 1) { + headline = localize_correct + '!'; + correctClass = 'correct'; + } else { + headline = localize_incorrect + '.'; + correctClass = 'incorrect'; + } + select.classList.add(correctClass); + title = `${headline}`; + // make a span that contains feedback + feedbackSpan = document.createElement('span'); + feedbackSpan.id = `${ww_id}-${name}-feedback`; + feedbackHeadline = document.createElement('span'); + feedbackHeadline.id = `${ww_id}-${name}-headline`; + feedbackHeadline.textContent = headline; + feedbackSpan.appendChild(feedbackHeadline); + // make a non breaking span to surround select, sr span, and feedback button + let nbSpan = document.createElement('span'); + nbSpan.classList.add("nobreak"); + select.parentNode.insertBefore(nbSpan, select); + nbSpan.appendChild(select); + // put a span around select since select does not support ::after pseudo elements + let psSpan = document.createElement('span'); + psSpan.classList.add("select-wrapper", correctClass); + select.parentNode.insertBefore(psSpan, select); + psSpan.appendChild(select); + // put a visibly hidden (screen-reader only) span with feedback + let srSpan = document.createElement('span'); + srSpan.classList.add("visually-hidden"); + srSpan.appendChild(feedbackSpan); + nbSpan.appendChild(srSpan); + // give the feedback to sr users immediately + select.setAttribute('aria-describedby',`${ww_id}-${name}-feedback`); + // make the feedback button + srSpan.after(createFeedbackButton(`${ww_id}-${name}`, title)); + } + } + + let iframeContents = '' + + '' + + `` + + '' + + '' + + '' + + ''; + + // Determine javascript and css dependencies + const extra_css_files = []; + const extra_js_files = []; + //if (data.rh_result.flags.extra_js_files) { + // for (const jsFile of data.rh_result.flags.extra_js_files) { + // if (jsFile.file == "js/apps/GraphTool/graphtool.min.js" || jsFile.file == "js/apps/Scaffold/scaffold.js") { + // extra_css_files.push({ file: 'js/vendor/bootstrap/css/bootstrap.css', external: '0' }); + // extra_css_files.push({ file: 'themes/math4/math4.css', external: '0' }); + // extra_js_files.push({ file: 'js/vendor/bootstrap/js/bootstrap.js', external: '0' }); + // } + // } + //} + + if (data.extra_css_files) data.extra_css_files.unshift(...extra_css_files); + else data.extra_css_files = extra_css_files; + for (const cssFile of data.extra_css_files) { + if (/knowl/.test(cssFile.file)) continue; + iframeContents += ''; + } + + if (data.extra_js_files) data.extra_js_files.unshift(...extra_js_files); + else data.extra_js_files = extra_js_files; + for (const jsFile of data.extra_js_files) { + if (/knowl/.test(jsFile.file)) continue; + iframeContents += ''; + } + + iframeContents += + '' + + '' + + '' + + `` + + '
    ' + form.outerHTML + '
    ' + + ''; + + let iframe; + // If there is no action this is the initialization call. + if (!action) { + // Create the iframe. + iframe = document.createElement('iframe'); + iframe.style.width = '1px'; + iframe.style.minWidth = '100%'; + iframe.classList.add('problem-iframe'); + + // Hide the static problem + ww_container.querySelector('.problem-contents').classList.add('hidden-content'); + + if (activate_button != null) { + // Make sure the iframe follows the activate button in the DOM + activate_button.after(iframe); + } else { + ww_container.prepend(iframe); + } + + iFrameResize({ checkOrigin: false, scrolling: 'omit', heightCalculationMethod: 'min' }, iframe); + + iframe.addEventListener('load', () => { + // Set up form submission from inside the iframe. + const iframeForm = iframe.contentDocument.getElementById(ww_id + '-form'); + iframeForm.addEventListener('submit', (e) => { + handleWW(ww_id, "check"); + e.preventDefault(); + }); + + iframe.contentDocument.querySelectorAll('.collapse.in').forEach(collapse => collapse.classList.add('expanded')); + iframe.contentWindow.jQuery('.collapse').on('shown', function(e) { if (e.target != this) return; this.classList.add('expanded'); }); + iframe.contentWindow.jQuery('.collapse').on('hide', function(e) { if (e.target != this) return; this.classList.remove('expanded'); }); + + iframe.contentDocument.querySelectorAll("button.ww-feedback[data-bs-content]").forEach(button => { + const bsPopover = new iframe.contentWindow.bootstrap.Popover(button, { + html: true, + placement: 'bottom', + fallbackPlacements: [], + trigger: 'click', + customClass: 'result-popover' + (button.dataset.emptyContent ? ' nocontent' : '') + }); + bsPopover.show(); + const content = iframe.contentDocument.getElementById(button.id.replace("-feedback-button", "-content")); + const popover = content.parentNode.parentNode; + bsPopover.hide(); + + /*button.addEventListener('shown.bs.popover', () => { + // Note that the button itself has the aria-describedby attribute set by bootstrap automatically. + button.previousElementSibling?.setAttribute('aria-describedby', popover.id); + });*/ + + button.addEventListener('click', () => bsPopover.show(), { once: true }); + + popover.querySelector('.popover-arrow').remove(); + const title = popover.querySelector('.popover-header'); + if (button.dataset.emptyContent) content.parentNode.remove(); + if (title.textContent == localize_correct + '!') title.classList.add('correct'); + }); + iframe.contentWindow.MathJax.startup.promise.then(() => iframe.contentWindow.MathJax.typesetPromise(['.popover', '.popover-content'])); + }); + } else { + iframe = ww_container.querySelector('.problem-iframe'); + } + + iframe.srcdoc = iframeContents; + + iframe.addEventListener('load', () => { + // Remove the loader overlay + loader.remove(); + }, { once: true }) + + // Place focus on the problem. + ww_container.focus() + }); +} + +function WWshowCorrect(ww_id, answers) { + const ww_container = document.getElementById(ww_id); + const iframe = ww_container.querySelector('.problem-iframe'); + + const body = iframe.contentDocument.getElementById(ww_id + '-body') + $("body").trigger("runestone_show_correct", { + "ww_id": ww_id, + "answers": answers + }); + + let inputs = body.querySelectorAll("input:not([type=hidden])"); + for (const input of inputs) { + const name = input.name; + const span_id = `${ww_id}-${name}-correct`; + + if (input.type == 'text' && answers[name] && !(iframe.contentDocument.getElementById(span_id))) { + const input_id = input.id; + const mq_span = iframe.contentDocument.getElementById(`mq-answer-${input_id}`); + if (mq_span) { + mq_span.style.display = 'none'; + } + const feedbackButton = iframe.contentDocument.getElementById(`${ww_id}-${name}-feedback-button`); + if (feedbackButton) { + iframe.contentWindow.bootstrap.Popover.getInstance(feedbackButton)?.hide(); + feedbackButton.remove(); + } + label = iframe.contentDocument.getElementById(`${span_id}-label`); + if (label) { + label.parentElement.insertBefore(input, label); + label.remove(); + } + input.type = "hidden"; + // we need to convert things like < in answers to < + const correct_ans_text = iframe.contentDocument.createElement('div'); + correct_ans_text.innerHTML = answers[name].correct_ans; + input.value = correct_ans_text.textContent; + const show_span = iframe.contentDocument.createElement('span'); + show_span.id = span_id; + show_span.appendChild(answers[name].correct_ans_latex_string + ? iframe.contentDocument.createTextNode('\\(' + answers[name].correct_ans_latex_string + '\\)') + : iframe.contentDocument.createTextNode(answers[name].correct_ans)); + input.parentElement.insertBefore(show_span, input); + } + + if (input.type.toUpperCase() == 'RADIO' && answers[name]) { + const feedbackButton = iframe.contentDocument.getElementById(`${ww_id}-${name}-feedback-button`); + if (feedbackButton) { + iframe.contentWindow.bootstrap.Popover.getInstance(feedbackButton)?.hide(); + feedbackButton.remove(); + } + const correct_ans = answers[name].correct_choice || answers[name].correct_ans; + if (input.value == correct_ans) { + input.checked = true; + //input.setAttribute('checked', 'checked'); + } else { + input.checked = false; + } + } + + if (input.type.toUpperCase() == 'CHECKBOX' && answers[name]) { + const correct_choices = answers[name].correct_choices; + if (correct_choices.includes(input.value)) { + input.checked = true; + // input.setAttribute('checked', 'checked'); + } else { + input.checked = false; + // input.setAttribute('checked', false); + } + } + } + + const hiddenInputs = body.querySelectorAll("input[type=hidden]"); + for (const input of hiddenInputs) { + const name = input.name; + if (!input.nextElementSibling) continue; + const graphtoolContainer = input.nextElementSibling.nextElementSibling; + if (graphtoolContainer && answers[name] && graphtoolContainer.classList.contains('graphtool-container')) { + const feedbackButton = iframe.contentDocument.getElementById(`${ww_id}-${name}-feedback-button`); + if (feedbackButton) { + iframe.contentWindow.bootstrap.Popover.getInstance(feedbackButton)?.hide(); + feedbackButton.remove(); + } + const correct_ans_div = iframe.contentDocument.createElement('div'); + input.parentElement.insertBefore(correct_ans_div, graphtoolContainer); + graphtoolContainer.style.display = 'none'; + input.value = answers[name].correct_ans; + iframe.contentWindow.jQuery(correct_ans_div).html(answers[name].correct_ans_latex_string); + const script = iframe.contentDocument.createElement('script'); + script.textContent = correct_ans_div.querySelector('script').textContent + .replace('\nwindow.addEventListener("DOMContentLoaded",', '(') + .replace(/;\n$/, '();'); + iframe.contentDocument.body.appendChild(script); + } + } + + let selects = body.querySelectorAll("select:not([type=hidden])"); + for (const select of selects) { + const name = select.name; + const span_id = `${ww_id}-${name}-correct`; + if (answers[name] && !iframe.contentDocument.getElementById(span_id)) { + const feedbackButton = iframe.contentDocument.getElementById(`${ww_id}-${name}-feedback-button`); + if (feedbackButton) { + iframe.contentWindow.bootstrap.Popover.getInstance(feedbackButton)?.hide(); + feedbackButton.remove(); + } + select.style.display = "none"; + select.value = answers[name].correct_ans; + const show_span = iframe.contentDocument.createElement('span'); + show_span.id = span_id; + show_span.appendChild(answers[name].correct_ans_latex_string + ? iframe.contentDocument.createTextNode('\\(' + answers[name].correct_ans_latex_string + '\\)') + : iframe.contentDocument.createTextNode(answers[name].correct_ans)); + select.parentElement.insertBefore(show_span, select); + } + } + + // run MathJax on our new rendering + // FIXME: We only need to typeset the added elements, not the entire body. + const mathjaxTypesetScript = iframe.contentDocument.createElement('script'); + mathjaxTypesetScript.textContent = 'MathJax.startup.promise.then(() => MathJax.typesetPromise([document.body]));'; + iframe.contentDocument.body.appendChild(mathjaxTypesetScript); +} + +function resetWW(ww_id) { + const ww_container = document.getElementById(ww_id); + const activate_button = document.getElementById(ww_id + '-button'); + + ww_container.dataset.current_seed = ww_container.dataset.seed; + + iframe = ww_container.querySelector('.problem-iframe'); + iframe.remove(); + + ww_container.querySelector('.problem-contents').classList.remove('hidden-content'); + + ww_container.querySelector('.problem-buttons.webwork').remove(); + ww_container.querySelector('.problem-buttons').classList.remove('hidden-content'); + // if the newer activate button is there (but hidden) bring it back too + if (activate_button != null) {activate_button.classList.remove('hidden-content');}; +} + +function adjustSrcHrefs(container,ww_domain) { + container.querySelectorAll('[href]').forEach((node) => { + const href = node.attributes.href.value; + if (href !== '#' && !href.match(/^[a-z]+:\/\//i)) node.href = ww_domain + '/' + href; + }); + container.querySelectorAll('[src]').forEach((node) => { + node.src = new URL(node.attributes.src.value, ww_domain).href; + }); +} + +function translateHintSol(ww_id, body_div, ww_domain, b_ptx_has_hint, b_ptx_has_solution, hint_label_text, solution_label_text) { + // the problem text may come with "hint"s and "solution"s + // Each one is an "a" with the class ".knowl" and the data-type attribute of either "hint" or "solution". + // the WeBWorK knowl js would normally handle this, but we want PreTeXt knowl js to handle it + // so we replace the "a" with the content that should be there for PTX knowl js + // also if hint/sol were missing from the static version, we want these removed here + + const hintSols = body_div.querySelectorAll('.knowl[data-type="hint"],.knowl[data-type="solution"]'); + let solutionlikewrapper; + for (const hintSol of hintSols) { + const hintsolp = hintSol.parentNode; + if (!hintsolp) continue; + const hintsolpp = hintsolp.parentNode; //the div that contains each p with a knowl anchor + const hintSolType = hintSol.dataset.type; + + if (hintsolpp.querySelectorAll(".webwork.solutions").length == 0) + { + solutionlikewrapper = document.createElement('div'); + solutionlikewrapper.classList.add('webwork', 'solutions'); + hintsolpp.insertBefore(solutionlikewrapper, hintsolp); + } + + if ((hintSolType == 'solution' && !b_ptx_has_solution) || + (hintSolType == 'hint' && !b_ptx_has_hint)) + continue; + + const knowlDetails = document.createElement('details'); + knowlDetails.classList.add(hintSolType); + knowlDetails.classList.add('solution-like'); + knowlDetails.classList.add('born-hidden-knowl'); + + const knowlSummary = document.createElement('summary'); + const summaryLabel = document.createElement('span'); + summaryLabel.classList.add('type'); + summaryLabel.innerHTML = hintSolType == 'hint' ? hint_label_text : solution_label_text; + knowlSummary.appendChild(summaryLabel); + knowlDetails.appendChild(knowlSummary); + + const knowlContents = document.createElement('div'); + knowlContents.classList.add(hintSolType); + knowlContents.classList.add('solution-like'); + knowlContents.innerHTML = hintsolp.firstElementChild.dataset.knowlContents; + knowlDetails.appendChild(knowlContents); + + adjustSrcHrefs(knowlContents, ww_domain) + solutionlikewrapper.appendChild(knowlDetails); + } + for (const hintSol of hintSols) { + hintSol.parentNode?.remove(); + } +} + +function cloneAttributes(target, source) { + [...source.attributes].forEach( attr => { target.setAttribute(attr.nodeName ,attr.nodeValue) }); +} + +function createFeedbackButton(id, title, content) { + const feedbackButton = document.createElement('button'); + feedbackButton.dataset.bsTitle = title; + feedbackButton.dataset.bsContent = `
    ${content || ''}
    `; + if (!content) feedbackButton.dataset.emptyContent = '1'; + const contentSpan = document.createElement('span'); + contentSpan.innerHTML = ``; + feedbackButton.appendChild(contentSpan); + feedbackButton.type = 'button'; + feedbackButton.classList.add('ww-feedback'); + + feedbackButton.id = `${id}-feedback-button`; + feedbackButton.dataset.bsToggle = 'popover'; + + return feedbackButton; +} + +function webworkSeedHash(string) { + var hash = 0, i, chr; + if (string.length === 0) return hash; + for (i = 0; i < string.length; i++) { + chr = string.charCodeAt(i); + hash = ((hash << 5) - hash) + chr; + hash |= 0; //Convert to 32bit integer + } + return Math.abs(hash); +}; diff --git a/slides/_static/pretext/js/pretext-webwork/2.19/pretext-webwork.js b/slides/_static/pretext/js/pretext-webwork/2.19/pretext-webwork.js new file mode 100755 index 0000000..99fb2f0 --- /dev/null +++ b/slides/_static/pretext/js/pretext-webwork/2.19/pretext-webwork.js @@ -0,0 +1,677 @@ +//Critical: +// TODO: Think about what should be done for an essay question. + +//Accessibility: + +//Enhancement: +// TODO: Have randomize check that new seed is actually producing new HTML. +// TODO: Don't offer randomize button unless we know a new version can be produced after trying a few seeds. + +//Styling: +// TODO: Review all styling in all scenarios (staged/not, correct/partly-correct/incorrect/blank, single/multiple) + +async function handleWW(ww_id, action) { + const ww_container = document.getElementById(ww_id); + const ww_domain = ww_container.dataset.domain; + const ww_processing = 'webwork2'; + const ww_origin = ww_container.dataset.origin; + const ww_problemSource = ww_container.dataset.problemsource; + const ww_sourceFilePath = ww_container.dataset.sourcefilepath; + const ww_course_id = ww_container.dataset.courseid; + const ww_user_id = ww_container.dataset.userid; + const ww_passwd = ww_container.dataset.coursepassword; + const localize_submit = ww_container.dataset.localizeSubmit || "Submit"; + const localize_check_responses = ww_container.dataset.localizeCheckResponses || "Check Responses"; + const localize_reveal = ww_container.dataset.localizeReveal || "Reveal"; + const localize_randomize = ww_container.dataset.localizeRandomize || "Randomize"; + const localize_reset = ww_container.dataset.localizeReset || "Reset"; + const runestone_logged_in = (typeof eBookConfig !== 'undefined' && eBookConfig.username !== ''); + // will be null on pages generated prior to late December 2022 + const activate_button = document.getElementById(ww_id + '-button') + + // Set the current seed + if (!action) { + ww_container.dataset.current_seed = ww_container.dataset.seed; + if (runestone_logged_in) { + ww_container.dataset.current_seed = webworkSeedHash(eBookConfig.username + ww_container.dataset.current_seed); + } + } + else if (action == 'randomize') ww_container.dataset.current_seed = Number(ww_container.dataset.current_seed) + 100; + + let loader = document.createElement('div'); + loader.style.position = 'absolute'; + loader.style.left = 0; + loader.style.top = 0; + loader.style.backgroundColor = 'rgba(0.2, 0.2, 0.2, 0.4)'; + loader.style.color = 'white'; + loader.style.width = '100%'; + loader.style.height = '100%'; + loader.style.display = 'flex'; + loader.style.alignItems = 'center'; + loader.style.justifyContent = 'center'; + loader.style.marginTop = '0'; + loader.tabIndex = -1; + const loaderText = document.createElement('span'); + loaderText.textContent = 'Loading'; + loaderText.style.fontSize = '2rem'; + loader.appendChild(loaderText); + ww_container.appendChild(loader); + loader.focus(); + + if (!action) { + // Determine if static version shows hints, solutions, or answers and save that information in the container dataset for later runs. + ww_container.dataset.hasHint = ww_container.getElementsByClassName('hint').length > 0; + ww_container.dataset.hasSolution = ww_container.getElementsByClassName('solution').length > 0; + ww_container.dataset.hasAnswer = ww_container.getElementsByClassName('answer').length > 0; + // Get (possibly localized) label text for hints and solutions. + ww_container.dataset.hintLabelText = ww_container.dataset.hasHint == 'true' + ? ww_container.querySelectorAll('.hint-knowl span.type, details.hint span.type')[0].textContent : 'Hint'; + ww_container.dataset.solutionLabelText = ww_container.dataset.hasSolution == 'true' + ? ww_container.querySelectorAll('.solution-knowl span.type, details.solution span.type')[0].textContent : 'Solution'; + + ww_container.tabIndex = -1; + } + + let url; + if (ww_processing == 'webwork2') { + url = new URL(ww_domain + '/webwork2/render_rpc'); + } + let formData = new FormData(); + let generatedPG = 'generated/webwork/pg/'; + if (runestone_logged_in) { + generatedPG = `/ns/books/published/${eBookConfig.basecourse}/${generatedPG}`; + } + + if (action == 'check' || action =='reveal') { + const iframe = ww_container.querySelector('.problem-iframe'); + formData = new FormData(iframe.contentDocument.getElementById(ww_id + "-form")); + formData.set("answersSubmitted", '1'); + formData.set('WWsubmit', "1"); + if (action == 'reveal' && ww_container.dataset.hasAnswer == 'true') { + formData.set('WWcorrectAnsOnly', "1"); + } + if (ww_origin == 'generated') { + const rawProblemSource = await fetch(generatedPG + ww_problemSource).then((r) => r.text()); + formData.set("rawProblemSource", rawProblemSource); + } + else if (ww_origin == 'webwork2') formData.set("sourceFilePath", ww_sourceFilePath); + } else { + formData.set("problemSeed", ww_container.dataset.current_seed); + if (ww_origin == 'generated') { + const rawProblemSource = await fetch(generatedPG + ww_problemSource).then((r) => r.text()); + formData.set("rawProblemSource", rawProblemSource); + } + else if (ww_origin == 'webwork2') formData.set("sourceFilePath", ww_sourceFilePath); + formData.set("answersSubmitted", '0'); + formData.set("displayMode", "MathJax"); + formData.set("courseID", ww_course_id); + formData.set("user", ww_user_id); + formData.set("userID", ww_user_id); + formData.set("passwd", ww_passwd); + formData.set("disableCookies", '1'); + formData.set("outputformat", "raw"); + // note ww_container.dataset.hasSolution is a string, possibly 'false' which is true + formData.set("showSolutions", ww_container.dataset.hasSolution == 'true' ? '1' : '0'); + formData.set("showHints", ww_container.dataset.hasHint == 'true' ? '1' : '0'); + formData.set("problemUUID",ww_id); + } + + // If in Runestone, check if there are previous answer submissions and get them now to use in the form. + let checkboxesString = ''; + if (runestone_logged_in && !action) { + const answersObject = (wwList[ww_id.replace(/-ww-rs$/,'')].answers ? wwList[ww_id.replace(/-ww-rs$/,'')].answers : {'answers' : [], 'mqAnswers' : []}); + const previousAnswers = answersObject.answers; + if (previousAnswers !== null) { + formData.set('WWsubmit', 1); + } + for (const answer in previousAnswers) { + if (previousAnswers[answer].constructor === Array) { + for (const k in previousAnswers[answer]) { + checkboxesString += '&'; + checkboxesString += answer; + checkboxesString += '='; + checkboxesString += previousAnswers[answer][k]; + } + } else { + formData.set(answer, previousAnswers[answer]); + } + } + } + // Need to get form data as a string, including possible repeated checkbox names + // Do not pass post data as an object, or checkbox names will overwrite one another + const formString = new URLSearchParams(formData).toString(); + + $.post(url, formString + checkboxesString, (data) => { + // Create the form that will contain the text and input fields of the interactive problem. + const form = document.createElement("form"); + form.id = ww_id + "-form"; + form.dataset.iframeHeight = 1; + + // Create a div for the problem text. + const body_div = document.createElement("div"); + body_div.id = ww_id + "-body"; + body_div.classList.add("exercise", "exercise-like"); + body_div.lang = data.lang; + body_div.dir = data.dir; + + // Dump the problem text, answer blanks, etc. + body_div.innerHTML = data.rh_result.text; + + // If showPartialCorrectAnswers = 0, alter the feedback buttons according to whether or not the score is 100%. + if ('showPartialCorrectAnswers' in data.rh_result.flags && data.rh_result.flags.showPartialCorrectAnswers == 0) { + if ('score' in data.rh_result.problem_result && data.rh_result.problem_result.score >= 1) { + body_div.querySelectorAll('button.ww-feedback-btn').forEach( + function(button) { + button.classList.remove('btn-info'); + button.classList.add('btn-success'); + button.setAttribute('aria-label', 'Correct'); + button.dataset.bsCustomClass = button.dataset.bsCustomClass + ' correct'; + button.dataset.bsTitle = button.dataset.bsTitle.replace('Answer Preview', 'Correct'); + button.firstChild.classList.add('correct') + } + ); + } else { + body_div.querySelectorAll('button.ww-feedback-btn').forEach( + function(button) { + button.setAttribute('aria-label', 'One or more answers incorrect'); + button.dataset.bsTitle = button.dataset.bsTitle.replace('Answer Preview', 'One or more answers incorrect'); + } + ); + } + } + + // Replace all hn headings with h6 headings. + for (const tag_name of ['h6', 'h5', 'h4', 'h3', 'h2', 'h1']) { + const headings = body_div.getElementsByTagName(tag_name); + for (heading of headings) { + const new_heading = document.createElement("h6"); + new_heading.innerHTML = heading.innerHTML; + cloneAttributes(new_heading, heading); + new_heading.classList.add('webwork-part'); + heading.replaceWith(new_heading); + } + } + + // Hide textarea input and hide associated buttons + var textareas = body_div.getElementsByTagName("textarea"); + for(var i = 0, max = textareas.length; i < max; i++) + { + textareas[i].style.display = "none"; + textareas[i].className = ''; + } + var textareabuttons = body_div.querySelectorAll(".latexentry-preview"); + for(var i = 0, max = textareabuttons.length; i < max; i++) + { + textareabuttons[i].remove(); + } + + adjustSrcHrefs(body_div, ww_domain); + + translateHintSol(ww_id, body_div, ww_domain, + ww_container.dataset.hasHint == 'true', ww_container.dataset.hasSolution == 'true', + ww_container.dataset.hintLabelText, ww_container.dataset.solutionLabelText) + + // insert previous answers + if (runestone_logged_in) { + const answersObject = (wwList[ww_id.replace(/-ww-rs$/,'')].answers ? wwList[ww_id.replace(/-ww-rs$/,'')].answers : {'answers' : [], 'mqAnswers' : []}); + const mqAnswers = answersObject.mqAnswers; + for (const mqAnswer in mqAnswers) { + const mqInput = body_div.querySelector('input[id=' + mqAnswer + ']'); + if (mqInput && mqInput.value == '') { + mqInput.setAttribute('value', mqAnswers[mqAnswer]); + } + } + const answers = answersObject.answers; + for (const answer in answers) { + const input = body_div.querySelector('input[id=' + answer + ']'); + if (input && input.value == '') { + input.setAttribute('value', answers[answer]); + } + } + } + + // insert our cleaned up problem text + form.appendChild(body_div); + + // Set up hidden input fields that the form uses + const wwInputs = { + problemSeed: data.inputs_ref.problemSeed, + problemUUID: data.inputs_ref.problemUUID, + psvn: data.inputs_ref.psvn, + courseName: ww_course_id, + courseID: ww_course_id, + user: ww_user_id, + passwd: ww_passwd, + displayMode: "MathJax", + session_key: data.rh_result.session_key, + outputformat: "raw", + language: data.formLanguage, + showSummary: data.showSummary, + disableCookies: '1', + // note ww_container.dataset.hasSolution is a string, possibly 'false' which is true + showSolutions: ww_container.dataset.hasSolution == 'true' ? '1' : '0', + showHints: ww_container.dataset.hasHint == 'true' ? '1' : '0', + forcePortNumber: data.forcePortNumber + }; + + if (ww_sourceFilePath) wwInputs.sourceFilePath = ww_sourceFilePath; + else if (ww_problemSource && ww_origin == 'webwork2') wwInputs.problemSource = ww_problemSource; + + for (const wwInputName of Object.keys(wwInputs)) { + const input = document.createElement('input'); + input.type = 'hidden'; + input.name = wwInputName; + input.value = wwInputs[wwInputName]; + form.appendChild(input); + } + + let buttonContainer = ww_container.querySelector('.problem-buttons.webwork'); + // Create the submission buttons if they have not yet been created. + if (!buttonContainer) { + // Hide the original div that contains the Activate. + ww_container.querySelector('.problem-buttons').classList.add('hidden-content', 'hidden'); + + // Create a new div for the webwork buttons. + buttonContainer = document.createElement('div'); + buttonContainer.classList.add('problem-buttons', 'webwork'); + if (activate_button != null) { + // Make sure the button container follows the activate button in the DOM + activate_button.after(buttonContainer); + } else { + ww_container.prepend(buttonContainer); + } + + // Check button + const check = document.createElement("button"); + check.type = "button"; + check.id = ww_id + '-check'; + check.style.marginRight = "0.25rem"; + check.classList.add('webwork-button'); + + check.textContent = runestone_logged_in ? localize_submit : localize_check_responses; + + check.addEventListener('click', () => handleWW(ww_id, "check")); + + buttonContainer.appendChild(check); + + // Show correct answers button if original PTX has answer knowl. + if (ww_container.dataset.hasAnswer == 'true') { + const correct = document.createElement("button"); + correct.classList.add("show-correct", 'webwork-button'); + correct.type = "button"; + correct.style.marginRight = "0.25rem"; + correct.textContent = localize_reveal; + correct.addEventListener('click', () => handleWW(ww_id, 'reveal')); + buttonContainer.appendChild(correct); + } + + // randomize button + const randomize = document.createElement("button") + randomize.type = "button"; + randomize.classList.add('webwork-button'); + randomize.style.marginRight = "0.25rem"; + randomize.textContent = localize_randomize; + randomize.addEventListener('click', () => handleWW(ww_id, 'randomize')); + buttonContainer.appendChild(randomize) + + // reset button + const reset = document.createElement("button") + reset.type = "button" + reset.classList.add('webwork-button'); + reset.textContent = localize_reset; + reset.addEventListener('click', () => resetWW(ww_id)); + buttonContainer.appendChild(reset) + } + + if (runestone_logged_in && action == 'check') { + // Runestone trigger + $("body").trigger('runestone_ww_check', data) + } + let courseUrlBase = ''; + if (runestone_logged_in){ + courseUrlBase = '/ns/books/published/' + eBookConfig.basecourse + '/'; + } + let iframeContents = '' + + '' + + `` + + '' + + `` + + '' + + ''; + + // Determine javascript and css dependencies + const extra_css_files = []; + const extra_js_files = []; + + if (data.extra_css_files) data.extra_css_files.unshift(...extra_css_files); + else data.extra_css_files = extra_css_files; + for (const cssFile of data.extra_css_files) { + iframeContents += ''; + } + + if (data.extra_js_files) data.extra_js_files.unshift(...extra_js_files); + else data.extra_js_files = extra_js_files; + for (const jsFile of data.extra_js_files) { + iframeContents += ''; + } + + iframeContents += + `` + + '' + + `` + + '' + + '
    ' + form.outerHTML + '
    ' + + ''; + + let iframe; + // If there is no action this is the initialization call. + if (!action) { + // Create the iframe. + iframe = document.createElement('iframe'); + iframe.style.width = '1px'; + iframe.style.minWidth = '100%'; + iframe.classList.add('problem-iframe'); + + // Hide the static problem + ww_container.querySelector('.problem-contents').classList.add('hidden-content', 'hidden'); + + if (activate_button != null) { + // Make sure the iframe follows the activate button in the DOM + activate_button.after(iframe); + } else { + ww_container.prepend(iframe); + } + + iFrameResize({ checkOrigin: false, scrolling: 'omit', heightCalculationMethod: 'taggedElement' }, iframe); + + iframe.addEventListener('load', () => { + // Set up form submission from inside the iframe. + const iframeForm = iframe.contentDocument.getElementById(ww_id + '-form'); + iframeForm.addEventListener('submit', (e) => { + handleWW(ww_id, "check"); + e.preventDefault(); + }); + + iframe.contentDocument.querySelectorAll('.collapse.in').forEach(collapse => collapse.classList.add('expanded')); + iframe.contentWindow.jQuery('.collapse').on('shown', function(e) { if (e.target != this) return; this.classList.add('expanded'); }); + iframe.contentWindow.jQuery('.collapse').on('hide', function(e) { if (e.target != this) return; this.classList.remove('expanded'); }); + + iframe.contentWindow.MathJax.startup.promise.then(() => iframe.contentWindow.MathJax.typesetPromise(['.popover', '.popover-content'])); + }); + } else { + iframe = ww_container.querySelector('.problem-iframe'); + } + + iframe.srcdoc = iframeContents; + + iframe.addEventListener('load', () => { + // Remove the loader overlay + loader.remove(); + }, { once: true }) + + // Place focus on the problem. + ww_container.focus() + }, "json"); +} + +function WWshowCorrect(ww_id, answers) { + const ww_container = document.getElementById(ww_id); + const iframe = ww_container.querySelector('.problem-iframe'); + + const body = iframe.contentDocument.getElementById(ww_id + '-body') + $("body").trigger("runestone_show_correct", { + "ww_id": ww_id, + "answers": answers + }); + + let inputs = body.querySelectorAll("input:not([type=hidden])"); + for (const input of inputs) { + const name = input.name; + const span_id = `${ww_id}-${name}-correct`; + + if (input.type == 'text' && answers[name] && !(iframe.contentDocument.getElementById(span_id))) { + const input_id = input.id; + const mq_span = iframe.contentDocument.getElementById(`mq-answer-${input_id}`); + if (mq_span) { + mq_span.style.display = 'none'; + } + label = iframe.contentDocument.getElementById(`${span_id}-label`); + if (label) { + label.parentElement.insertBefore(input, label); + label.remove(); + } + input.type = "hidden"; + // we need to convert things like < in answers to < + const correct_ans_text = iframe.contentDocument.createElement('div'); + correct_ans_text.innerHTML = answers[name].correct_ans; + input.value = correct_ans_text.textContent; + const show_span = iframe.contentDocument.createElement('span'); + show_span.id = span_id; + show_span.appendChild(answers[name].correct_ans_latex_string + ? iframe.contentDocument.createTextNode('\\(' + answers[name].correct_ans_latex_string + '\\)') + : iframe.contentDocument.createTextNode(answers[name].correct_ans)); + input.parentElement.insertBefore(show_span, input); + } + + if (input.type.toUpperCase() == 'RADIO' && answers[name]) { + const correct_ans = answers[name].correct_choice || answers[name].correct_ans; + if (input.value == correct_ans) { + input.checked = true; + } else { + input.checked = false; + } + } + + if (input.type.toUpperCase() == 'CHECKBOX' && answers[name]) { + const correct_choices = answers[name].correct_choices; + if (correct_choices.includes(input.value)) { + input.checked = true; + } else { + input.checked = false; + } + } + } + + const hiddenInputs = body.querySelectorAll("input[type=hidden]"); + for (const input of hiddenInputs) { + const name = input.name; + if (!input.nextElementSibling) continue; + const graphtoolContainer = input.nextElementSibling.nextElementSibling; + if (graphtoolContainer && answers[name] && graphtoolContainer.classList.contains('graphtool-container')) { + const correct_ans_div = iframe.contentDocument.createElement('div'); + input.parentElement.insertBefore(correct_ans_div, graphtoolContainer); + graphtoolContainer.style.display = 'none'; + input.value = answers[name].correct_ans; + iframe.contentWindow.jQuery(correct_ans_div).html(answers[name].correct_ans_latex_string); + const script = iframe.contentDocument.createElement('script'); + script.textContent = correct_ans_div.querySelector('script').textContent + .replace('\nwindow.addEventListener("DOMContentLoaded",', '(') + .replace(/;\n$/, '();'); + iframe.contentDocument.body.appendChild(script); + } + } + + let selects = body.querySelectorAll("select:not([type=hidden])"); + for (const select of selects) { + const name = select.name; + const span_id = `${ww_id}-${name}-correct`; + if (answers[name] && !iframe.contentDocument.getElementById(span_id)) { + select.style.display = "none"; + select.value = answers[name].correct_ans; + const show_span = iframe.contentDocument.createElement('span'); + show_span.id = span_id; + show_span.appendChild(answers[name].correct_ans_latex_string + ? iframe.contentDocument.createTextNode('\\(' + answers[name].correct_ans_latex_string + '\\)') + : iframe.contentDocument.createTextNode(answers[name].correct_ans)); + select.parentElement.insertBefore(show_span, select); + } + } + + // run MathJax on our new rendering + // FIXME: We only need to typeset the added elements, not the entire body. + const mathjaxTypesetScript = iframe.contentDocument.createElement('script'); + mathjaxTypesetScript.textContent = 'MathJax.startup.promise.then(() => MathJax.typesetPromise([document.body]));'; + iframe.contentDocument.body.appendChild(mathjaxTypesetScript); +} + +function resetWW(ww_id) { + const ww_container = document.getElementById(ww_id); + const activate_button = document.getElementById(ww_id + '-button'); + + ww_container.dataset.current_seed = ww_container.dataset.seed; + + iframe = ww_container.querySelector('.problem-iframe'); + iframe.remove(); + + ww_container.querySelector('.problem-contents').classList.remove('hidden-content', 'hidden'); + + ww_container.querySelector('.problem-buttons.webwork').remove(); + ww_container.querySelector('.problem-buttons').classList.remove('hidden-content', 'hidden'); + // if the newer activate button is there (but hidden) bring it back too + if (activate_button != null) {activate_button.classList.remove('hidden-content', 'hidden');}; +} + +function adjustSrcHrefs(container,ww_domain) { + container.querySelectorAll('[href]').forEach((node) => { + const href = node.attributes.href.value; + if (href !== '#' && !href.match(/^[a-z]+:\/\//i)) node.href = ww_domain + '/' + href; + }); + container.querySelectorAll('[data-knowl-url]').forEach((node) => { + const dku = node.dataset.knowlUrl; + if (dku !== '#' && !dku.match(/^[a-z]+:\/\//i)) node.dataset.knowlUrl = ww_domain + dku; + }); + container.querySelectorAll('[src]').forEach((node) => { + node.src = new URL(node.attributes.src.value, ww_domain).href; + }); +} + +function translateHintSol(ww_id, body_div, ww_domain, b_ptx_has_hint, b_ptx_has_solution, hint_label_text, solution_label_text) { + // The problem text may come with "hint"s and "solution"s + // Each one is a div.accordion > details.accordion-item > summary.accordion-button + // Styling is not the PTX way, so we change it to one div.solutions + // with (potentially multiple) details.born-hidden-knowl > summary.knowl__link + // Also if hint/sol were missing from the static version, we want these removed here + + const hintSols = body_div.querySelectorAll('.accordion.hint,.accordion.solution'); + if (hintSols.length == 0) {return}; + + for (const hintSol of hintSols) { + const parent = hintSol.parentNode; + solutionlikewrapper = document.createElement('div'); + solutionlikewrapper.classList.add('solutions'); + parent.insertBefore(solutionlikewrapper, hintSol); + + const hintSolType = hintSol.classList.contains('hint') ? 'hint' : 'solution'; + + if ((hintSolType == 'solution' && !b_ptx_has_solution) || + (hintSolType == 'hint' && !b_ptx_has_hint)) + continue; + + const knowlDetails = hintSol.getElementsByTagName('details')[0]; + knowlDetails.className = ''; + knowlDetails.classList.add(hintSolType, 'solution-like', 'born-hidden-knowl'); + solutionlikewrapper.appendChild(knowlDetails); + + const knowlSummary = knowlDetails.getElementsByTagName('summary')[0]; + knowlSummary.className = ''; + knowlSummary.classList.add('knowl__link'); + + const summaryLabel = knowlSummary.children[0]; + summaryLabel.remove(); + + const newLabelSpan = document.createElement('span'); + newLabelSpan.innerHTML = hintSolType == 'hint' ? hint_label_text : solution_label_text; + knowlSummary.appendChild(newLabelSpan); + + const newLabelPeriod = document.createElement('span'); + newLabelPeriod.innerHTML = '.'; + knowlSummary.appendChild(newLabelPeriod); + + adjustSrcHrefs(knowlDetails, ww_domain); + hintSol.remove(); + + const originalDetailsContent = knowlDetails.getElementsByTagName('div')[0]; + const newDetailsContent = originalDetailsContent.getElementsByTagName('div')[0]; + newDetailsContent.className = ''; + newDetailsContent.classList.add(hintSolType, 'solution-like', 'knowl__content'); + knowlDetails.appendChild(newDetailsContent); + originalDetailsContent.remove(); + } +} + +function cloneAttributes(target, source) { + [...source.attributes].forEach( attr => { target.setAttribute(attr.nodeName ,attr.nodeValue) }); +} + +function webworkSeedHash(string) { + var hash = 0, i, chr; + if (string.length === 0) return hash; + for (i = 0; i < string.length; i++) { + chr = string.charCodeAt(i); + hash = ((hash << 5) - hash) + chr; + hash |= 0; //Convert to 32bit integer + } + return Math.abs(hash); +}; diff --git a/slides/_static/pretext/js/pretext-webwork/2.20/pretext-webwork.js b/slides/_static/pretext/js/pretext-webwork/2.20/pretext-webwork.js new file mode 100755 index 0000000..1cd3ca6 --- /dev/null +++ b/slides/_static/pretext/js/pretext-webwork/2.20/pretext-webwork.js @@ -0,0 +1,686 @@ +//Critical: +// TODO: Think about what should be done for an essay question. + +//Accessibility: + +//Enhancement: +// TODO: Have randomize check that new seed is actually producing new HTML. +// TODO: Don't offer randomize button unless we know a new version can be produced after trying a few seeds. + +//Styling: +// TODO: Review all styling in all scenarios (staged/not, correct/partly-correct/incorrect/blank, single/multiple) + +async function handleWW(ww_id, action) { + const ww_container = document.getElementById(ww_id); + const ww_domain = ww_container.dataset.domain; + const ww_processing = 'webwork2'; + const ww_origin = ww_container.dataset.origin; + const ww_problemSource = ww_container.dataset.problemsource; + let ww_baseCourse = ww_container.dataset.documentid; + const ww_sourceFilePath = ww_container.dataset.sourcefilepath; + const ww_course_id = ww_container.dataset.courseid; + const ww_user_id = ww_container.dataset.userid; + const ww_passwd = ww_container.dataset.coursepassword; + const localize_submit = ww_container.dataset.localizeSubmit || "Submit"; + const localize_check_responses = ww_container.dataset.localizeCheckResponses || "Check Responses"; + const localize_reveal = ww_container.dataset.localizeReveal || "Reveal"; + const localize_randomize = ww_container.dataset.localizeRandomize || "Randomize"; + const localize_reset = ww_container.dataset.localizeReset || "Reset"; + const runestone_logged_in = (typeof eBookConfig !== 'undefined' && eBookConfig.username !== ''); + // will be null on pages generated prior to late December 2022 + const activate_button = document.getElementById(ww_id + '-button') + + // Set the current seed + if (!action) { + ww_container.dataset.current_seed = ww_container.dataset.seed; + if (runestone_logged_in) { + ww_container.dataset.current_seed = webworkSeedHash(eBookConfig.username + ww_container.dataset.current_seed); + } + } + else if (action == 'randomize') ww_container.dataset.current_seed = Number(ww_container.dataset.current_seed) + 100; + + let loader = document.createElement('div'); + loader.style.position = 'absolute'; + loader.style.left = 0; + loader.style.top = 0; + loader.style.backgroundColor = 'rgba(0.2, 0.2, 0.2, 0.4)'; + loader.style.color = 'white'; + loader.style.width = '100%'; + loader.style.height = '100%'; + loader.style.display = 'flex'; + loader.style.alignItems = 'center'; + loader.style.justifyContent = 'center'; + loader.style.marginTop = '0'; + loader.tabIndex = -1; + const loaderText = document.createElement('span'); + loaderText.textContent = 'Loading'; + loaderText.style.fontSize = '2rem'; + loader.appendChild(loaderText); + ww_container.appendChild(loader); + loader.focus(); + + if (!action) { + // Determine if static version shows hints, solutions, or answers and save that information in the container dataset for later runs. + ww_container.dataset.hasHint = ww_container.getElementsByClassName('hint').length > 0; + ww_container.dataset.hasSolution = ww_container.getElementsByClassName('solution').length > 0; + ww_container.dataset.hasAnswer = ww_container.getElementsByClassName('answer').length > 0; + // Get (possibly localized) label text for hints and solutions. + ww_container.dataset.hintLabelText = ww_container.dataset.hasHint == 'true' + ? ww_container.querySelectorAll('.hint-knowl span.type, details.hint span.type')[0].textContent : 'Hint'; + ww_container.dataset.solutionLabelText = ww_container.dataset.hasSolution == 'true' + ? ww_container.querySelectorAll('.solution-knowl span.type, details.solution span.type')[0].textContent : 'Solution'; + + ww_container.tabIndex = -1; + } + + let url; + if (ww_processing == 'webwork2') { + url = new URL(ww_domain + '/webwork2/render_rpc'); + } + let formData = new FormData(); + let generatedPG = 'generated/webwork/pg/'; + // For Runestone, we specify where to find the generated problems. + if (runestone_logged_in) { + if (ww_problemSource && !ww_baseCourse) { + // WeBWorK authored in source, generated after 2025/10/15-ish, should have a baseCourse specified. We fall back to parsing the baseCourse from ww_id, which *should* always start with the base course prior to the first underscore (although this is not guaranteed). If no underscore is found, we fall back to eBookConfig.basecourse. + // This is only needed for webwork problems authored in source, not OPL problems, so we only check when ww_problemSource is set. + const parts = ww_id.split('_'); + ww_baseCourse = parts.length > 1 ? parts[0] : eBookConfig.basecourse; + console.log("using the base course " + ww_baseCourse); + } + generatedPG = `/ns/books/published/${ww_baseCourse}/${generatedPG}`; + } + + if (action == 'check' || action =='reveal') { + const iframe = ww_container.querySelector('.problem-iframe'); + formData = new FormData(iframe.contentDocument.getElementById(ww_id + "-form")); + formData.set("answersSubmitted", '1'); + formData.set('WWsubmit', "1"); + if (action == 'reveal' && ww_container.dataset.hasAnswer == 'true') { + formData.set('WWcorrectAnsOnly', "1"); + } + if (ww_origin == 'generated') { + const rawProblemSource = await fetch(generatedPG + ww_problemSource).then((r) => r.text()); + formData.set("rawProblemSource", rawProblemSource); + } + else if (ww_origin == 'webwork2') formData.set("sourceFilePath", ww_sourceFilePath); + } else { + formData.set("problemSeed", ww_container.dataset.current_seed); + if (ww_origin == 'generated') { + const rawProblemSource = await fetch(generatedPG + ww_problemSource).then((r) => r.text()); + formData.set("rawProblemSource", rawProblemSource); + } + else if (ww_origin == 'webwork2') formData.set("sourceFilePath", ww_sourceFilePath); + formData.set("answersSubmitted", '0'); + formData.set("displayMode", "MathJax"); + formData.set("courseID", ww_course_id); + formData.set("user", ww_user_id); + formData.set("userID", ww_user_id); + formData.set("passwd", ww_passwd); + formData.set("disableCookies", '1'); + formData.set("outputformat", "raw"); + // note ww_container.dataset.hasSolution is a string, possibly 'false' which is true + formData.set("showSolutions", ww_container.dataset.hasSolution == 'true' ? '1' : '0'); + formData.set("showHints", ww_container.dataset.hasHint == 'true' ? '1' : '0'); + formData.set("problemUUID",ww_id); + } + + // If in Runestone, check if there are previous answer submissions and get them now to use in the form. + let checkboxesString = ''; + if (runestone_logged_in && !action) { + const answersObject = (wwList[ww_id.replace(/-ww-rs$/,'')].answers ? wwList[ww_id.replace(/-ww-rs$/,'')].answers : {'answers' : [], 'mqAnswers' : []}); + const previousAnswers = answersObject.answers; + if (previousAnswers !== null) { + formData.set('WWsubmit', 1); + } + for (const answer in previousAnswers) { + if (previousAnswers[answer].constructor === Array) { + for (const k in previousAnswers[answer]) { + checkboxesString += '&'; + checkboxesString += answer; + checkboxesString += '='; + checkboxesString += previousAnswers[answer][k]; + } + } else { + formData.set(answer, previousAnswers[answer]); + } + } + } + // Need to get form data as a string, including possible repeated checkbox names + // Do not pass post data as an object, or checkbox names will overwrite one another + const formString = new URLSearchParams(formData).toString(); + + $.post(url, formString + checkboxesString, (data) => { + // Create the form that will contain the text and input fields of the interactive problem. + const form = document.createElement("form"); + form.id = ww_id + "-form"; + form.dataset.iframeHeight = 1; + + // Create a div for the problem text. + const body_div = document.createElement("div"); + body_div.id = ww_id + "-body"; + body_div.classList.add("exercise", "exercise-like"); + body_div.lang = data.lang; + body_div.dir = data.dir; + + // Dump the problem text, answer blanks, etc. + body_div.innerHTML = data.rh_result.text; + + // If showPartialCorrectAnswers = 0, alter the feedback buttons according to whether or not the score is 100%. + if ('showPartialCorrectAnswers' in data.rh_result.flags && data.rh_result.flags.showPartialCorrectAnswers == 0) { + if ('score' in data.rh_result.problem_result && data.rh_result.problem_result.score >= 1) { + body_div.querySelectorAll('button.ww-feedback-btn').forEach( + function(button) { + button.classList.remove('btn-info'); + button.classList.add('btn-success'); + button.setAttribute('aria-label', 'Correct'); + button.dataset.bsCustomClass = button.dataset.bsCustomClass + ' correct'; + button.dataset.bsTitle = button.dataset.bsTitle.replace('Answer Preview', 'Correct'); + button.firstChild.classList.add('correct') + } + ); + } else { + body_div.querySelectorAll('button.ww-feedback-btn').forEach( + function(button) { + button.setAttribute('aria-label', 'One or more answers incorrect'); + button.dataset.bsTitle = button.dataset.bsTitle.replace('Answer Preview', 'One or more answers incorrect'); + } + ); + } + } + + // Replace all hn headings with h6 headings. + for (const tag_name of ['h6', 'h5', 'h4', 'h3', 'h2', 'h1']) { + const headings = body_div.getElementsByTagName(tag_name); + for (heading of headings) { + const new_heading = document.createElement("h6"); + new_heading.innerHTML = heading.innerHTML; + cloneAttributes(new_heading, heading); + new_heading.classList.add('webwork-part'); + heading.replaceWith(new_heading); + } + } + + // Hide textarea input and hide associated buttons + var textareas = body_div.getElementsByTagName("textarea"); + for(var i = 0, max = textareas.length; i < max; i++) + { + textareas[i].style.display = "none"; + textareas[i].className = ''; + } + var textareabuttons = body_div.querySelectorAll(".latexentry-preview"); + for(var i = 0, max = textareabuttons.length; i < max; i++) + { + textareabuttons[i].remove(); + } + + adjustSrcHrefs(body_div, ww_domain); + + translateHintSol(ww_id, body_div, ww_domain, + ww_container.dataset.hasHint == 'true', ww_container.dataset.hasSolution == 'true', + ww_container.dataset.hintLabelText, ww_container.dataset.solutionLabelText) + + // insert previous answers + if (runestone_logged_in) { + const answersObject = (wwList[ww_id.replace(/-ww-rs$/,'')].answers ? wwList[ww_id.replace(/-ww-rs$/,'')].answers : {'answers' : [], 'mqAnswers' : []}); + const mqAnswers = answersObject.mqAnswers; + for (const mqAnswer in mqAnswers) { + const mqInput = body_div.querySelector('input[id=' + mqAnswer + ']'); + if (mqInput && mqInput.value == '') { + mqInput.setAttribute('value', mqAnswers[mqAnswer]); + } + } + const answers = answersObject.answers; + for (const answer in answers) { + const input = body_div.querySelector('input[id=' + answer + ']'); + if (input && input.value == '') { + input.setAttribute('value', answers[answer]); + } + } + } + + // insert our cleaned up problem text + form.appendChild(body_div); + + // Set up hidden input fields that the form uses + const wwInputs = { + problemSeed: data.inputs_ref.problemSeed, + problemUUID: data.inputs_ref.problemUUID, + psvn: data.inputs_ref.psvn, + courseName: ww_course_id, + courseID: ww_course_id, + user: ww_user_id, + passwd: ww_passwd, + displayMode: "MathJax", + session_key: data.rh_result.session_key, + outputformat: "raw", + language: data.formLanguage, + showSummary: data.showSummary, + disableCookies: '1', + // note ww_container.dataset.hasSolution is a string, possibly 'false' which is true + showSolutions: ww_container.dataset.hasSolution == 'true' ? '1' : '0', + showHints: ww_container.dataset.hasHint == 'true' ? '1' : '0', + forcePortNumber: data.forcePortNumber + }; + + if (ww_sourceFilePath) wwInputs.sourceFilePath = ww_sourceFilePath; + else if (ww_problemSource && ww_origin == 'webwork2') wwInputs.problemSource = ww_problemSource; + + for (const wwInputName of Object.keys(wwInputs)) { + const input = document.createElement('input'); + input.type = 'hidden'; + input.name = wwInputName; + input.value = wwInputs[wwInputName]; + form.appendChild(input); + } + + let buttonContainer = ww_container.querySelector('.problem-buttons.webwork'); + // Create the submission buttons if they have not yet been created. + if (!buttonContainer) { + // Hide the original div that contains the Activate. + ww_container.querySelector('.problem-buttons').classList.add('hidden-content', 'hidden'); + + // Create a new div for the webwork buttons. + buttonContainer = document.createElement('div'); + buttonContainer.classList.add('problem-buttons', 'webwork'); + if (activate_button != null) { + // Make sure the button container follows the activate button in the DOM + activate_button.after(buttonContainer); + } else { + ww_container.prepend(buttonContainer); + } + + // Check button + const check = document.createElement("button"); + check.type = "button"; + check.id = ww_id + '-check'; + check.style.marginRight = "0.25rem"; + check.classList.add('webwork-button'); + + check.textContent = runestone_logged_in ? localize_submit : localize_check_responses; + + check.addEventListener('click', () => handleWW(ww_id, "check")); + + buttonContainer.appendChild(check); + + // Show correct answers button if original PTX has answer knowl. + if (ww_container.dataset.hasAnswer == 'true') { + const correct = document.createElement("button"); + correct.classList.add("show-correct", 'webwork-button'); + correct.type = "button"; + correct.style.marginRight = "0.25rem"; + correct.textContent = localize_reveal; + correct.addEventListener('click', () => handleWW(ww_id, 'reveal')); + buttonContainer.appendChild(correct); + } + + // randomize button + const randomize = document.createElement("button") + randomize.type = "button"; + randomize.classList.add('webwork-button'); + randomize.style.marginRight = "0.25rem"; + randomize.textContent = localize_randomize; + randomize.addEventListener('click', () => handleWW(ww_id, 'randomize')); + buttonContainer.appendChild(randomize) + + // reset button + const reset = document.createElement("button") + reset.type = "button" + reset.classList.add('webwork-button'); + reset.textContent = localize_reset; + reset.addEventListener('click', () => resetWW(ww_id)); + buttonContainer.appendChild(reset) + } + + if (runestone_logged_in && action == 'check') { + // Runestone trigger + $("body").trigger('runestone_ww_check', data) + } + let courseUrlBase = ''; + if (runestone_logged_in){ + courseUrlBase = '/ns/books/published/' + eBookConfig.basecourse + '/'; + } + let iframeContents = '' + + '' + + `` + + '' + + `` + + '' + + ''; + + // Determine javascript and css dependencies + const extra_css_files = []; + const extra_js_files = []; + + if (data.extra_css_files) data.extra_css_files.unshift(...extra_css_files); + else data.extra_css_files = extra_css_files; + for (const cssFile of data.extra_css_files) { + iframeContents += ''; + } + + if (data.extra_js_files) data.extra_js_files.unshift(...extra_js_files); + else data.extra_js_files = extra_js_files; + for (const jsFile of data.extra_js_files) { + iframeContents += ''; + } + + iframeContents += + `` + + '' + + `` + + '' + + '
    ' + form.outerHTML + '
    ' + + ''; + + let iframe; + // If there is no action this is the initialization call. + if (!action) { + // Create the iframe. + iframe = document.createElement('iframe'); + iframe.style.width = '1px'; + iframe.style.minWidth = '100%'; + iframe.classList.add('problem-iframe'); + + // Hide the static problem + ww_container.querySelector('.problem-contents').classList.add('hidden-content', 'hidden'); + + if (activate_button != null) { + // Make sure the iframe follows the activate button in the DOM + activate_button.after(iframe); + } else { + ww_container.prepend(iframe); + } + + iFrameResize({ checkOrigin: false, scrolling: 'omit', heightCalculationMethod: 'taggedElement' }, iframe); + + iframe.addEventListener('load', () => { + // Set up form submission from inside the iframe. + const iframeForm = iframe.contentDocument.getElementById(ww_id + '-form'); + iframeForm.addEventListener('submit', (e) => { + handleWW(ww_id, "check"); + e.preventDefault(); + }); + + iframe.contentDocument.querySelectorAll('.collapse.in').forEach(collapse => collapse.classList.add('expanded')); + iframe.contentWindow.jQuery('.collapse').on('shown', function(e) { if (e.target != this) return; this.classList.add('expanded'); }); + iframe.contentWindow.jQuery('.collapse').on('hide', function(e) { if (e.target != this) return; this.classList.remove('expanded'); }); + + iframe.contentWindow.MathJax.startup.promise.then(() => iframe.contentWindow.MathJax.typesetPromise(['.popover', '.popover-content'])); + }); + } else { + iframe = ww_container.querySelector('.problem-iframe'); + } + + iframe.srcdoc = iframeContents; + + iframe.addEventListener('load', () => { + // Remove the loader overlay + loader.remove(); + }, { once: true }) + + // Place focus on the problem. + ww_container.focus() + }, "json"); +} + +function WWshowCorrect(ww_id, answers) { + const ww_container = document.getElementById(ww_id); + const iframe = ww_container.querySelector('.problem-iframe'); + + const body = iframe.contentDocument.getElementById(ww_id + '-body') + $("body").trigger("runestone_show_correct", { + "ww_id": ww_id, + "answers": answers + }); + + let inputs = body.querySelectorAll("input:not([type=hidden])"); + for (const input of inputs) { + const name = input.name; + const span_id = `${ww_id}-${name}-correct`; + + if (input.type == 'text' && answers[name] && !(iframe.contentDocument.getElementById(span_id))) { + const input_id = input.id; + const mq_span = iframe.contentDocument.getElementById(`mq-answer-${input_id}`); + if (mq_span) { + mq_span.style.display = 'none'; + } + label = iframe.contentDocument.getElementById(`${span_id}-label`); + if (label) { + label.parentElement.insertBefore(input, label); + label.remove(); + } + input.type = "hidden"; + // we need to convert things like < in answers to < + const correct_ans_text = iframe.contentDocument.createElement('div'); + correct_ans_text.innerHTML = answers[name].correct_ans; + input.value = correct_ans_text.textContent; + const show_span = iframe.contentDocument.createElement('span'); + show_span.id = span_id; + show_span.appendChild(answers[name].correct_ans_latex_string + ? iframe.contentDocument.createTextNode('\\(' + answers[name].correct_ans_latex_string + '\\)') + : iframe.contentDocument.createTextNode(answers[name].correct_ans)); + input.parentElement.insertBefore(show_span, input); + } + + if (input.type.toUpperCase() == 'RADIO' && answers[name]) { + const correct_ans = answers[name].correct_choice || answers[name].correct_ans; + if (input.value == correct_ans) { + input.checked = true; + } else { + input.checked = false; + } + } + + if (input.type.toUpperCase() == 'CHECKBOX' && answers[name]) { + const correct_choices = answers[name].correct_choices; + if (correct_choices.includes(input.value)) { + input.checked = true; + } else { + input.checked = false; + } + } + } + + const hiddenInputs = body.querySelectorAll("input[type=hidden]"); + for (const input of hiddenInputs) { + const name = input.name; + if (!input.nextElementSibling) continue; + const graphtoolContainer = input.nextElementSibling.nextElementSibling; + if (graphtoolContainer && answers[name] && graphtoolContainer.classList.contains('graphtool-container')) { + const correct_ans_div = iframe.contentDocument.createElement('div'); + input.parentElement.insertBefore(correct_ans_div, graphtoolContainer); + graphtoolContainer.style.display = 'none'; + input.value = answers[name].correct_ans; + iframe.contentWindow.jQuery(correct_ans_div).html(answers[name].correct_ans_latex_string); + const script = iframe.contentDocument.createElement('script'); + script.textContent = correct_ans_div.querySelector('script').textContent + .replace('\nwindow.addEventListener("DOMContentLoaded",', '(') + .replace(/;\n$/, '();'); + iframe.contentDocument.body.appendChild(script); + } + } + + let selects = body.querySelectorAll("select:not([type=hidden])"); + for (const select of selects) { + const name = select.name; + const span_id = `${ww_id}-${name}-correct`; + if (answers[name] && !iframe.contentDocument.getElementById(span_id)) { + select.style.display = "none"; + select.value = answers[name].correct_ans; + const show_span = iframe.contentDocument.createElement('span'); + show_span.id = span_id; + show_span.appendChild(answers[name].correct_ans_latex_string + ? iframe.contentDocument.createTextNode('\\(' + answers[name].correct_ans_latex_string + '\\)') + : iframe.contentDocument.createTextNode(answers[name].correct_ans)); + select.parentElement.insertBefore(show_span, select); + } + } + + // run MathJax on our new rendering + // FIXME: We only need to typeset the added elements, not the entire body. + const mathjaxTypesetScript = iframe.contentDocument.createElement('script'); + mathjaxTypesetScript.textContent = 'MathJax.startup.promise.then(() => MathJax.typesetPromise([document.body]));'; + iframe.contentDocument.body.appendChild(mathjaxTypesetScript); +} + +function resetWW(ww_id) { + const ww_container = document.getElementById(ww_id); + const activate_button = document.getElementById(ww_id + '-button'); + + ww_container.dataset.current_seed = ww_container.dataset.seed; + + iframe = ww_container.querySelector('.problem-iframe'); + iframe.remove(); + + ww_container.querySelector('.problem-contents').classList.remove('hidden-content', 'hidden'); + + ww_container.querySelector('.problem-buttons.webwork').remove(); + ww_container.querySelector('.problem-buttons').classList.remove('hidden-content', 'hidden'); + // if the newer activate button is there (but hidden) bring it back too + if (activate_button != null) {activate_button.classList.remove('hidden-content', 'hidden');}; +} + +function adjustSrcHrefs(container,ww_domain) { + container.querySelectorAll('[href]').forEach((node) => { + const href = node.attributes.href.value; + if (href !== '#' && !href.match(/^[a-z]+:\/\//i)) node.href = ww_domain + '/' + href; + }); + container.querySelectorAll('[data-knowl-url]').forEach((node) => { + const dku = node.dataset.knowlUrl; + if (dku !== '#' && !dku.match(/^[a-z]+:\/\//i)) node.dataset.knowlUrl = ww_domain + dku; + }); + container.querySelectorAll('[src]').forEach((node) => { + node.src = new URL(node.attributes.src.value, ww_domain).href; + }); +} + +function translateHintSol(ww_id, body_div, ww_domain, b_ptx_has_hint, b_ptx_has_solution, hint_label_text, solution_label_text) { + // The problem text may come with "hint"s and "solution"s + // Each one is a div.accordion > details.accordion-item > summary.accordion-button + // Styling is not the PTX way, so we change it to one div.solutions + // with (potentially multiple) details.born-hidden-knowl > summary.knowl__link + // Also if hint/sol were missing from the static version, we want these removed here + + const hintSols = body_div.querySelectorAll('.accordion.hint,.accordion.solution'); + if (hintSols.length == 0) {return}; + + for (const hintSol of hintSols) { + const parent = hintSol.parentNode; + solutionlikewrapper = document.createElement('div'); + solutionlikewrapper.classList.add('solutions'); + parent.insertBefore(solutionlikewrapper, hintSol); + + const hintSolType = hintSol.classList.contains('hint') ? 'hint' : 'solution'; + + if ((hintSolType == 'solution' && !b_ptx_has_solution) || + (hintSolType == 'hint' && !b_ptx_has_hint)) + continue; + + const knowlDetails = hintSol.getElementsByTagName('details')[0]; + knowlDetails.className = ''; + knowlDetails.classList.add(hintSolType, 'solution-like', 'born-hidden-knowl'); + solutionlikewrapper.appendChild(knowlDetails); + + const knowlSummary = knowlDetails.getElementsByTagName('summary')[0]; + knowlSummary.className = ''; + knowlSummary.classList.add('knowl__link'); + + const summaryLabel = knowlSummary.children[0]; + summaryLabel.remove(); + + const newLabelSpan = document.createElement('span'); + newLabelSpan.innerHTML = hintSolType == 'hint' ? hint_label_text : solution_label_text; + knowlSummary.appendChild(newLabelSpan); + + const newLabelPeriod = document.createElement('span'); + newLabelPeriod.innerHTML = '.'; + knowlSummary.appendChild(newLabelPeriod); + + adjustSrcHrefs(knowlDetails, ww_domain); + hintSol.remove(); + + const originalDetailsContent = knowlDetails.getElementsByTagName('div')[0]; + const newDetailsContent = originalDetailsContent.getElementsByTagName('div')[0]; + newDetailsContent.className = ''; + newDetailsContent.classList.add(hintSolType, 'solution-like', 'knowl__content'); + knowlDetails.appendChild(newDetailsContent); + originalDetailsContent.remove(); + } +} + +function cloneAttributes(target, source) { + [...source.attributes].forEach( attr => { target.setAttribute(attr.nodeName ,attr.nodeValue) }); +} + +function webworkSeedHash(string) { + var hash = 0, i, chr; + if (string.length === 0) return hash; + for (i = 0; i < string.length; i++) { + chr = string.charCodeAt(i); + hash = ((hash << 5) - hash) + chr; + hash |= 0; //Convert to 32bit integer + } + return Math.abs(hash); +}; diff --git a/slides/_static/pretext/js/pretext.js b/slides/_static/pretext/js/pretext.js new file mode 100755 index 0000000..bcddf7e --- /dev/null +++ b/slides/_static/pretext/js/pretext.js @@ -0,0 +1,257 @@ +/******************************************************************************* + * pretext.js + ******************************************************************************* + * The main front-end controller for PreTeXt documents. + * + * Homepage: pretextbook.org + * Repository: https://github.com/PreTeXtBook/JS_core + * + * Authors: David Farmer, Rob Beezer + * + ******************************************************************************* + */ + +// from https://stackoverflow.com/questions/34422189/get-item-offset-from-the-top-of-page +function getOffsetTop(e) { + // recursively walk up the DOM via offsetParent, accumulating offsetTop as we go + if (!e) return 0; + return getOffsetTop(e.offsetParent) + e.offsetTop; +}; + +function scrollTocToActive() { + //Try to figure out current TocItem from URL + let fileNameWHash = window.location.href.split("/").pop(); + let fileName = fileNameWHash.split("#")[0]; + + //Find just the filename in ToC + let tocEntry = document.querySelector('#ptx-toc a[href="' + fileName + '"]'); + if (!tocEntry) { + return; //complete failure, get out + } + + let tocEntryTop = 0; + //See if we can also match fileName#hash (assuming there is a fragment) + if (fileNameWHash.includes('#')) { + let tocEntryWHash = document.querySelector( + '#ptx-toc a[href="' + fileNameWHash + '"]' + ); + if (tocEntryWHash) { + //Matched something below a subsection - activate the list item that contains it + tocEntry.closest("li").querySelectorAll("li").forEach(li => { + li.classList.remove("active"); + }); + tocEntryWHash.closest("li").classList.add("active"); + tocEntryTop = getOffsetTop(tocEntryWHash); + } + } + if (!tocEntryTop) { + tocEntryTop = getOffsetTop(tocEntry); + } + + //Now activate ToC item for fileName and scroll to it + // Don't use scrollIntoView because it changes users tab position in Chrome + // and messes up keyboard navigation + tocEntry.closest("li").classList.add("active"); + // Scroll only if the tocEntry is below the bottom half of the window, + // scrolling to that position. + let toc = document.querySelector("#ptx-toc"); + let tocTop = getOffsetTop(toc); + toc.scrollTop = tocEntryTop - tocTop - 0.4 * self.innerHeight; +} + +function toggletoc() { + thesidebar = document.getElementById("ptx-sidebar"); + if (thesidebar.classList.contains("hidden") || thesidebar.classList.contains("visible")) { + thesidebar.classList.toggle("hidden"); + thesidebar.classList.toggle("visible"); + } else if (thesidebar.offsetParent === null) { /* not currently visible */ + thesidebar.classList.toggle("visible"); + } else { + thesidebar.classList.toggle("hidden"); + } + scrollTocToActive(); +} + +function samePageLink(a) { + if (!(a instanceof HTMLAnchorElement)) return false; + + try { + const linkUrl = new URL(a.href, document.baseURI); + const currentUrl = new URL(window.location.href); + + const sameDocument = + linkUrl.origin === currentUrl.origin && + linkUrl.pathname === currentUrl.pathname && + linkUrl.search === currentUrl.search; + + return sameDocument && !!linkUrl.hash; + } catch (e) { + // Invalid URL + return false; + } +} + + +window.addEventListener("DOMContentLoaded",function(event) { + thetocbutton = document.getElementsByClassName("toc-toggle")[0]; + thetocbutton.addEventListener("click", (e) => { + toggletoc(); + e.stopPropagation(); // keep global click handler from immediately toggling it back + }); + + // For themes that want it, install click handlers to auto close the toc + // when the reader clicks anywhere outside it or selects a subsection. + // (Selecting other sections or chapters navigates away from the page so + // effectively closes the TOC.) + if (getComputedStyle(document.documentElement).getPropertyValue('--auto-collapse-toc') == "yes") { + + const sidebar = document.getElementById("ptx-sidebar"); + + // Handle all clicks outside the sidebar + window.addEventListener("click", function(event) { + if (sidebar.classList.contains("visible")) { + if (!event.composedPath().includes(sidebar)) { + toggletoc(); + } + } + }); + + // Handle clicks inside the sidebar but on link within a subsection. + sidebar.addEventListener("click", function (event) { + if (samePageLink(event.target.closest('a'))) { + toggletoc(); + } + }); + + // Handle persistent sidebar if the page is restored from cache on back/forward buttons. + window.addEventListener('pageshow', (e) => { + if (e.persisted) { + sidebar.classList.remove('visible'); + sidebar.classList.add('hidden'); + } + }); + } +}); + +/* jump to next page if reader tries to scroll past the bottom */ +// var hitbottom = false; +// window.onscroll = function(ev) { +// if ((window.innerHeight + window.scrollY) >= document.body.scrollHeight) { +// // you're at the bottom of the page +// console.log("Bottom of page"); +// if (hitbottom) { +// console.log("hit bottom again"); +// thenextbutton = document.getElementsByClassName("next-button")[0]; +// thenextbutton.click(); +// } else { +// hitbottom = true; +// /* only jump to next page if hard scroll in quick succession */ +// window.scrollBy(0, -20); +// setTimeout(function (){ hitbottom = false }, 1000); +// } +// } +// }; + + +//----------------------------------------------------------------------------- +// Dynamic TOC logic +//----------------------------------------------------------------------------- + +//item is assumed to be expander in toc-item +function toggleTOCItem(expander) { + let listItem = expander.closest(".toc-item"); + listItem.classList.toggle("expanded"); + let expanded = listItem.classList.contains("expanded"); + + let itemType = getTOCItemType(listItem); + if(expanded) { + expander.title = "Close" + (itemType !== "" ? " " + itemType : ""); + } else { + expander.title = "Expand" + (itemType !== "" ? " " + itemType : ""); + } + + //should be one of each... for/of for safety and built in null avoidance + for (const childUL of listItem.querySelectorAll(":scope > ul.toc-item-list")) { + for (const childItem of childUL.querySelectorAll(":scope > li.toc-item")) { + if(expanded) { + childItem.classList.add("visible"); + childItem.classList.remove("hidden"); + } else { + childItem.classList.remove("visible"); + childItem.classList.add("hidden"); + } + } + } +} + +//finds item type from classes or empty string on failure +function getTOCItemType(item) { + //Type should be class that looks like toc-X where X is not item. Find it and return X + for(let className of item.classList) { + if(className !== "toc-item" && className.length > 3 && className.slice(0,4) === "toc-") + return className.slice(4); + } + return ""; +} + +//finds depth of toc-item as defined by number .toc-item-lists it is in +function getTOCItemDepth(item) { + let depth = 0; + let curParent = item.closest(".toc-item-list"); + while(curParent !== null) { + depth++; + curParent = curParent.parentElement.closest(".toc-item-list"); + } + return depth; +} + +window.addEventListener("DOMContentLoaded", function(event) { + if(document.querySelector(".ptx-toc.focused") === null) + return; //only in focused mode + + let maxDepth = 1000; //how deep TOC goes + //check toc for depth class and get value from there + for(let className of document.querySelector(".ptx-toc").classList) + if(className.length > 5 && className.slice(0,5) === "depth") + maxDepth = Number(className.slice(5)); + + let preexpandedLevels = 1; //how many levels to preexpand + let tocDataSet = document.querySelector(".ptx-toc").dataset; + if(typeof tocDataSet.preexpandedLevels !== 'undefined') + preexpandedLevels = Number(tocDataSet.preexpandedLevels); + + let tocItems = document.querySelectorAll(".ptx-toc ul.structural > .toc-item"); + for (const tocItem of tocItems) { + let hasChildren = tocItem.querySelector('ul.structural') !== null; + let depth = getTOCItemDepth(tocItem); + + if(hasChildren && depth < maxDepth) { + let expander = document.createElement("button"); + expander.classList.add('toc-expander'); + expander.classList.add('toc-chevron-surround'); + expander.title = 'toc-expander'; + // content of span is set by CSS :before rule. + expander.innerHTML = ''; + tocItem.querySelector(".toc-title-box").append(expander); + expander.addEventListener('click', () => { + toggleTOCItem(expander); + }); + + let isActive = tocItem.classList.contains("contains-active") || tocItem.classList.contains("active"); + let preExpanded = isActive || depth < preexpandedLevels; + let itemType = getTOCItemType(tocItem); + if(preExpanded) { + toggleTOCItem(expander); + } else { + expander.title = "Expand" + (itemType !== "" ? " " + itemType : ""); + } + } + } +}); + +// This needs to be after the TOC's geometry is settled +window.addEventListener("DOMContentLoaded",function(event) { + scrollTocToActive(); +}); + +window.onhashchange = scrollTocToActive; diff --git a/slides/_static/pretext/js/pretext_add_on.js b/slides/_static/pretext/js/pretext_add_on.js new file mode 100755 index 0000000..ae2225f --- /dev/null +++ b/slides/_static/pretext/js/pretext_add_on.js @@ -0,0 +1,1264 @@ +/******************************************************************************* + * pretext_add_on.js + ******************************************************************************* + * Javascript for supplementary material in PreTeXt documents. + * + * Homepage: pretextbook.org + * Repository: https://github.com/PreTeXtBook/JS_core + * + * Authors: David Farmer, Rob Beezer, Alex Jordan + * + ******************************************************************************* + */ + +/* +console.log("thisbrowser.userAgent", window.navigator.userAgent); +*/ + +var minivers = "0"; +if (typeof miniversion !== 'undefined') { + console.log("typeof miniversion", typeof miniversion, "dddd", typeof miniversion == 'undefined'); + minivers = miniversion.toString(); +} +console.log(" minivers", minivers); + +/* scrollbar width from https://stackoverflow.com/questions/13382516/getting-scroll-bar-width-using-javascript */ +function getScrollbarWidth() { + var outer = document.createElement("div"); + outer.style.visibility = "hidden"; + outer.style.width = "100px"; + outer.style.msOverflowStyle = "scrollbar"; // needed for WinJS apps + + document.body.appendChild(outer); + + var widthNoScroll = outer.offsetWidth; + // force scrollbars + outer.style.overflow = "scroll"; + + // add innerdiv + var inner = document.createElement("div"); + inner.style.width = "100%"; + outer.appendChild(inner); + + var widthWithScroll = inner.offsetWidth; + + // remove divs + outer.parentNode.removeChild(outer); + + return widthNoScroll - widthWithScroll; +} + +/* + copy permalink address to clipboard + requires browser support, otherwise does nothing +*/ +async function copyPermalink(linkNode) { + // structure borrowed from https://flaviocopes.com/clipboard-api/ + if (!navigator.clipboard) { + // Clipboard API not available + console.log("Error: Clipboard API not available"); + return + } + console.log("copying permalink for", linkNode); + var elem = linkNode.parentElement + if (!linkNode) { + console.log("Error: Something went wrong finding permalink URL") + return + } + const this_permalink_url = linkNode.href; + const this_permalink_description = elem.getAttribute('data-description'); + var link = "" + this_permalink_description + ""; + var msg_link = "" + this_permalink_description + ""; + var text_fallback = this_permalink_description + " \r\n" + this_permalink_url; + var copy_success = true; + try { + // NOTE: this method will only work in Firefox if the user has + // dom.events.asyncClipboard.clipboardItem + // set to true in their about:config. + // Annoyingly, this setting is turned off by default. + // If that setting is off, this try block will fail and we'll use the + // fallback method lower down instead. + await navigator.clipboard.write([ + new ClipboardItem({ + 'text/html': new Blob([link], { type: 'text/html' }), + 'text/plain': new Blob([text_fallback], { type: 'text/plain' }), + }) + ]); + } catch (err) { + console.log('Permalink-to-clipboard using ClipboardItem failed, falling back to clipboard.writeText', err); + copy_success = false; + } + if (! copy_success) { + try { + await navigator.clipboard.writeText(text_fallback); + } catch (err) { + console.log('Permalink-to-clipboard using clipboard.writeText failed', err); + console.error('Failed to copy link to clipboard!'); + return + } + } + + console.log(`copied '${this_permalink_url}' to clipboard`); + // temporary element to alert user that link was copied + let copied_msg = document.createElement('p'); + copied_msg.setAttribute('role', 'alert'); + copied_msg.className = "permalink-alert"; + copied_msg.innerHTML = "Link to " + msg_link + " copied to clipboard"; + elem.parentElement.insertBefore(copied_msg, elem); + // show confirmation for a couple seconds + await new Promise((resolve, reject) => setTimeout(resolve, 1500)); + copied_msg.remove(); + +} + +// Add event listener to add onClick handler for permalinks +window.addEventListener("DOMContentLoaded", function() { + const permalinks = document.querySelectorAll('.autopermalink > a'); + permalinks.forEach(link => { + link.addEventListener('click', function(event) { + event.preventDefault(); // Prevent default anchor behavior + copyPermalink(link); + }); + }); +}); + + +window.addEventListener("load",function(event) { + + + /* click an image to magnify */ + $('body').on('click','.image-box > img:not(.draw_on_me):not(.mag_popup), .sbspanel > img:not(.draw_on_me):not(.mag_popup), figure > img:not(.draw_on_me):not(.mag_popup), figure > div > img:not(.draw_on_me):not(.mag_popup)', function(){ + var img_big = document.createElement('div'); + img_big.setAttribute('style', 'background:#fff;'); + img_big.setAttribute('class', 'mag_popup_container'); + img_big.innerHTML = ''; + // place_to_put_big_img = $(this).parents(".sbsrow, figure, li").last(); + place_to_put_big_img = $(this).parents(".image-box, .sbsrow, figure, li, .cols2 article:nth-of-type(2n)").last(); + // for .cols2, the even ones have to go inside the previous odd one + if (place_to_put_big_img.prop("tagName") == "ARTICLE") { + place_to_put_big_img = place_to_put_big_img.prev().children().first(); + } + $(img_big).insertBefore(place_to_put_big_img); + }); + + /* click the big image to make it go away */ + $('body').on('click','img.mag_popup', function(){ + this.parentNode.remove(); + }); + + /* add ids to p that have none */ + p_no_id = document.querySelectorAll('.main p:not([id])'); + for (var n=p_no_id.length - 1; n >= 0; --n) { + e = p_no_id[n]; + if (e.hasAttribute('id')) { +/* + console.log(e, "was id'd in a previous round"); +*/ + continue + } +/* +console.log("this is e", e); +*/ + if (e.classList.contains('watermark')) { + console.log(e, "skipping the watermark"); + continue + } +/* + console.log("\n XXXXXXXXX p with no id", e); +*/ + prev_p = $(e).prevAll("p"); + console.log("prev_p", prev_p, "xx"); + if(prev_p.length == 0) { + console.log(" PPP problem: prev_p has no length:", prev_p); + continue + } + console.log("which has id", prev_p[0].id); + var parts_found = 1; + var parts_to_id = [e]; + for (var i=0; i < prev_p.length; ++i) { + this_previous = prev_p[i]; + console.log("i", i, "this_previous", this_previous, "id", this_previous.id, "???", this_previous.hasAttribute('id')) + if (!this_previous.hasAttribute('id')) { + parts_to_id.unshift(this_previous) + } + else { + base_id = this_previous.id; + console.log("base_id", base_id); + console.log("ready to add id to", parts_to_id); + for (var j=0; j < parts_to_id.length; ++j) { + ++parts_found; + var next_id = base_id + "-part" + parts_found.toString(); + console.log("parts_found", parts_found, "next_id", next_id); + parts_to_id[j].setAttribute("id", next_id); + } + break // because we found the id that is the base for the missing ids + } + } + } + + console.log("adding video popouts"); + all_iframes = document.querySelectorAll('body iframeXXXX'); + // for now, we just want the iframes that hace youtube in the src + for (var i = 0; i < all_iframes.length; i++) { + this_item = all_iframes[i]; + this_item_src = this_item.src; + // console.log("this_item_src", this_item_src); + if(this_item_src.includes("youtube")) { + this_item_id = this_item.id; + this_item_width = this_item.width; + this_item_height = this_item.height; + if(this_item_height < 150) { continue } + console.log("found a youtube video on", this_item_id); + var empty_div = document.createElement('div'); + var this_videomag_container = document.createElement('div'); + parent_tag = this_item.parentElement.tagName; + if(parent_tag == "FIGURE") { + this_videomag_container.setAttribute("class", "videobig"); + } else { + this_videomag_container.setAttribute("class", "videobig nofigure"); + } +/* + this_videomag_container.setAttribute('class', 'videobig'); +*/ + this_videomag_container.setAttribute('video-id', this_item_id); + this_videomag_container.setAttribute('data-width', this_item_width); + this_videomag_container.setAttribute('data-height', this_item_height); + this_videomag_container.innerHTML = 'fit width'; + +/* replace this with a surrounding div, for placement, containing a inline-block so the background looks right */ + this_item.insertAdjacentElement("beforebegin", empty_div); // because of hard-coded permalinks being inline-block */ + this_item.insertAdjacentElement("beforebegin", this_videomag_container); + this_item.insertAdjacentElement("beforebegin", empty_div); // because of hard-coded permalinks being inline-block */ + } + } + +/* replace this with a single class fo rthe button, with supplementary classes that say to shrink or grow */ + $(".videobig").click(function(){ + parent_video_id = this.getAttribute("video-id"); + console.log("clicked videobig for", parent_video_id); + this_video = document.getElementById(parent_video_id); + console.log("make big: ", this_video); + original_width = this.getAttribute("data-width"); + original_height = this.getAttribute("data-height"); + + browser_width = $(window).width(); + width_ratio = browser_width/original_width; + console.log("the browser is wider by a factor of",width_ratio); + this_video.setAttribute("width", width_ratio*original_width); + this_video.setAttribute("height", width_ratio*original_height); + this_video.setAttribute("style", "position:relative; left:-260px; z-index:1000"); + + this.setAttribute("class", "videosmall"); + this.innerHTML = "make small"; + $(".videosmall").click(function(){ + console.log("clicked videosmall"); + parent_video_id = this.getAttribute("video-id"); + this_video = document.getElementById(parent_video_id); + original_width = this.getAttribute("data-width"); + original_height = this.getAttribute("data-height"); + + this_video.removeAttribute("style"); + this_video.setAttribute("width", original_width); + this_video.setAttribute("height", original_height); + this.setAttribute("class", "videobig"); + this.innerHTML = "fit width"; + }); + }); + +}, +false); + +/* for the random WW problems */ + +function updateURLParameter(url, param, paramVal){ + var newAdditionalURL = ""; + var tempArray = url.split("?"); + var baseURL = tempArray[0]; + var additionalURL = tempArray[1]; + var temp = ""; + if (additionalURL) { + tempArray = additionalURL.split("&"); + for (var i=0; i div.onclick > svg > path').attr('fill', '#0000aa'); + $('path').attr('fill', '#0000aa') +// }, 5000) +}); +*/ + +window.addEventListener("load",function(event) { + document.onkeyup = function(event) + { + var e = (!event) ? window.event : event; + switch(e.keyCode) + { + case 13: //CR + just_hit_escape = false; + if ($(document.activeElement).hasClass("workspace")) { + process_workspace() + } + case 27: //esc + // var parent_sage_cell = $(this).closest(".sagecell_editor"); + var parent_sage_cell = document.activeElement.closest(".sagecell_editor"); + if (parent_sage_cell && !just_hit_escape) { + console.log("staying in the sage cell", parent_sage_cell, document.activeElement) + just_hit_escape = true; + setTimeout(function(){ just_hit_escape = false }, 1000); + // console.log("parent_sage_cell", parent_sage_cell); + // if ($(parent_sage_cell).hasClass('sagecell_editor')) { + // console.log("I am trapped in a sage cell", $(document.activeElement).closest(".sagecell_editor")); + // console.log($(document.activeElement)); + // var this_sage_cell = $(document.activeElement).closest(".sagecell_editor"); + // this_sage_cell.next().focus; + // } + // else + } else + if(knowl_focus_stack.length > 0 ) { + most_recently_opened = knowl_focus_stack.pop(); + knowl_focus_stack_uid.pop(); + most_recently_opened.focus(); + console.log("moved back one knowl"); + } else { + console.log("no open knowls being tracked"); + break; + } + break; + } +}; +}, +false); + +// a hack for hosted tracking + +window.addEventListener("load",function(event) { + if($('body').attr('id') == "judson-AATA") { + console.log(" found AATA"); + console.log(" looking for id"); + if (typeof eBookConfig !== 'undefined') { + if(eBookConfig['username']) { + aa_id = "run" + eBookConfig['username']; + ut_id = eBookConfig['username']; + console.log(" done looking for id", ut_id); +var newscript = document.createElement('script'); + newscript.type = 'text/javascript'; + newscript.async = true; + newscript.src = 'https://pretextbook.org/js/' + '0.13' + '/' + 'trails' + '.js'; + var allscripts = document.getElementsByTagName('script'); + var s = allscripts[allscripts.length - 1]; + console.log('s',s); + console.log("adding a script", newscript); + s.parentNode.insertBefore(newscript, s.nextSibling); + trail = true; + console.log(" done adding script"); + } else { + console.log(" did not find username"); + } + } else { + console.log(" did not find eBookConfig") + } + } +}); + +function loadResource(type, file) { + /* type should be js or css */ + if (typeof js_version === 'undefined') { js_version = '0.2' } + if (typeof css_version === 'undefined') { css_version = '0.6' } + var newresource, allresources, s; + var linktype = "script"; + if (type == "css") { linktype = "link" } + newresource = document.createElement(linktype); + + if (type == "css") { + newresource.type = 'text/css'; + newresource.rel = 'stylesheet'; + newresource.href = 'https://pretextbook.org/css/' + css_version + '/' + file + '.css'; + newresource.href += '?minivers=' + minivers; + } else if (type == "js") { + newresource.type = 'text/javascript'; +// newscript.async = true; + newresource.src = 'https://pretextbook.org/js/' + js_version + '/' + file + '.js'; + newresource.src += '?minivers=' + minivers; + } else { + console.log("unknown resource type", type, "for", file); + return + } + + allresources = document.getElementsByTagName(linktype); + s = allresources[allresources.length - 1]; + console.log('s',s); + console.log("adding a resource", newresource); + s.parentNode.insertBefore(newresource, s.nextSibling); +} + + +window.addEventListener("load",function(event) { + if(false && $('body').attr('id') == "pretext-SA") { + console.log(" found DMOI"); + if (typeof uname === "undefined") { uname = "" } + console.log("aaaa", uname, " uname"); + if(uname == "editor") { + loadResource('js', 'edit'); + } else { + console.log("not enabling editing") + } + /* } else if ($('body').attr('id') == "pugetsound-SW") { */ + } else if (false && window.location.href.includes("soundwriting.pugetsound")) { +/* a bunch of temporary exploration for a Sound Writing survey */ + console.log("please take our survey"); + console.log(window.location.href); + console.log(window.location.href.includes("soundwriting.pugetsound")); + + loadResource("js", "login"); + loadResource("css", "features"); + setTimeout( loadResource("js", "survey"), 1000); /* I know: sloppy */ + + // } else if ((typeof online_editable !== 'undefined') && online_editable) { + } else if (false && $('body').attr('id') == "pretext-SA") { + loadResource('css', 'features'); + loadResource('js', 'login') + loadResource('js', 'edit'); + } else { + var this_source_txt; + var source_url = window.location.href; + source_url = source_url.replace(/(#|\?).*/, ""); + source_url = source_url.replace(/html$/, "ptx"); + if (typeof sourceeditable !== 'undefined') { + fetch(source_url).then( + function(u){ return u.text();} + ).then( + function(text){ + this_source_txt = text; + if (this_source_txt.includes("404 Not")) { + console.log("Editing not enabled: source unavailable") + } else { + loadResource('css', 'features'); + loadResource('css', 'edit'); + loadResource('js', 'login') + loadResource('js', 'edit'); + } + } + ); + } else { + console.log("Source file unavailable: editing not possible") + } + } + +}); + +// this is to open every knowl on a page +// (this code is not actually used anywhere) +window.addEventListener("load",function(event) { + if($('body').hasClass("braillesample")) { + var knowl_id_counterX = 0; + console.log(" found braillesample"); + var all_knowls = $('[data-knowl]'); + console.log("found", all_knowls.length, "knowls"); + console.log("which are", all_knowls); + for (var j=1; j < all_knowls.length; ++j) { + console.log(j, "un-knowling", all_knowls[j]); + console.log("attr", $(all_knowls[j]).attr("data-knowl")); + $knowl = $(all_knowls[j]); + if(!$knowl.attr("data-knowl-uid")) { + $knowl.attr("data-knowl-uid", knowl_id_counterX); + knowl_id_counterX++; + } + knowl_click_handler($knowl); + // knowl_click_handler($(all_knowls[j])) + } +}}); + +// when the anchor is a knowl, open it +window.addEventListener("load",function(event) { + if (window.location.hash.length) { + let id = window.location.hash.substring(1); + var the_anchor = document.getElementById(id); + console.log("id", id, "the_anchor", the_anchor); + if (the_anchor.tagName == "ARTICLE") { + var contained_knowl = the_anchor.querySelector("a[data-knowl]"); + if (contained_knowl && contained_knowl.parentElement == the_anchor) { + console.log("found a knowl", contained_knowl); + // knowl_click_handler($(contained_knowl)) + contained_knowl.click() + } + } else if (the_anchor.hasAttribute("data-knowl")) { + the_anchor.click() + } else { + // if it is a hidden knowl, find the knowl and open it + var this_hidden_content = the_anchor.closest(".hidden-content"); + if (this_hidden_content) { + console.log("linked to a hidden knowl with this_hidden_content", this_hidden_content); + var the_refid = this_hidden_content.id; + var this_knowl = document.querySelector('[data-refid="' + the_refid + '"]'); + this_knowl.click() + } + } + } +}); + + +// What purpose does this serve? +function urlattribute() { + var this_urlstub = window.location.hostname; + document.body.setAttribute("data-urlstub", this_urlstub); +} + + +// The new method for creating pages and adjusting workspace // + +// This is used multiple places to set height of workspace divs to their author-provided heights +function setInitialWorkspaceHeights() { + const workspaces = document.querySelectorAll('.workspace'); + workspaces.forEach(ws => { + ws.style.height = ws.getAttribute('data-space') || '0px'; + ws.setAttribute("contenteditable", "true"); + }); +} + +// If a printout (worksheet or handout) includes authored pages, we only need to put content before the first page and after the last page into the first and last pages, respectively. +function adjustPrintoutPages() { + const printout = document.querySelector('section.worksheet, section.handout'); + if (!printout) { + console.warn("No printout found, exiting adjustPrintoutPages."); + return; + } + const pages = printout.querySelectorAll('.onepage'); + if (pages.length === 0) { + console.warn("No pages found in printout, exiting adjustPrintoutPages."); + return; + } + // Find all children before the first .onepage element: + const firstPage = pages[0]; + const lastPage = pages[pages.length - 1]; + // Move all children before the first page into the first page + const pageFirstChild = firstPage.firstChild; + let currentChild = printout.firstChild; + while (currentChild && currentChild !== firstPage) { + const nextChild = currentChild.nextSibling; // Save the next sibling before removing + firstPage.insertBefore(currentChild, pageFirstChild); // Move to the first page + currentChild = nextChild; // Move to the next child + } + // Now find all children after the last .onepage element: + let nextChild = lastPage.nextSibling; + while (nextChild) { + const tempChild = nextChild; + nextChild = nextChild.nextSibling; + lastPage.appendChild(tempChild); + } + console.log("Moved all content before the first page and after the last page into the respective pages."); +} + +// This is the main function we will call then a printout does not come from the XSL with pages already defined (for now, the XSL will keep the behavior as an option). +function createPrintoutPages(margins) { + + // Assumptions: needs to work for both letter (8.5in x 11in) and a4 (210mm x 297mm) paper sizes. We will work in pixels (96/in): those are 816px x 1056px and 794px x 1122.5px respectively (1 inch = 96 px, 1 cm = 37.8 px). We assume that the printing interface of the browser will do the right thing with these. + + // For purposes of finding page breaks, we will use 794 as our width and 1056 as our height (so A4 width and letter height). Then we will rescale workspace on each page to fit the actual paper size selected. + + const conservativeContentHeight = 1056 - (margins.top + margins.bottom); // in pixels + const conservativeContentWidth = 794 - (margins.left + margins.right); // in pixels + + const printout = document.querySelector('section.worksheet, section.handout'); + if (!printout) { + console.warn("No printout found, exiting createPrintoutPages."); + return; + } + printout.style.width = toString(conservativeContentWidth + margins.left + margins.right) + 'px'; + // Set the height of each workspace based on its data-space attribute + setInitialWorkspaceHeights(printout); + + // We want to consider each "block" of the printout. Some of these will be direct children of the printout, some will be nested inside these children. So first create a list of the elements that we consider blocks. + let rows = []; + for (const child of printout.children) { + if (child.classList.contains('sidebyside')) { + // sidebyside could have tasks, but we don't want to dive further into them. + rows.push(child); + } else if (child.querySelector('.task')) { + // Keep the child as a block, but put each task after the first one as its own row: + rows.push(child); + const tasks = child.querySelectorAll('.task'); + for (let i = tasks.length-1; i > 0; i--) { + // Move the task out of the original child and place it directly after it in the printout. We do this in reverse order so when every task is moved, they return to the original order. They will then be added to the rows list as their own blocks. + printout.insertBefore(tasks[i], child.nextSibling); + } + // Skipping separate treatment of exercisegroups for now. + //} else if (child.classList.contains('exercisegroup')) { + // for (const subChild of child.children) { + // if (subChild.classList.contains('exercisegroup-exercises')){ + // for (const row of subChild.children){ + // rows.push(row); + // } + // } else { + // rows.push(child); + // } + // } + } else { + rows.push(child); + } + } + // Loop through the blocks and create a list of objects including the block, its height, and its workspace height. Only include blocks that have height (this will remove autopermalinks, as desired). + let blockList = []; + for (const row of rows) { + let blockHeight = getElementTotalHeight(row); + if (blockHeight === 0) { + console.log("Skipping row with zero height:", row); + continue; + } + let totalWorkspaceHeight = 0; + if (row.querySelector('.workspace')) { + // Workspace height is not just sum of workspace heights; we need to be careful with sidebyside and columns + totalWorkspaceHeight = getElemWorkspaceHeight(row); + } + blockList.push({elem: row, height: blockHeight, workspaceHeight: totalWorkspaceHeight}); + } + + // Now find pageBreaks so that extra workspace is as uniform as possible. + const pageBreaks = findPageBreaks(blockList, conservativeContentHeight); + + // Create page divs and insert rows into them + for (let i = 0; i < pageBreaks.length; i++) { + const pageDiv = document.createElement('section'); + pageDiv.classList.add('onepage'); + if (i === 0) { + pageDiv.classList.add('firstpage'); + } + // A single page will be both first and last + if (i === pageBreaks.length - 1) { + pageDiv.classList.add('lastpage'); + } + // The pageBreaks array gives the indices of blocks that should start a page. + // So we will want to look for go through the blocks selecting those starting with the previous index (or 0) up to but not including the current index. + const start = pageBreaks[i-1] || 0; + const end = pageBreaks[i]; + for (let j = start; j < end; j++) { + const row = blockList[j].elem; + pageDiv.appendChild(row); + } + printout.appendChild(pageDiv); + } + + // remove any old content that is not in a page + for (const child of printout.children) { + if (!child.classList.contains('onepage')) { + console.log("Removing old child not in a page:", child); + printout.removeChild(child); + } + } +} + + + // We look at each page and adjust the heights of the workspaces to fit it nicely into the page. + // The width and height of the page will now depend on the letter or a4 setting. +function adjustWorkspaceToFitPage({paperSize, margins}) { + let paperWidth, paperHeight; + if (paperSize === 'a4' || document.body.classList.contains('a4')) { + console.log("Setting page size to A4"); + paperWidth = 794; // 210mm in px + paperHeight = 1122.5; // 297mm in px 794px x 1122.5px + } else { + console.log("Setting page size to Letter"); + paperWidth = 816; // 8.5in in px + paperHeight = 1056; // 11in in px + } + const paperContentHeight = paperHeight - (margins.top + margins.bottom); + + // Reset the heights of workspace divs to their author-provided heights + setInitialWorkspaceHeights(); + + const pages = document.querySelectorAll('.onepage'); + pages.forEach(page => { + console.log("Adjusting workspace height for page:", page); + // Set width to get accurate calculations + page.style.width = paperWidth + 'px'; + const rows = page.children; + let totalContentHeight = 0; + let totalWorkspaceHeight = 0; + for (const row of rows) { + totalContentHeight += getElementTotalHeight(row); + totalWorkspaceHeight += getElemWorkspaceHeight(row); + } + if (totalWorkspaceHeight === 0) { + console.log("No workspaces on this page, skipping workspace adjustment."); + // Reset the style for the page + page.style.width = ""; + return; + } + const extraHeight = paperContentHeight - totalContentHeight; + console.log("Extra height to distribute across workspaces:", extraHeight, "px."); + // Determine the factor by which to multiply each workspace to make the total height fit the paperContentHeight + const workspaceAdjustmentFactor = (totalWorkspaceHeight + extraHeight) / totalWorkspaceHeight; + console.log("Workspace adjustment factor for page:", workspaceAdjustmentFactor); + // Now adjust each workspace in the page by this factor + const pageWorkspaces = page.querySelectorAll('.workspace'); + pageWorkspaces.forEach(ws => { + const originalHeight = ws.offsetHeight; + const newHeight = originalHeight * workspaceAdjustmentFactor; + ws.style.height = newHeight + "px"; + }); + // Reset the style for the page + page.style.width = ""; + }); + console.log("Set page sizes to content area of paper size."); +} + +// Helper functions for calculating heights and workspace sizes +function getElementTotalHeight(elem) { + // Calculate the total height of the element, including padding, border, and top margin. + const style = getComputedStyle(elem); + const marginTop = parseFloat(style.marginTop); + const marginBottom = parseFloat(style.marginBottom); + const height = elem.offsetHeight; + return height + marginTop + marginBottom; +} + +function getElemWorkspaceHeight(elem) { + // Calculate the total height of all workspaces in the element. + // This is easy for elements stacked vertically, but we must be careful for side-by-side workspaces. Since we will multiply each workspace by a factor to fit the page, taking the largest workspace height should give us an upper bound for the amount of vertical space that is workspace. + // Note that this won't work well if we need to reduce the workspace, since there we would want to take the minimum heights. + if (elem.classList.contains('sidebyside')) { + const sbspanels = elem.querySelectorAll('.sbspanel'); + let max = 0; + sbspanels.forEach(panel => { + const workspaces = panel.querySelectorAll('.workspace'); + let totalHeight = 0; + workspaces.forEach(workspace => { + const workspaceHeight = workspace.offsetHeight; + if (workspaceHeight) { + totalHeight += workspaceHeight; + } + }); + if (totalHeight > max) { + max = totalHeight; // Take the maximum height of workspaces in sidebyside + } + }); + return max; // Return the maximum height of workspaces in sidebyside + } + // We can take care of exercisegroups and single colomn regular layout together. + let columns = 1; + if (elem.classList.contains('exercisegroup')) { + // Check for column classes and set columns accordingly + for (let i = 2; i <= 6; i++) { + if (elem.querySelector(`.cols${i}`)) { + columns = i; + console.log("Found exercisegroup with columns:", columns); + break; + } + } + } + const workspaces = elem.querySelectorAll('.workspace'); + let totalHeight = 0; + workspaces.forEach(ws => { + const workspaceHeight = ws.offsetHeight; + if (workspaceHeight) { + totalHeight += workspaceHeight; + } + }); + return totalHeight / columns; // Divide by columns if sidebyside to get average height per column +} + +// Functions for finding the optimal page breaks +function findPageBreaks(rows, pageHeight) { + // An array for the page breaks. The nth element will be the index of the last row on page n. + let pageBreaks = []; + // An array for the minimum cost possible for rows i to the end. + let minCost = Array(rows.length).fill(Infinity); + minCost[rows.length] = 0; // No cost for no rows + // An array to keep track of the next row to start a new page after i in minCost. + let nextPageBreak = Array(rows.length).fill(-1); + + // Now loop through the rows in reverse order to find the optimal page breaks. + for (let i = rows.length - 1; i >= 0; i--) { + let cumulativeHeight = 0; + let cumulativeWorkspaceHeight = 0; + // Loop through the rows starting from i to find the best page break + for (let j = i; j < rows.length; j++) { + cumulativeHeight += rows[j].height; + cumulativeWorkspaceHeight += rows[j].workspaceHeight; + if (cumulativeHeight > pageHeight) { + if (j === i) { + // The page height is too big for a single row. We make this row its own page and move on. + console.log("Row", i, "exceeds page height by itself, setting as its own page."); + minCost[i] = 0; // No cost for a single row + nextPageBreak[i] = i + 1; // The next page break is after this row + break; // Move to the next row + } else { + // We have already set minCost and NextPageBreak at an earlier point in the loop. This means we have done the best we can for this row so we stop and move to the next earlier row. + break; // Stop if we exceed the page height + } + } + + const cost = (pageHeight - cumulativeHeight)**2 + minCost[j+1]; // Cost is how much space is left on the page, plus the cost of the following pages. + if (cost < minCost[i]) { + minCost[i] = cost; + nextPageBreak[i] = j+1; // Set the next page break to be after row j + } + } + } + // Backtrack to find the actual page breaks based on nextPageBreak + // Note: the nextPage = 1 is not an indexing mistake; we always assume that row 0 is a title and will go on the same page as row 1. + let nextPage = 1; + while (nextPage < rows.length) { + pageBreaks.push(nextPageBreak[nextPage]); + nextPage = nextPageBreak[nextPage]; + } + return pageBreaks; +} + +function setPageGeometryCSS({paperSize, margins}) { + // Remove any existing geometry CSS to avoid duplicates + const existingStyle = document.getElementById("page-geometry-css"); + if (existingStyle) { + existingStyle.remove(); + } + let wsWidth = paperSize === "letter" ? "816px" : "794px"; // 8.5in for Letter, 210mm for A4 + let wsHeight = paperSize === "letter" ? "1056px" : "1123px"; // 11in for Letter, 297mm for A4 + // Create a new style element for geometry CSS + const style = document.createElement("style"); + // Add an identifier to the style element to avoid conflicts + style.id = "page-geometry-css"; + // NB we need to add the fallback values for the margins in @page because some browsers do not support CSS variables in @page rules. + style.textContent = ` + :root { + --ws-width: ${wsWidth}; + --ws-height: ${wsHeight}; + --ws-top-margin: ${margins.top}px; + --ws-right-margin: ${margins.right}px; + --ws-bottom-margin: ${margins.bottom}px; + --ws-left-margin: ${margins.left}px; + } + @page { + margin: var(--ws-top-margin, ${margins.top}px) var(--ws-right-margin, ${margins.right}px) var(--ws-bottom-margin, ${margins.bottom}px) var(--ws-left-margin, ${margins.left}px); + } + `; + document.head.appendChild(style); +} + +function toggleWorkspaceHighlight(isChecked) { + if (isChecked) { + // Toggle the highlight class on the body based on the checkbox state + document.body.classList.add("highlight-workspace"); + // If we haven't already inserted divs to show the original workspace heights, do that now + if (!document.querySelector('.workspace-container')) { + console.log("adding original workspace divs"); + // Insert divs to show the original workspace + document.querySelectorAll('.workspace').forEach(workspace => { + // Create a container div to hold the workspace div and the original div + const container = document.createElement('div'); + container.classList.add('workspace-container'); + // Set the container height to the current workspace height + container.style.height = window.getComputedStyle(workspace).height; + const original = document.createElement('div'); + original.classList.add('original-workspace'); + const originalHeight = workspace.getAttribute('data-space') || '0px'; + original.setAttribute('title', 'Author-specified workspace height (' + originalHeight + ')'); + console.log("setting original workspace height for", workspace); + // Use the data-space attribute for height of original workspace + original.style.height = originalHeight; + // insert original div before the workspace content + container.appendChild(original); + // Add a warning class if the original height is greater than the current height + if (original.offsetHeight > workspace.offsetHeight) { + original.classList.add('warning'); + } + // Move the workspace into the container + workspace.parentNode.insertBefore(container, workspace); + container.appendChild(workspace); + }); + } + } else { + document.body.classList.remove("highlight-workspace"); + } +} + +// Printout print preview and page setup +window.addEventListener("load",function(event) { + // We condition on the existence of the papersize radio buttons, which only appear in the printout print preview. + if (document.querySelector('input[name="papersize"]')) { + // First, get the margins for pages to be passed around as needed. + const marginList = document.querySelector('section.worksheet, section.handout').getAttribute('data-margins').split(' '); + // Convert margin values to pixels if they are not already numbers + function toPixels(value) { + if (typeof value === "number") return value; + if (typeof value !== "string") return 0; + value = value.trim(); + if (value.endsWith("px")) { + return parseFloat(value); + } else if (value.endsWith("in")) { + return Math.floor(parseFloat(value) * 96); + } else if (value.endsWith("cm")) { + return Math.floor(parseFloat(value) * 37.8); + } else if (value.endsWith("mm")) { + return Math.floor(parseFloat(value) * 3.78); + } else if (value.endsWith("pt")) { + return Math.floor(parseFloat(value) * (96 / 72)); + } else { + // fallback: try to parse as px + return parseFloat(value) || 0; + } + } + const margins = { + top: toPixels(marginList[0] || "0.75in"), // Default to 0.75in if not specified + right: toPixels(marginList[1] || "0.75in"), + bottom: toPixels(marginList[2] || "0.75in"), + left: toPixels(marginList[3] || "0.75in") + } + // Get the papersize from localStorage or set it based on user's geographic region + let paperSize = localStorage.getItem("papersize"); + if (paperSize) { + const radio = document.querySelector(`input[name="papersize"][value="${paperSize}"]`); + if (radio) { + radio.checked = true; + } + // Set the papersize class on body + document.body.classList.remove("a4", "letter"); + document.body.classList.add(paperSize); + setPageGeometryCSS({paperSize: paperSize, margins: margins}); + } else { + // Try to set papersize based on user's geographic region + // Default to 'letter' for North and South America, 'a4' elsewhere + try { + fetch('https://ipapi.co/json/') + .then(response => response.json()) + .then(data => { + let continent = data && data.continent_code ? data.continent_code : ""; + paperSize = (continent === "NA" || continent === "SA") ? "letter" : "a4"; + const radio = document.querySelector(`input[name="papersize"][value="${paperSize}"]`); + if (radio) { + radio.checked = true; + localStorage.setItem("papersize", paperSize); + } + document.body.classList.remove("a4", "letter"); + document.body.classList.add(paperSize); + console.log("Setting papersize to", paperSize); + }) + .catch((err) => { + // rethrow to be caught by the outer catch + throw err; + }); + } catch (e) { + // fallback: default to letter + const radio = document.querySelector(`input[name="papersize"][value="letter"]`); + if (radio) radio.checked = true; + } + //NB: the default papersize is set to 'letter' in the body class list. + } + const papersizeRadios = document.querySelectorAll('input[name="papersize"]'); + papersizeRadios.forEach(radio => { + radio.addEventListener('change', function() { + if (this.checked) { + document.body.classList.remove("a4", "letter"); + document.body.classList.add(this.value); + localStorage.setItem("papersize", this.value); + console.log("Setting papersize to", this.value); + + // If the "highlight workspace" checkbox was already checked, then we should restart the process by reloading the page. Specifically, we run into issues when there are .workspace-container divs already present. + if (document.querySelector(".workspace-container")) { + console.log("Reloading page to apply new papersize with workspace highlight enabled."); + window.location.reload(); + return; + } else { + // Otherwise, we can just adjust the workspace heights to fit the new paper size. + console.log("Adjusting workspace heights to fit new papersize."); + adjustWorkspaceToFitPage({paperSize: this.value, margins: margins}); + setPageGeometryCSS({paperSize: this.value, margins: margins}); + } + } + }); + }); + + // Open all details elements (knowls) on the page + var born_hidden_knowls = document.querySelectorAll('details'); + console.log("born_hidden_knowls", born_hidden_knowls); + born_hidden_knowls.forEach(function(detail) { + detail.open = true; + }); + // If the printout has authored pages, there will be at least one .onepage element. + if (document.querySelector('.onepage')) { + adjustPrintoutPages(); + /* not the right way: need to figure out what this needs to wait for */ + //window.setTimeout(adjustPrintoutPages, 1000); + } else { + createPrintoutPages(margins); + } + // After pages are set up, we adjust the workspace heights to fit the page (based on the paper size). + adjustWorkspaceToFitPage({paperSize: paperSize, margins: margins}); + + console.log("finished adjusting workspace"); + + + // Get the 'highlight workspace' checkbox state from localStorage or set it to false by default + const highlightWorkspaceCheckbox = document.getElementById("highlight-workspace-checkbox"); + if (highlightWorkspaceCheckbox) { + highlightWorkspaceCheckbox.checked = localStorage.getItem("highlightWorkspace") === "true"; + highlightWorkspaceCheckbox.addEventListener("change", function() { + localStorage.setItem("highlightWorkspace", this.checked); + toggleWorkspaceHighlight(this.checked); + }); + // Initial toggle to apply the highlight class if checked + toggleWorkspaceHighlight(highlightWorkspaceCheckbox.checked); + } + + + + // Not sure why this is here: + window.setTimeout(urlattribute, 1500); + } +}); + + + +//----------------------------------------------------------------- +// Dark/Light mode swiching + +function isDarkMode() { + if (document.documentElement.dataset.darkmode === 'disabled') + return false; + + const currentTheme = localStorage.getItem("theme"); + if (currentTheme === "dark") + return true; + else if (currentTheme === "light") + return false; + + return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; +} + +function setDarkMode(isDark) { + if(document.documentElement.dataset.darkmode === 'disabled') + return; + + const parentHtml = document.documentElement; + const iframes = document.querySelectorAll("iframe[data-dark-mode-enabled]"); + + // Update the parent document + if (isDark) { + parentHtml.classList.add("dark-mode"); + } else { + parentHtml.classList.remove("dark-mode"); + } + + // Sync each iframe's class with the parent + for (const iframe of iframes) { + try { + const iframeHtml = iframe.contentWindow.document.documentElement; + if (isDark) { + iframeHtml.classList.add("dark-mode") + } else { + iframeHtml.classList.remove("dark-mode") + } + } catch (err) { + console.warn("Dark mode sync to iframe failed:", err); + } + } + + const modeButton = document.getElementById("light-dark-button"); + if (modeButton) { + modeButton.querySelector('.icon').innerText = isDark ? "light_mode" : "dark_mode"; + modeButton.querySelector('.name').innerText = isDark ? "Light Mode" : "Dark Mode"; + } +} + +// Run this as soon as possible to avoid flicker +setDarkMode(isDarkMode()); + +// Rest of dark mode setup logic waits until after load +window.addEventListener("DOMContentLoaded", function(event) { + // Rerun setDarkMode now that it can update buttons + const isDark = isDarkMode(); + setDarkMode(isDark); + + const modeButton = document.getElementById("light-dark-button"); + modeButton.addEventListener("click", function() { + const wasDark = isDarkMode(); + setDarkMode(!wasDark); + localStorage.setItem("theme", wasDark ? "light" : "dark"); + }); +}); + +// Share button and embed in LMS code +window.addEventListener("DOMContentLoaded", function(event) { + const shareButton = document.getElementById("embed-button"); + if (shareButton) { + const sharePopup = document.getElementById("embed-popup"); + const embedCode = ""; + const embedTextbox = document.getElementById("embed-code-textbox"); + if (embedTextbox) { + embedTextbox.value = embedCode; + } + shareButton.addEventListener("click", function() { + sharePopup.classList.toggle("hidden"); + }); + const copyButton = document.getElementById("copy-embed-button"); + if (copyButton) { + copyButton.addEventListener("click", function() { + const embedTextbox = document.getElementById("embed-code-textbox"); + if (embedTextbox) { + navigator.clipboard.writeText(embedCode).then(() => { + console.log("Embed code copied to clipboard!"); + }).catch(err => { + console.error("Failed to copy embed code: ", err); + }); + //copyButton.innerHTML = "✓✓"; + // show confirmation for 2 seconds: + copyButton.querySelector('.icon').innerText = "library_add_check"; + setTimeout(function() { + copyButton.querySelector('.icon').innerText = "content_copy"; + sharePopup.classList.add("hidden"); + }, 450); + } + }); + } + } +}); + +// Hide everything except the content when the URL has "embed" in it +window.addEventListener("DOMContentLoaded", function(event) { + const urlParams = new URLSearchParams(window.location.search); + if (urlParams.has("embed")) { + // Set dark mode based on value of param + if (urlParams.get("embed") === "dark") { + setDarkMode(true); + } else { + setDarkMode(false); + } + const elemsToHide = [ + "ptx-navbar", + "ptx-masthead", + "ptx-page-footer", + "ptx-sidebar", + "ptx-content-footer" + ]; + for (let id of elemsToHide) { + const elem = document.getElementById(id); + if (elem) { + elem.classList.add("hidden"); + } + } + } +}); + +// START Support for code-copy button functionality +document.addEventListener("click", (ev) => { + const codeBox = ev.target.closest(".clipboardable"); + if (!navigator.clipboard || !codeBox) return; + const button = ev.target.closest(".code-copy"); + const preContent = codeBox.querySelector("pre").textContent; + navigator.clipboard.writeText(preContent); + button.classList.toggle("copied") + setTimeout(() => button.classList.toggle("copied"), 1000); +}); + +document.addEventListener("DOMContentLoaded", () => { + const elements = document.querySelectorAll(".clipboardable"); + for (el of elements) { + const div = document.createElement("div"); + div.classList.add("clipboardable"); + el.classList.remove("clipboardable"); + el.replaceWith(div); + div.insertAdjacentElement("afterbegin", el); + div.insertAdjacentHTML("beforeend", ` + + `.trim()); + } +}); +// END Support for code-copy button functionality diff --git a/slides/_static/pretext/js/pretext_search.js b/slides/_static/pretext/js/pretext_search.js new file mode 100755 index 0000000..e78d765 --- /dev/null +++ b/slides/_static/pretext/js/pretext_search.js @@ -0,0 +1,315 @@ + +// next comment is out of date: there are more search options +// from lunr-pretext-search-index.js we will have either +// var ptx_lunr_search_style = "default"; +// or +// var ptx_lunr_search_style = "reference"; + +// since there is only one search box now, this can be simplified +function doSearch(searchlocation="A") { + // Get the search terms from the input text box + var terms; + if(searchlocation == "A") { + terms = document.getElementById("ptxsearch").value; + } else { + terms = document.getElementById("ptxsearchB").value; + } + + localStorage.setItem('last-search-terms', JSON.stringify({terms: terms, time: Date.now()})); + + // Where do we want to put the results? + let resultArea = document.getElementById("searchresults") + resultArea.innerHTML = ""; // clear out any previous results + // assume AND for multiple words + var searchterms = terms; + if(searchlocation == "B") { + document.getElementById("ptxsearch").value = searchterms + console.log("ptxsearch value", document.getElementById("ptxsearch").value); + } else { + searchterms = terms; + } + + searchterms = searchterms.toLowerCase().trim(); + let pageResult = []; + if(searchterms != "") { + pageResult = ptx_lunr_idx.query((q) => { + for(let term of searchterms.split(' ')) { + q.term(term, { fields: ["title"], boost: 20 }); //exact title match with 20x weight + q.term(term, { wildcard: lunr.Query.wildcard.TRAILING, fields: ["title"], boost: 10 }); //inexact title 10x weight + q.term(term, { fields: ["body"], boost: 5 }); //exact body 5x weight + q.term(term, { wildcard: lunr.Query.wildcard.TRAILING, fields: ["body"] }); //inexact body + } + }); + } + // Number the documents from first to last so we can order the results by their + // position in the book. + snum = 0; + for (let doc of ptx_lunr_docs) { + doc.snum = snum; + snum += 1; + } + + //Limit to a sane number of results - otherwise search like 'e' matches every page + const MAX_RESULTS = 100; + let numUnshown = (pageResult.length > MAX_RESULTS) ? pageResult.length - MAX_RESULTS : 0; + pageResult.slice(0, MAX_RESULTS); + + // Transfer meta data from the document to the results to make it easy to add + // our lists later. + augmentResults(pageResult, ptx_lunr_docs); + pageResult.sort(comparePosition); + addResultToPage(terms, pageResult, ptx_lunr_docs, numUnshown, resultArea); + MathJax.typeset(); +} + +// Find the entry for a search result in the original document index +function findEntry(resultId, db) { + for (const page of db) { + if (page.id === resultId) { + return page; + } + } + return resultId; +} + +function augmentResults(result, docs) { + for (let res of result) { + let info = findEntry(res.ref, docs); + res.number = info.number; + res.type = info.type; + res.title = info.title; + res.url = info.url; + res.level = info.level; + res.snum = info.snum; + res.score = parseFloat(res.score); + + //extra score multiplier based on level - prioritize sections over subsections/exercises/etc... + const LEVEL_WEIGHTS = [3, 2, 1.5] + if( res.level < 2 ) + res.score *= LEVEL_WEIGHTS[res.level]; + + res.body = ''; + //Add body snippets and highlights + const REVEAL_WINDOW = 30; + let titleMarked = false; + for (const hit in res.matchData.metadata) { + if(res.matchData.metadata[hit].title) { + //only show one match in title as locations change after first markup + if(!titleMarked) { + if(!res.matchData.metadata[hit].title.position) + continue; + let positionData = res.matchData.metadata[hit].title.position[0]; + const startClipInd = positionData[0]; + const endClipInd = positionData[0] + positionData[1]; + res.title = res.title.substring(0, endClipInd) + '' + res.title.substring(endClipInd); + res.title = res.title.substring(0, startClipInd) + '' + res.title.substring(startClipInd); + titleMarked = true; + } + } else if (res.matchData.metadata[hit].body) { + if(!res.matchData.metadata[hit].body.position) + continue; + const bodyContent = info.body; + let positionData = res.matchData.metadata[hit].body.position[0]; + const startInd = positionData[0] - REVEAL_WINDOW; + const endInd = positionData[0] + positionData[1] + REVEAL_WINDOW; + const startClipInd = positionData[0]; + const endClipInd = positionData[0] + positionData[1]; + let resultSnippet = (startInd > 0 ? '...' : '' ) + bodyContent.substring(startInd, startClipInd); + resultSnippet += '' + bodyContent.substring(startClipInd, endClipInd) + ''; + resultSnippet += bodyContent.substring(endClipInd, endInd) + (endInd < bodyContent.length ? '...' : '' ) + '
    '; + res.body += resultSnippet; + } + } + } +} + +function rearrangedArray(arry) { + // return a new array which is arry (with depth) sorted according to meas, + // again as an array with depth. + // "with depth'' means that large children drag along their parents. + let newarry = []; + let startind = 0; + let numtograb = 0; +let ct = 1; + while (arry.length > 0 && ct < 500) { +++ct; // just in case something goes wrong + const locofmax = maxLocation(arry) + let segmentstart = locofmax; + let segmentlength = 1; + while (arry[segmentstart].level == "2") { + --segmentstart + } + while (segmentstart + segmentlength < arry.length && arry[segmentstart + segmentlength].level == "2") { + ++segmentlength + } +// console.log("locofmax", locofmax, "starting", segmentstart, "going", segmentlength, "from", arry.length); + newarry.push(...arry.splice(segmentstart,segmentlength)); + } +// console.log("newarry", newarry); + return newarry +} +function maxLocation(arry) { + let maxloc = 0; + let maxvalsofar = -1; + for (let index = 0; index < arry.length; ++index) { + if (arry[index].score > maxvalsofar) { + maxloc = index; + maxvalsofar = arry[index].score + } + } + return maxloc +} + +function comparePosition(a, b) { + if (a.snum < b.snum) { + return -1; + } + if (a.snum > b.snum) { + return 1; + } + return 0; +} +function compareScoreDesc(a, b) { + if (a.score < b.score) { + return 1; + } + if (a.score > b.score) { + return -1; + } + return 0; +} + +function addResultToPage(searchterms, result, docs, numUnshown, resultArea) { + /* backward compatibility for old html */ + if (document.getElementById("searchempty")) { + document.getElementById("searchempty").style.display = "none"; + } + let len = result.length; + console.log("first result", result[0]); + if (len == 0) { + if (document.getElementById("searchempty")) { + document.getElementById("searchempty").style.display = "block"; + } else { + let noresults = document.createElement("div"); + noresults.classList.add("noresults"); + search_no_results_string = "No results were found" + noresults.innerHTML = search_no_results_string + "."; + // console.log("the new variable", search_results_heading_string); + resultArea.appendChild(noresults); + } + document.getElementById("searchresultsplaceholder").style.display = null; + return + } +// console.log("result",result); + let allScores = result.map(function (r) { return r.score }); +// console.log(typeof allScores[0], "allScores",allScores); + allScores.sort((a,b) => (a - b)); + allScores.reverse(); +// console.log("allScores, sorted",allScores); + +// let high = result[Math.floor(len*0.25)].score; +// let med = result[Math.floor(len*0.5)].score; +// let low = result[Math.floor(len*0.75)].score; + // sort the results by their position in the book, not their score + let high = allScores[Math.floor(len*0.20)]; + let med = allScores[Math.floor(len*0.40)]; + let low = allScores[Math.floor(len*0.75)]; + if (ptx_lunr_search_style == "reference") { + result = rearrangedArray(result); + } + let indent = "1"; + let currIndent = indent; + let origResult = resultArea; + // Create list entries indenting as needed. + for (const res of result) { + let link = document.createElement("a") + // add a class so we can colorize the results based on their rank in terms + // of search score. + if (res.score >= high) { + link.classList.add("high_result") + } else if (res.score >= med) { + link.classList.add("medium_result") + } else if (res.score >= low) { + link.classList.add("low_result") + } else { + link.classList.add("no_result") + } + currIndent = res.level; + if (currIndent > indent) { + indent = currIndent; + let ilist = document.createElement("ul") + ilist.classList.add("detailed_result"); + resultArea.appendChild(ilist); + resultArea = ilist; + } else if (currIndent < indent) { + resultArea = origResult; + indent = currIndent; + } + link.href = `${res.url}`; + link.innerHTML = `${res.type} ${res.number} ${res.title}`; + let clip = document.createElement("div"); + clip.classList.add("search-result-clip"); + clip.innerHTML = `${res.body}`; + let bullet = document.createElement("li"); + bullet.classList.add('search-result-bullet'); + bullet.appendChild(link); + bullet.appendChild(clip); + let p = document.createElement("text"); + p.classList.add('search-result-score'); + p.innerHTML = ` (${res.score.toFixed(2)})`; + bullet.appendChild(p); + resultArea.appendChild(bullet); + } + + // Auto-close search results when a result is clicked in case result is on + // the same page search started from + const resultsDiv = document.getElementById('searchresultsplaceholder'); + const backDiv = document.querySelector('.searchresultsbackground'); + resultArea.querySelectorAll("a").forEach((link) => { + link.addEventListener('click', (e) => { + backDiv.style.display = 'none'; + resultsDiv.style.display = 'none'; + }); + }); + //Could print message about how many results are not shown. No way to localize it though... + // if(numUnshown > 0) { + // let bullet = document.createElement("li"); + // bullet.classList.add('search-results-bullet'); + // bullet.classList.add('search-results-unshown-count'); + // let p = document.createElement("text"); + // p.innerHTML = `${parseInt(numUnshown)} unshown results...`; + // bullet.appendChild(p); + // resultArea.appendChild(bullet); + // } + document.getElementById("searchresultsplaceholder").style.display = null; + MathJax.typesetPromise(); +} + +window.addEventListener("load", function (event) { + const resultsDiv = document.getElementById('searchresultsplaceholder'); + + //insert a div to be backgroud behind searchresultsplaceholder + const backDiv = document.createElement("div"); + backDiv.classList.add("searchresultsbackground"); + backDiv.style.display = 'none'; + resultsDiv.parentNode.appendChild(backDiv); + + document.getElementById("searchbutton").addEventListener('click', (e) => { + resultsDiv.style.display = null; + backDiv.style.display = null; + let searchInput = document.getElementById("ptxsearch"); + searchInput.value = JSON.parse(localStorage.getItem("last-search-terms")).terms; + searchInput.select(); + doSearch(); + }); + + document.getElementById("ptxsearch").addEventListener('input', (e) => { + doSearch(); + }); + + document.getElementById("closesearchresults").addEventListener('click', (e) => { + resultsDiv.style.display = 'none'; + backDiv.style.display = 'none'; + document.getElementById('searchbutton').focus(); + }); +}); diff --git a/slides/_static/pretext/js/ptx_search.js b/slides/_static/pretext/js/ptx_search.js new file mode 100755 index 0000000..f35bea2 --- /dev/null +++ b/slides/_static/pretext/js/ptx_search.js @@ -0,0 +1,112 @@ +function doSearch() { + // Get the search terms from the input text box + let terms = document.getElementById("ptxsearch").value; + // Where do we want to put the results? + let resultArea = document.getElementById("searchresults") + resultArea.innerHTML = ""; // clear out any previous results + // do the search using the provided index + let pageResult = ptx_lunr_idx.search(terms); + // Number the documents from first to last so we can order the results by their + // position in the book. + snum = 0; + for (let doc of ptx_lunr_docs) { + doc.snum = snum; + snum += 1; + } + // Transfer meta data from the document to the results to make it easy to add + // our lists later. + augmentResults(pageResult, ptx_lunr_docs); + addResultToPage(pageResult, ptx_lunr_docs, resultArea); + MathJax.typeset(); +} + +// Find the entry for a search result in the original document index +function findEntry(resultId, db) { + for (const page of db) { + if (page.id === resultId) { + return page; + } + } + return resultId; +} + +function augmentResults(result, docs) { + for (let res of result) { + let info = findEntry(res.ref, docs); + res.number = info.number; + res.type = info.type; + res.title = info.title; + res.url = info.url; + res.level = info.level; + res.snum = info.snum; + } +} + + +function comparePosition(a, b) { + if (a.snum < b.snum) { + return -1; + } + if (a.snum > b.snum) { + return 1; + } + return 0; +} + +function addResultToPage(result, docs, resultArea) { + let len = result.length + let high = result[Math.floor(len*0.25)].score; + let med = result[Math.floor(len*0.5)].score; + let low = result[Math.floor(len*0.75)].score; + // sort the results by their position in the book, not their score + result = result.sort(comparePosition) + let indent = "1"; + let currIndent = indent; + let origResult = resultArea; + // Create list entries indenting as needed. + for (const res of result) { + let link = document.createElement("a") + // add a class so we can colorize the results based on their rank in terms + // of search score. + if (res.score >= high) { + link.classList.add("high_result") + } else if (res.score >= med) { + link.classList.add("medium_result") + } else if (res.score >= low) { + link.classList.add("low_result") + } + currIndent = res.level; + if (currIndent > indent) { + indent = currIndent; + let ilist = document.createElement("ul") + ilist.classList.add("detailed_result"); + resultArea.appendChild(ilist); + resultArea = ilist; + } else if (currIndent < indent) { + resultArea = origResult; + indent = currIndent; + } + let bullet = document.createElement("li") + bullet.style.marginTop = "5px"; + link.href = `${res.url}`; + link.innerHTML = `${res.type} ${res.number} ${res.title}`; + bullet.appendChild(link) + let p = document.createElement("text"); + p.innerHTML = ` (${res.score.toFixed(2)})`; + bullet.appendChild(p); + resultArea.appendChild(bullet); + } + +} + + +function showHelp() { + let state = document.getElementById("helpme").style.display; + if (state == "none") { + document.getElementById("helpme").style.display = "block"; + document.getElementById("helpbutt").innerHTML = "Hide Help" + } else { + document.getElementById("helpme").style.display = "none"; + document.getElementById("helpbutt").innerHTML = "Show Help" + } +} diff --git a/slides/_static/pretext/js/user_preferences.js b/slides/_static/pretext/js/user_preferences.js new file mode 100755 index 0000000..b5a63c6 --- /dev/null +++ b/slides/_static/pretext/js/user_preferences.js @@ -0,0 +1,510 @@ + +editorLog = console.log; +// editorLog = function(){}; +debugLog = function(){}; +// debugLog = console.log; +//parseLog = function(){}; +parseLog = console.log; +errorLog = console.log; + +/* the structure of each object, and its realization as PreTeXt source or in HTML, + is recorded in the objectStructure dictionary. + +{"xml:id": new_id, "sourcetag": new_tag, "parent": parent_description, "title": ""} + +In pretext, pieces are of the form ["piecename", "tag"], while +in source, pieces are of the form ["piecename", "required_component"], while + +*/ + +editing_mode = 0; + +current_page = location.host+location.pathname; +debugLog("current_page", current_page); +chosen_edit_option_key = "edit_option".concat(current_page); +/* +chosen_edit_option = readCookie(chosen_edit_option_key) || ""; +editing_mode = chosen_edit_option; +*/ +chosen_edit_option = "PLACEHOLDER"; +debugLog("chosen_edit_option", chosen_edit_option, "chosen_edit_option", chosen_edit_option > 0); + +var font_families = { + 'RS': "'Roboto Serif', serif;", + 'OS': "'Open Sans', sans-serif;" +} + +var font_vals = { + // 'face': 'serif', + 'size': [12, 8, 20], + 'height': [135, 80, 200], + 'wspace': [0, -10,20], + 'lspace': [0, -20,20], + 'wdth': [100, 50, 150], + 'wght': [400, 100, 1000] +} + +function fontcss(fvals) { +console.log("in fontcss",fvals); + var csskeys = Object.keys(fvals); + var this_style = ""; + for (var j=0; j < csskeys.length; ++j) { + this_key = csskeys[j]; + document.getElementById("the" + this_key).innerHTML = fvals[this_key][0]; + +console.log("in fontcss", this_key, "with value", fvals[this_key][0], "/10", fvals[this_key][0]/10.0); + if (this_key == 'size') { + this_style += "font-size: " + fvals[this_key][0].toString() + "pt; " + } else if (this_key == 'height') { + this_style += "line-height: " + (fvals[this_key][0]/100.0).toString() + "; " + } else if (this_key == 'lspace') { + this_style += "letter-spacing: " + (fvals[this_key][0]/200.0).toString() + "rem; " + } else if (this_key == 'wspace') { + this_style += "word-spacing: " + (fvals[this_key][0]/50.0).toString() + "rem; " + } + } + + this_style += "font-variation-settings: "; + for (var j=0; j < csskeys.length; ++j) { + this_key = csskeys[j]; + + if (this_key == 'wdth') { + this_style += "'wdth' " + fvals[this_key][0].toString() + "," + } else if (this_key == 'wght') { + this_style += "'wght' " + fvals[this_key][0].toString() + "," + } + } + this_style = this_style.slice(0, -1); + this_style += ";"; + +console.log("returning this_style", this_style); + return this_style +} + +function choice_options(this_choice) { + console.log("choice_options of", this_choice); + val_dict = prefs_menu_vals[this_choice]; + console.log("val_dict", val_dict); + var these_keys = Object.keys(val_dict); + these_keys.sort(); + var these_choices = ""; + for (var j=0; j < these_keys.length; ++j) { + this_key = these_keys[j]; + these_choices += '
  • '; + these_choices += val_dict[this_key]; + these_choices += '
  • '; + } + return these_choices +} + + +// we have to keep track of multiple consecutive carriage returns +this_char = ""; +prev_char = ""; +prev_prev_char = ""; + +// sometimes we have to prevent Tab from changing focus +this_focused_element = ""; +prev_focused_element = ""; +prev_prev_focused_element = ""; + +var menu_neutral_background = "#ddb"; +var menu_active_background = "#fdd"; + +var recent_editing_actions = []; // we unshift to this, so most recent edit is first. + // currently just a human-readable list +var ongoing_editing_actions = []; +var old_content = {}; // to hold old versions of changed materials + +// what will happen with internationalization? +var keyletters = ["KeyA", "KeyB", "KeyC", "KeyD", "KeyE", "KeyF", "KeyG", "KeyH", "KeyI", "KeyJ", "KeyK", "KeyL", "KeyM", "KeyN", "KeyO", "KeyP", "KeyQ", "KeyR", "KeyS", "KeyT", "KeyU", "KeyV", "KeyW", "KeyX", "KeyY", "KeyZ"]; + +var movement_location_options = []; +var movement_location = 0; +var first_move = true; // used when starting to move, because object no longer occupies its original location + +Storage.prototype.setObject = function(key, value) { +// this.setItem(key, JSON.stringify(value)); + this.setItem(key, JSON.stringify(value, function(key, val) { +// console.log("key", key, "value", value, "val", val); + return val.toFixed ? Number(val.toFixed(3)) : val; +})); +} + +Storage.prototype.getObject = function(key) { + var value = this.getItem(key); + return value && JSON.parse(value); +} + +function randomstring(len) { + if (!len) { len = 10 } + return "tMP" + (Math.random() + 1).toString(36).substring(2,len) +} + +function removeItemFromList(lis, value) { + var index = lis.indexOf(value); + if (index > -1) { + lis.splice(index, 1); + } + return lis; +} + + +function textNodesUnder(node){ + var all = []; + for (node=node.firstChild;node;node=node.nextSibling){ + if (node.nodeType==3) { all.push([3, node]) } + else if (node.nodeType==1) { + all.push([1, node]) + var thistag = node.cloneNode().outerHTML; + console.log("thistag", thistag); + [thisopen, thisclose] = thistag.split("><"); + thisopen += ">"; thisclose += "<"; + thisinsides = textNodesUnder(node.cloneNode().innerHTML); +// all.push([0, thisopen]); + for (var j=0; j < thisinsides.length; ++j) { +// all.push(thisinsides[j]) + } +// all.push([0, thisclose]); + } +// probably we only want direct children +// else all = all.concat(textNodesUnder(node)); + } + return all; +} + +function wordsAllWrapped(node) { + var these_text_nodes = textNodesUnder(node); + console.log("node", node); + console.log("these_text_nodes", these_text_nodes); + for (var j=0; j < these_text_nodes.length; ++j) { + var this_text_node = these_text_nodes[j]; + var thistype = this_text_node[0]; + var thisnode = this_text_node[1]; + if (thistype == 3) { + // var these_node_words_and_spaces = these_text_nodes[j].nodeValue.split(/(\s+)/); + var these_node_words_and_spaces = thisnode.nodeValue.split(/(\s+)/); + console.log("these_node_words_and_spaces", these_node_words_and_spaces); + var spanned_words = ""; + for (var k=0; k < these_node_words_and_spaces.length; ++k) { + spanned_words += '' + these_node_words_and_spaces[k] + "" + } + var wass_text = document.createElement('div'); + wass_text.setAttribute('class', 'wastext'); + wass_text.innerHTML = spanned_words; + // these_text_nodes[j].nodeValue = spanned_words + thisnode.replaceWith(wass_text); + wass_text.outerHTML = wass_text.innerHTML + } else if (thistype == 1) { + // // leave it there, but make sure we know about it + // thisnode.classList.add("oneelement") + if (thisnode.classList.contains("process-math") || + thisnode.classList.contains("autopermalink") || + thisnode.classList.contains("latex-logo") || + thisnode.classList.contains("heading") || + // should we instead check for tags that *can* contain line breaks? + ["A", "CODE", "PRE"].includes(thisnode.tagName)) { + //wrap it in a span.oneword + var word_wrapper = document.createElement('span'); + word_wrapper.setAttribute('class', 'oneword'); + thisnode.parentNode.insertBefore(word_wrapper, thisnode); + word_wrapper.appendChild(thisnode); + } else { + wordsAllWrapped(thisnode) + } + } + } +} + +function linesAllWrapped() { + + // var testID = "p-446"; + // var testNode = document.getElementById(testID); + // var all_para = document.querySelectorAll(".para"); + // var all_para = document.querySelectorAll("SECTION P"); + var all_para = document.querySelectorAll("SECTION .para:not(.logical)"); + for (var pj = 0; pj < all_para.length; ++pj) { + testNode = all_para[pj]; + wordsAllWrapped(testNode); + console.log(" wordsAllWrapped", pj, "of", testNode); + + var these_words = document.querySelectorAll(".oneword, .oneelement"); + var this_line = []; + var all_lines = ""; + var current_height = these_words[0].getBoundingClientRect().bottom; + var word_depth = 0; + for (var j=0; j < these_words.length; ++j) { + // next line is an error if the paragraphs starts with an element + var this_word = these_words[j]; + var this_parent = this_word.parentElement; + if (this_word.classList.contains("oneword")) { + this_height = this_word.getBoundingClientRect().bottom; + if ( (Math.abs(this_height - current_height) < 10) && j > 0) { + if (this_parent == testNode && word_depth == 0) { + this_line.push(this_word.innerHTML) + } else { +console.log(word_depth, ":",this_parent == testNode, "this_parent", this_parent.tagName, "ccc", this_parent.ClassList, "xxx", this_word.innerHTML); + if (word_depth == 0) { +console.log("opening the tag", this_parent.tagName,"parent", this_parent,"of",this_word.innerHTML); + var parent_classlist = this_parent.classList; +console.log("parent_classlist", parent_classlist, "first", parent_classlist[0], "length", parent_classlist.length); + // did not work. why? var parent_classes = parent_classlist.join(" "); + this_line.push('<' + this_parent.tagName + ' class="' + parent_classlist[0] + '">'); + this_line.push(this_word.innerHTML); + word_depth += 1 + + } else { + this_line.push(this_word.innerHTML); + } + if (this_word.nextElementSibling == null) { +console.log("closing the tag", this_parent.tagName); + // need to close the wrapping element + this_line.push(""); + word_depth -= 1; + if (word_depth > 0 && this_word.parentElement.nextElementSibling == null) { + this_line.push(""); + word_depth -= 1; + } + } + } + } else { + html_line_contents = ""; + for (var k=0; k < this_line.length; ++k) { + html_line_contents += this_line[k] + } + // hack which only handles up to depth 2 + if (word_depth > 0) { + html_line_contents += "" + } + if (word_depth > 1) { + html_line_contents += "" + } + console.log("made", html_line_contents); + all_lines += '
    ' + html_line_contents + '
    '; + current_height = this_height; + this_line = [this_word.innerHTML]; + if (word_depth > 0) { + this_line.unshift('<' + this_parent.tagName + ' class="' + this_parent.classList[0] + '">') + } + if (word_depth > 1) { + this_line.unshift('<' + this_parent.tagName + ' class="' + this_parent.parentElement.classList[0] + '">') + } + } + } else { // we have an html node + this_line.push(this_word.outerHTML) + } + } + // partial last line may be dangling + // bug: need to fix closing tags at the end? maybe rely on automatic closure + if (this_line.length > 0) { + html_line_contents = ""; + for (var k=0; k < this_line.length; ++k) { + html_line_contents += this_line[k] + } + all_lines += '
    ' + html_line_contents + '
    ' + } + testNode.innerHTML = all_lines + } +} + +editorLog("adding tab listener"); + +document.addEventListener('keydown', logKeyDown); + +function nextsibligntabbable(startingplace, where) { + var candidate = startingplace.nextElementSibling; + if (where == "previous" ) { candidate = startingplace.previousElementSibling } + + while (candidate) { +console.log("candidate A", candidate); + if (candidate.hasAttribute("tabindex")) { return candidate } +console.log("candidate B", candidate); + if (where == "next" ) { candidate = candidate.nextElementSibling } + else { candidate = candidate.previousElementSibling } + } +} + +function logKeyDown(e) { + if (e.code == "ShiftLeft" || e.code == "ShiftRight" || e.code == "Shift") { return } + prev_prev_char = prev_char; + prev_char = this_char; + this_char = e; + debugLog("logKey",e,"XXX",e.code); + + var input_region = document.activeElement; + debugLog("input_region", input_region); + + if (input_region.id == "user-preferences-button") { + if (e.code == "Enter") { + document.getElementById("preferences_menu_holder").classList.toggle("hidden") + } else if (e.code == "Tab") { +console.log("tabbing to main pref menu", document.getElementById("preferences_menu_holder").firstElementChild); +console.log("tabbing to main pref menu", document.getElementById("preferences_menu_holder").firstElementChild.firstElementChild); + e.preventDefault(); + document.getElementById("preferences_menu_holder").firstElementChild.firstElementChild.focus() + } + } else if (input_region.hasAttribute("data-env")) { + var this_submenu = input_region.getElementsByTagName("ol")[0]; +console.log("this_submenu C", this_submenu); + if ((e.code == "Enter") || (e.code == "ArrowRight")) { +console.log("selecting",input_region, "which has", input_region.getElementsByTagName("ol"), "first",input_region.getElementsByTagName("ol")[0]); + this_submenu.classList.toggle("hidden") + input_region.classList.toggle("selected") + input_region.parentElement.classList.toggle("active") + } else if (e.code == "Tab" && e.shiftKey) { + e.preventDefault(); + if (input_region.classList.contains("selected")) { + this_submenu.classList.loggle("hidden"); + input_region.classList.loggle("selected"); + } else { // cycle up through the elements + // previous_sibling = input_region.previousElementSibling; + previous_sibling = nextsibligntabbable(input_region, "previous"); + if (previous_sibling) { previous_sibling.focus() } + else { + document.getElementById("preferences_menu_holder").classList.toggle("hidden"); + document.getElementById("user-preferences-button").focus(); + } + } + } else if (e.code == "Tab") { + e.preventDefault(); + if (input_region.classList.contains("selected")) { + var firstchild = this_submenu.firstElementChild; + if (firstchild.hasAttribute("tabindex")) { + firstchild.focus() + } else { + nextsibligntabbable(firstchild, "next").focus() + } + } else { // cycle through the elements +// next_sibling = input_region.nextElementSibling; + next_sibling = nextsibligntabbable(input_region, "next"); + if (next_sibling) { next_sibling.focus() } + else { + document.getElementById("preferences_menu_holder").classList.toggle("hidden"); + document.getElementById("user-preferences-button").focus(); + } + } + } + } else if (input_region.hasAttribute("data-val")) { +console.log("input_region", input_region); +console.log("input_regionparentElement", input_region.parentElement); + if ((e.code == "Enter") || (e.code == "ArrowRight")) { +console.log("font_vals is", font_vals); + var dataval = input_region.getAttribute("data-val"); + var datachange = input_region.getAttribute("data-change"); +console.log("dataval", dataval, "datachange",datachange); + if (input_region.parentElement.classList.contains("fonts")) { + font_vals[dataval][0] += parseFloat(datachange); + if (font_vals[dataval][0] < font_vals[dataval][1]) { font_vals[dataval][0] = font_vals[dataval][1] } + else if (font_vals[dataval][0] > font_vals[dataval][2]) { font_vals[dataval][0] = font_vals[dataval][2] } +console.log("font_vals are", font_vals); +console.log("css font_vals", fontcss(font_vals)); + var new_style = fontcss(font_vals); + var paras = document.getElementsByClassName('para'); + for (i = 0; i < paras.length; i++) { + paras[i].setAttribute('style', new_style); + } + } else if (input_region.parentElement.classList.contains("fontfamily")) { + document.body.setAttribute("data-font", datachange); + var checks = document.getElementsByClassName('ffcheck'); + for (i = 0; i < checks.length; i++) { + checks[i].innerHTML = ''; + } + document.getElementById("the" + datachange).innerHTML = "✔️"; + } else if (input_region.parentElement.classList.contains("avatar")) { + var checks = document.getElementsByClassName('avatarcheck'); + for (i = 0; i < checks.length; i++) { + checks[i].innerHTML = ''; + } + document.getElementById("theavatarbutton").innerHTML = dataval; + document.getElementById("the" + dataval).innerHTML = "✔️"; + + } else if (input_region.parentElement.classList.contains("atmosphere")) { + document.body.setAttribute("data-atmosphere", dataval); + var checks = document.getElementsByClassName('atmospherecheck'); + for (i = 0; i < checks.length; i++) { + checks[i].innerHTML = ''; + } + document.getElementById("the" + dataval).innerHTML = "✔️"; + + } else if (input_region.parentElement.classList.contains("ruler")) { + // could be motion or actual ruler + if (["mouse", "arrow", "eye"].includes(dataval)) { + document.body.setAttribute("data-motion", dataval); + var checks = document.getElementsByClassName('motioncheck'); + for (i = 0; i < checks.length; i++) { + checks[i].innerHTML = ''; + } + document.getElementById("the" + dataval).innerHTML = "✔️"; + } else { + if (!document.body.hasAttribute("data-ruler")) { + linesAllWrapped() + } + document.body.setAttribute("data-ruler", dataval); + var checks = document.getElementsByClassName('rulercheck'); + for (i = 0; i < checks.length; i++) { + checks[i].innerHTML = ''; + } + document.getElementById("the" + dataval).innerHTML = "✔️"; + } + + } + } else if (e.code == "Tab" && e.shiftKey) { + e.preventDefault(); +// previous_sibling = input_region.previousElementSibling; + previous_sibling = nextsibligntabbable(input_region, "previous"); + +console.log("previous_sibling",previous_sibling); + if (previous_sibling) { previous_sibling.focus() } + else { + input_region.parentElement.parentElement.parentElement.classList.toggle("active"); + input_region.parentElement.parentElement.classList.toggle("selected"); + input_region.parentElement.parentElement.focus(); + input_region.parentElement.classList.toggle("hidden"); + } + } else if (e.code == "Tab") { + e.preventDefault(); + // next_sibling = input_region.nextElementSibling; + next_sibling = nextsibligntabbable(input_region, "next"); + if (next_sibling) { next_sibling.focus() } + else { + input_region.parentElement.parentElement.parentElement.classList.toggle("active"); + input_region.parentElement.parentElement.classList.toggle("selected"); + input_region.parentElement.parentElement.focus(); + input_region.parentElement.classList.toggle("hidden"); + } + } + } + + + return; + + editorLog("input_region", input_region); + // if we are writing something, keystrokes usually are just text input + if (document.getElementById('actively_editing')) { + editorLog(" we are actively editing"); + + if (e.code == "Tab" && !document.getElementById('local_menu_holder')) { + // disabled for now + e.preventDefault(); + return "" + create_local_menu() + } else if (document.getElementById('local_menu_holder')) { + main_menu_navigator(e); + } else { + local_editing_action(e) + } + + } else if (document.getElementById('phantomobject')) { + var the_phantomobject = document.getElementById('phantomobject'); + + if (the_phantomobject.classList.contains('move')) { + move_object(e) + } else { + alert("do not know what to do with that") + } + } else { + console.log("calling main_menu_navigator"); + main_menu_navigator(e); + } +} + diff --git a/slides/index.html b/slides/index.html new file mode 100755 index 0000000..992bfff --- /dev/null +++ b/slides/index.html @@ -0,0 +1,249 @@ + + + + + + + + + + + + + +Slides for MATH 1044 + + + + + + +
    + +
    +

    Slides for MATH 1044

    § 1.1: Set Theory

    Sample Space

    +

    +Definition 1. +

    +

    The sample space \(\Omega\) is the set of all possible results of an experiment. A single result is called an outcome. A collection of results is called an event.

    Rolling Dice

    +

    +Example 2. +

    +

    We roll a 6-sided die two times. What is \(\Omega\text{?}\)

    Flipping Coins

    +

    +Example 3. +

    +

    We flip a coin three times. What is \(\Omega\text{?}\)

    Picking a Value

    +

    +Example 4. +

    +

    We pick a number between 1 and 10. What is \(\Omega\text{?}\)

    Sets

    +

    +Definition 5. +

    +

    Let \(A\) be a set. The symbol \(\in\) means "is an element of", as in \(a \in A\text{.}\) Given another set \(B\text{,}\) we say \(A\) is a subset of \(B\text{,}\) written \(A\subset B\text{,}\) to mean that every element of the set \(A\) is also an element of the set \(B\text{.}\)

    +

    +Definition 6. +

    +

    Let \(A, B \subset \Omega\text{.}\)

    +
    +
    Union
    +

    The union is \(A \cup B = \{x \mid x \in A \text{ or } x \in B\}\text{.}\)

    +
    +
    +
    Intersection
    +

    The intersection is \(A \cap B = \{x \mid x \in A \text{ and } x \in B\}\text{.}\)

    +
    +
    +
    Difference
    +

    The set difference is \(A - B = \{x \mid x \in A \text{ and } x \notin B\}\text{.}\)

    +
    +
    +
    Complement
    +

    The complement is \(A^c = \{x \in \Omega \mid x \notin A\}\text{.}\)

    +
    +
    +
    Empty Set
    +

    The empty set, written \(\emptyset\) or \(\{\}\text{,}\) contains no elements.

    +
    +

    Section 1.2

    Title 2

    +

    Some more text.

    +
    +
    + +